# Sketch - A Python-based interactive drawing program
# Copyright (C) 1997, 1998, 1999 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

#
# Base Classes for Panels
#
# A Panel is an additional toplevel window that allows the user to
# manipulate properties of the document or the current selection. Panels
# are usually also additional Views of the document.
#
#
# Classes:
#
#
# SketchPanel
#
# Base class for all panels. Implements some standard behaviour and
# features, auch as managing the current document, passing document
# messages on to its widgets, saving preferences when the window is
# closed and functions to create some standard buttons.
#
# SketchPanel is used as an immediate base class for panels that affect
# the document as a whole or are independent from properties of the
# currently selected objects (LayerPanel, GridPanel and LayoutPanel for
# instance).
#
#
# PropertyPanel(SketchPanel)
#
# A panel derived from PropertyPanel is a panel that shows properties of
# the current selected objects and allows the user to change them.
#
# Baseclass for dialogs that display and change properties of the
# current selection (FillPanel and StylePanel for instance).
#
#
# CommandPanel(SketchPanel)
#
# A base class for panels that basically offer some commands that are
# either not accesible fom the menu or are more convenient on a seperate
# Panel (CurvePanel and AlignPanel for instance)
#

from Sketch.warn import pdebug, warn_tb, INTERNAL
from Sketch import _, Publisher
from Sketch.const import SELECTION, DOCUMENT, EDITED, CLOSED
from Sketch import config
import Sketch

from Tkinter import Toplevel, IntVar, Frame, Checkbutton, Label
from Tkinter import TOP, LEFT, RIGHT, BOTTOM, X, BOTH, TkVersion

from tkext import UpdatedButton
import tkext
import skpixmaps
pixmaps = skpixmaps.PixmapTk



class SketchPanel(Publisher):

    # SketchPanel.receivers is a list of tuples of the form
    #		(CHANNEL, FUNCTION[, ARG, ...])
    # SketchPanel automatically installs FUNCTION as a receiver for
    # CHANNEL. FUNCTION will be calles with ARG,.. as arguments. derived
    # classes should copy this list and append their own additional
    # receivers.

    receivers = [(SELECTION, 'issue', SELECTION)]

    title = 'Sketch'		# The window title
    class_name = 'SKPanel'	# the class_name for resources

    x_correction = 0
    y_correction = 0

    def __init__(self, master, main_window, doc, **kw):
	self.master = master
	self.main_window = main_window
	self.document = None

	kw['class'] = self.class_name
	top = apply(Toplevel, (master,), kw)
	top.title(self.title)
	self.pref_pos_name = 'dlg_pos_' + self.__class__.__name__
	if config.preferences.panel_use_coordinates:
	    posx, posy = config.get_preference(self.pref_pos_name, (0.1, 0.1))
	    # avoid confusing behaviour of panels if the position is negative
	    # or > 1:
	    posx = max(min(posx, 1), 0)
	    posy = max(min(posy, 1), 0)

	    posx = master.winfo_rootx() + int(posx * master.winfo_width())
	    posy = master.winfo_rooty() + int(posy * master.winfo_height())
	    top.geometry('%+d%+d' % (posx, posy))

	top.transient(master)
	top.group(master)
	top.iconname(self.title)
	top.iconbitmap(pixmaps.Icon)
	top.iconmask(pixmaps.Icon_mask)
	top.protocol('WM_DELETE_WINDOW',
		     tkext.MakeMethodCommand(self.close_dlg))
	# the following is a workaround for a Tkinter bug. Tkinter 1.63
	# doesn't like bindings for the Destroy event.
	top.tk.call('bind', top._w, '<Destroy>',
		    tkext.MakeMethodCommand(self.destroyed) + ('%W',))

	self.top = top
	self.build_dlg()
	self.SetDocument(doc)

	self.main_window.Subscribe(DOCUMENT, self.doc_changed)

	if config.preferences.panel_use_coordinates \
	   and config.preferences.panel_correct_wm:
	    if not top.winfo_ismapped():
		top.wait_visibility()
	    self.x_correction = posx - top.winfo_rootx()
	    self.y_correction = posy - top.winfo_rooty()

    if __debug__:
	def __del__(self):
	    pdebug('__del__', '__del__', self)

    def deiconify_and_raise(self):
	self.top.deiconify()
	self.top.tkraise()

    def withdraw(self):
	self.top.withdraw()

    def doc_changed(self, doc):
	self.SetDocument(doc)

    def doc_has_selection(self):
	return self.document.HasSelection()

    def destroyed(self, widget, *args):
	if widget == self.top._w:
	    self.save_prefs()

    def subscribe_receivers(self):
	for info in self.receivers:
	    apply(self.document.Subscribe,
		  (info[0], getattr(self, info[1])) + info[2:])

    def unsubscribe_receivers(self):
	for info in self.receivers:
	    apply(self.document.Unsubscribe,
		  (info[0], getattr(self, info[1])) + info[2:])

    def SetDocument(self, doc):
	if self.document:
	    self.unsubscribe_receivers()
	self.document = doc
	self.init_from_doc()
	self.subscribe_receivers()

    def save_prefs(self):
	# Save preferences. Called when dialog is closed.
	master = self.master
	top = self.top
	width = float(master.winfo_width())
	height = float(master.winfo_height())
	posx = (top.winfo_rootx() + self.x_correction - master.winfo_rootx())\
	       / width
	posy = (top.winfo_rooty() + self.y_correction - master.winfo_rooty())\
	       / height
	setattr(config.preferences, self.pref_pos_name, (posx, posy))

    def close_dlg(self):
	self.issue(CLOSED, self)
	if self.document:
	    self.unsubscribe_receivers()
	self.document = None
	try:
	    self.main_window.Unsubscribe(DOCUMENT, self.doc_changed)
	except:
	    warn_tb(INTERNAL)
	self.main_window = None
	Publisher.Destroy(self)
	self.save_prefs()
	self.top.destroy()
	self.master = None
	import pax
	pax.unregister_object(self)

    def create_std_buttons(self, master):
	frame = Frame(master)

	button = UpdatedButton(frame, text = _("Apply"),
			       command = self.do_apply,
			       sensitivecb = self.can_apply)
	button.pack(side = LEFT, expand = 1, fill = X)
	button = UpdatedButton(frame, text = _("Close"),
			       command = self.close_dlg)
	button.pack(side = RIGHT, expand = 1, fill = X)

	return frame

    def build_dlg(self):
	# Build the dialog window. Must be provided by the subclasses.
	pass

    def init_from_doc(self):
	# Called whenever the document changes and from __init__
	pass

    def Update(self):
	# Called when the selection changes.
	pass

    def do_apply(self):
	# called by the `Apply' standard button to apply the settings
	pass

    def can_apply(self):
	return 1


