# Sketch - A Python-based interactive drawing program
# Copyright (C) 1997, 1998, 1999, 2001 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	USA

from Sketch.warn import pdebug

from Sketch import Rect, EmptyRect, IntersectRects, Document, GraphicsDevice,\
     SketchInternalError, QueueingPublisher, StandardColors

from Sketch.const import STATE, VIEW, DOCUMENT, LAYOUT, REDRAW
from Sketch.const import LAYER, LAYER_STATE, LAYER_ORDER, LAYER_COLOR

from tkext import PyWidget
from viewport import Viewport


class SketchView(PyWidget, Viewport, QueueingPublisher):

    document = None

    def __init__(self, master=None, toplevel = None, document = None,
		 show_visible = 0, show_printable = 1,
		 resolution = None, **kw):
	apply(PyWidget.__init__, (self, master), kw)
	Viewport.__init__(self, resolution)
	QueueingPublisher.__init__(self)
	self.toplevel = toplevel

	self.move_window_count = 0
	self.show_page_outline = 1
	self.show_visible = show_visible
	self.show_printable = show_printable
	self.gcs_initialized = 0
	self.gc = GraphicsDevice()

	self.init_transactions()
	if document is not None:
	    self.SetDocument(document)
	else:
	    self.SetDocument(Document(create_layer = 1))

    def destroy(self):
        self.unsubscribe_doc()
	PyWidget.destroy(self)
	QueueingPublisher.Destroy(self)


    def MapMethod(self):
	# when being mapped the first time, initialise the gcs. this cannot be
	# done earlier, because the hitgc creates a pixmap which currently
	# only works after the window (id) has been created. In Xt this can be
	# done in the Realize widget method (after calling the superclass'
	# method), but Tk doesn't seem to offer any similar thing.
	if not self.gcs_initialized:
	    self.init_gcs()
	    self.issue_state()

    def DestroyMethod(self):
	# make sure that gc is deleted. gc may have a shared memory ximage
	# which is not freed if the gc is not destroyed leaving unused shared
	# memory segments in the system even after the process has finished.
	self.gc = None
	PyWidget.DestroyMethod(self)

    def init_gcs(self):
	self.gc.init_gc(self.tkwin, graphics_exposures = 1)
	self.gc.draw_visible = self.show_visible
	self.gc.draw_printable = self.show_printable
	self.gc.allow_outline = 0
	self.gcs_initialized = 1
	self.default_view()
	self.set_gc_transforms()

    def default_view(self):
        self.FitPageToWindow()

    def set_gc_transforms(self):
	self.gc.SetViewportTransform(self.scale, self.doc_to_win,
				     self.win_to_doc)

    #
    #	Channels
    #

    def issue_state(self):
	self.queue_message(STATE)

    def issue_view(self):
	self.queue_message(VIEW)

    def issue_document(self):
	self.doc_changed = 1

    def queue_message(self, Publisher):
	if self.transaction:
	    QueueingPublisher.queue_message(self, Publisher)
	else:
	    self.issue(Publisher)

    def init_transactions(self):
	self.sb_update_pending = 0
	self.doc_changed = 0
	self.transaction = 0

    def begin_transaction(self):
	self.transaction = self.transaction + 1

    def end_transaction(self):
	self.transaction = self.transaction - 1
	if self.transaction == 0:
	    if self.doc_changed:
		self.issue(DOCUMENT, self.document)
	    self.sb_update_pending = 0
	    self.doc_changed = 0
	    self.flush_message_queue()
	elif self.transaction < 0:
	    raise SketchInternalError('transaction count < 0')

    #
    #	receivers
    #

    def redraw_doc(self, all, rects = None):
	if all:
	    self.clear_window()
	else:
	    map(self.clear_area_doc, rects)

    def layout_changed(self):
        self.SetPageSize(self.document.Layout().Size())
        self.set_gc_transforms()
        self.update_scrollbars()
        self.update_rulers()
	if self.show_page_outline:
	    self.clear_window()

    def layer_changed(self, *args):
	if args:
	    redraw = EmptyRect
	    if args[0] == LAYER_STATE:
		layer, visible_changed, printable_changed, outlined_changed \
		       = args[1]
		rect = layer.bounding_rect
		if rect is not EmptyRect:
		    if self.show_printable and printable_changed:
			redraw = rect
		    if self.show_visible:
			if visible_changed:
			    redraw = rect
			if outlined_changed and layer.Visible():
			    redraw = rect
	    elif args[0] == LAYER_ORDER:
		layer = args[1]
		if (self.show_printable and layer.Printable()
		    or self.show_visible and layer.Visible()):
		    redraw = layer.bounding_rect
		if len(args) > 2:
		    other = args[2]
		    if (self.show_printable and other.Printable()
			or self.show_visible and other.Visible()):
			redraw = IntersectRects(redraw, other.bounding_rect)
		    else:
			redraw = EmptyRect
	    elif args[0] == LAYER_COLOR:
		layer = args[1]
		rect = layer.bounding_rect
		if self.show_visible and rect is not EmptyRect \
		   and layer.Visible():
		    redraw = rect
	    self.clear_area_doc(redraw)

    #
    #	Widget Methods (Redraw, ... )
    #

    time_redraw = 0
    def RedrawMethod(self, region = None):
	# draw the document
	if __debug__:
	    if self.time_redraw:
		import time
		start = time.clock()
	if self.move_window_count >= 2:
	    self.clear_window(update = 0)
	self.move_window_count = 0

	region = self.do_clear(region)

	# draw document
	self.gc.InitClip()
	self.gc.ResetFontCache()
	if region:
	    self.gc.PushClip()
	    self.gc.ClipRegion(region)

        tkwin = self.tkwin
	if region:
	    x, y, w, h = region.ClipBox()
            if x < 0:
                w = w - x; x = 0
            if y < 0:
                h = h - y; y = 0
            if w > tkwin.width:
                w = tkwin.width
            if h > tkwin.height:
                h = tkwin.height
	else:
	    x = y = 0
	    w = tkwin.width
	    h = tkwin.height
	p1 = self.WinToDoc(x - 1, y - 1)
	p2 = self.WinToDoc(x + w + 1, y + h + 1)
	rect = Rect(p1, p2)

        self.gc.SetFillColor(StandardColors.white)
        self.gc.gc.FillRectangle(x, y, w, h) # XXX ugly to access gc.gc

	#	draw paper
	if self.show_page_outline:
	    w, h = self.document.PageSize()
	    self.gc.DrawPageOutline(w, h)


	self.document.Draw(self.gc, rect)
	if region:
	    self.gc.PopClip()

	if __debug__:
	    if self.time_redraw:
		pdebug('timing', 'redraw', time.clock() - start)

	return region

    def ResizedMethod(self, width, height):
	Viewport.ResizedMethod(self, width, height)
	self.gc.WindowResized(width, height)


    #
    #	Viewport- and related methods
    #
    #	extend some Viewport methods to issue VIEW whenever
    #	the displayed area changes
    #

    def ForceRedraw(self):
	# Force a redraw of the whole window
	self.clear_window()
	if __debug__:
	    #self.time_redraw = 1
	    pass

#     def SetScrollbars(self, hbar, vbar):
# 	Viewport.SetScrollbars(self, hbar, vbar)
# 	hbar.configure(jump = 1)
# 	vbar.configure(jump = 1)

    def set_origin(self, xorg, yorg, move_contents = 1):
	self.begin_transaction()
	try:
	    Viewport.set_origin(self, xorg, yorg,
				move_contents = move_contents)
	    self.set_gc_transforms()
	    self.issue_view()
	finally:
	    self.end_transaction()

    def move_window_contents(self, offx, offy):
	# implement the method needed by Viewport.set_origin
	w = self.tkwin
	width = w.width
	height = w.height
	if abs(offx) < width and abs(offy) < height:
	    w.CopyArea(w, self.gc.gc, offx, offy, width, height, 0, 0)
	    self.move_window_count = self.move_window_count + 1
	else:
	    self.clear_window()

    def SetScale(self, scale, do_center = 1):
	# Set current scale
	self.begin_transaction()
	try:
	    Viewport.SetScale(self, scale, do_center = do_center)
	    self.set_gc_transforms()
	finally:
	    self.end_transaction()

    def zoom_fit_rect(self, rect, save_viewport = 0):
	if save_viewport:
	    self.save_viewport()
	Viewport.zoom_fit_rect(self, rect)


    #
    #	other view related methods
    #
    def FitToWindow(self, selected_only = 0, save_viewport = 1):
	self.begin_transaction()
	try:
	    if selected_only:
		rect = self.document.SelectionBoundingRect()
	    else:
		rect = self.document.BoundingRect()
	    if rect:
		self.zoom_fit_rect(rect, save_viewport = save_viewport)
	finally:
	    self.end_transaction()

    def FitPageToWindow(self, save_viewport = 1):
	self.begin_transaction()
	try:
	    w, h = self.document.PageSize()
	    self.zoom_fit_rect(Rect(0, 0, w, h).grown(10),
			       save_viewport = save_viewport)
	finally:
	    self.end_transaction()


    #
    #	Outline Mode
    #
    #	Although not directly related to the viewport methods (the outline
    #	mode doesn't change the displayed area) the outline mode changes the
    #	way the drawing is displayed and thus issues VIEW.

    def SetOutlineMode(self, on = 1):
	self.begin_transaction()
	try:
	    if on:
		if self.gc.IsOutlineActive():
		    return
		else:
		    self.gc.StartOutlineMode()
		    self.hitgc.StartOutlineMode()
	    else:
		if self.gc.IsOutlineActive():
		    self.gc.EndOutlineMode()
		    self.hitgc.EndOutlineMode()
		else:
		    return
	    self.issue_view()
	    self.clear_window()
	finally:
	    self.end_transaction()

    def ToggleOutlineMode(self):
	self.SetOutlineMode(not self.IsOutlineMode())

    def IsOutlineMode(self):
	return self.gc and self.gc.IsOutlineActive()

    #
    #	Show page outline on/off
    #

    def SetPageOutlineMode(self, on = 1):
	self.begin_transaction()
	try:
	    self.show_page_outline = on
	    self.issue_view()
	    self.clear_window()
	finally:
	    self.end_transaction()

    def TogglePageOutlineMode(self):
	self.SetPageOutlineMode(not self.IsPageOutlineMode())

    def IsPageOutlineMode(self):
	return self.show_page_outline

    #
    #
    #

    def unsubscribe_doc(self):
        if self.document is not None:
            self.document.Unsubscribe(REDRAW, self.redraw_doc)
            self.document.Unsubscribe(LAYOUT, self.layout_changed)
            self.document.Unsubscribe(LAYER, self.layer_changed)

    def subscribe_doc(self):
        self.document.Subscribe(REDRAW, self.redraw_doc)
        self.document.Subscribe(LAYOUT, self.layout_changed)
        self.document.Subscribe(LAYER, self.layer_changed)

    def SetDocument(self, doc):
	self.begin_transaction()
	try:
            self.unsubscribe_doc()
	    self.document = doc
            self.subscribe_doc()
	    self.clear_window()
	    self.SetPageSize(self.document.Layout().Size())
	    self.FitPageToWindow(save_viewport = 0)
	    self.issue_document()
	    self.issue_state()
	    self.issue_view()
	finally:
	    self.end_transaction()