class PropertyPanel(SketchPanel):

    receivers = SketchPanel.receivers[:]

    def __init__(self, master, main_window, doc, *args, **kw):
	self.var_auto_update = IntVar(master)
	self.var_auto_update.set(1)
	apply(SketchPanel.__init__, (self, master, main_window, doc) +args, kw)

    receivers.append((SELECTION, 'selection_changed'))
    receivers.append((EDITED, 'selection_changed'))
    def selection_changed(self, *args):
	if self.var_auto_update.get():
	    self.Update()

    def create_std_buttons(self, master, update_from = 1):
	button_frame = Frame(master)

	button = Checkbutton(button_frame, text = _("Auto Update"),
			     variable = self.var_auto_update)
	button.pack(side = TOP, expand = 1, fill = X)

	if update_from:
	    button = UpdatedButton(button_frame, text = _("Update From..."),
				   command = self.update_from_object)
	    button.pack(side = TOP, expand = 1, fill = X)

	button = UpdatedButton(button_frame, text = _("Apply"),
			       command = self.do_apply,
			       sensitivecb = self.can_apply)
	button.pack(side = LEFT, expand = 1, fill = X)
	self.Subscribe(SELECTION, button.Update)
	button = UpdatedButton(button_frame, text = _("Close"),
			       command = self.close_dlg)
	button.pack(side = RIGHT, expand = 1, fill = X)

	return button_frame

    def update_from_object(self):
	self.main_window.canvas.PickObject(self.update_from_object_cb)

    def update_from_object_cb(self, obj):
	pass

    can_apply = SketchPanel.doc_has_selection

    def SetDocument(self, doc):
	SketchPanel.SetDocument(self, doc)
	self.selection_changed()


class StylePropertyPanel(PropertyPanel):

    def can_apply(self):
        return PropertyPanel.can_apply(self) \
               or config.preferences.set_default_properties

    def set_properties(self, title, category, kw):
        import styledlg
        styledlg.set_properties(self.master, self.document, title, category,
                                kw)


class CommandPanel(SketchPanel):

    pass


#
#	Modal dialogs...
#

class SKModal:

    title = ''

    class_name = 'SKModal'

    old_focus = None
    focus_widget = None

    def __init__(self, master, **kw):
	self.master = master

	kw['class'] = self.class_name
	top = apply(Toplevel, (master,), kw)
	top.title(self.title)
	top.transient(master)
	top.group(master)
	top.iconname(self.title)
	top.iconbitmap(pixmaps.Icon)
	top.iconmask(pixmaps.Icon_mask)
	top.protocol('WM_DELETE_WINDOW', self.close_dlg)

	self.top = top
	self.build_dlg()
	mcx = master.winfo_rootx() + master.winfo_width()/ 2
	mcy = master.winfo_rooty() + master.winfo_height() / 2
	top.withdraw()
	top.update()
	width = top.winfo_reqwidth()
	height = top.winfo_reqheight()
	posx = max(min(top.winfo_screenwidth() - width, mcx - width / 2), 0)
	posy = max(min(top.winfo_screenheight() - height, mcy - height / 2), 0)
	top.geometry('%+d%+d' % (posx, posy))
	top.deiconify()

	self.result = None

    if __debug__:
	def __del__(self):
	    pdebug('__del__', '__del__', self)

    def build_dlg(self):
	pass

    def ok(self, *args):
	self.close_dlg()

    def cancel(self):
	self.close_dlg(None)

    def close_dlg(self, result = None):
	self.result = result
	if self.old_focus is not None:
	    self.old_focus.focus_set()
	    self.old_focus = None
	self.top.destroy()

    def RunDialog(self, grab = 1):
	try:
	    self.old_focus = self.top.focus_get()
	except KeyError:
	    # focus_get fails when the focus widget is a torn-off menu,
	    # since there#s no corresponding Tkinter object.
	    self.old_focus = None
	grab_widget = None
	if grab:
	    if not self.top.winfo_ismapped():
		self.top.wait_visibility()
	    grab_widget = self.top.grab_current()
	    if grab_widget is not None:
		grab_status = grab_widget.grab_status()
	    self.top.grab_set()
	if self.focus_widget is not None:
	    self.focus_widget.focus_set()
	else:
	    self.top.focus_set()
	self.result = None
	self.master.wait_window(self.top)
	if grab_widget is not None:
	    if grab_status == 'global':
		grab_widget.grab_set_global()
	    else:
		grab_widget.grab_set()
	return self.result


class MessageDialog(SKModal):

    class_name = 'SKMessageDialog'

    def __init__(self, master, title, message, buttons = _("OK"), default = 0,
		 bitmap = 'warning', dlgname = '__dialog__'):
	self.title = title
	self.message = message
	if type(buttons) != type(()):
	    buttons = (buttons,)
	self.buttons = buttons
	self.default = default
	self.bitmap = bitmap
	SKModal.__init__(self, master, name = dlgname)

    def build_dlg(self):
	top = self.top
	frame = Frame(top, name = 'top')
	frame.pack(side = TOP, fill = BOTH, expand = 1)
	label = Label(frame, bitmap = self.bitmap, name = 'bitmap')
	label.pack(side = LEFT, padx = 5, pady = 5)
	label = Label(frame, text = self.message, name = 'msg')
	label.pack(side = RIGHT, fill = BOTH, expand = 1, padx = 5, pady = 5)

	frame = Frame(top, name = 'bot')
	frame.pack(side = BOTTOM, fill = X, expand = 1)
	command = self.ok
	for i in range(len(self.buttons)):
	    button = UpdatedButton(frame, text = self.buttons[i],
				   command = command, args = i)
	    button.grid(column = i, row = 0, sticky = 'ew', padx = 10)
            if i == self.default:
                if TkVersion >= 8.0:
                    button['default'] = 'active'
                self.focus_widget = button
            else:
                if TkVersion >= 8.0:
                    button['default'] = 'normal'

	if self.default is not None:
	    top.bind('<Return>', self.invoke_default)

    def ok(self, pos):
	self.close_dlg(pos)

    def invoke_default(self, *rest):
	self.ok(self.default)
