# -*- coding: utf-8 -*-

# Copyright (C) 2003-2006 by Igor E. Novikov
# Copyright (C) 1997, 1998, 1999, 2003 by Bernhard Herzog
#
# This library is covered by GNU Library General Public License.
# For more info see COPYRIGHTS file in sK1 root directory.

#
# 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 Tkinter import Toplevel, IntVar, Frame, Checkbutton
from Tkinter import TOP, LEFT, RIGHT, BOTTOM, X, BOTH, TkVersion

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

from sk1sdk.libttk import TLabel, TFrame

from sk1.tkext import UpdatedButton
from sk1 import tkext



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 = 'sK1'# 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.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, borderwidth=2)

		button = UpdatedButton(frame, text=_("Apply"), command=self.do_apply, sensitivecb=self.can_apply, width=15)
		button.pack(side=TOP)
		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.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, icon='warning', dlgname='__dialog__'):
		self.title = title
		self.message = message
		if type(buttons) != type(()):
			buttons = (buttons,)
		self.buttons = buttons
		self.default = -1
		self.image = icon
		SKModal.__init__(self, master, name=dlgname)

	def build_dlg(self):
		root = self.top
		top = TFrame(root, style='FlatFrame', borderwidth=10)
		top.pack(side=TOP, fill=BOTH, expand=1)

		frame = TFrame(top, name='top', style='FlatFrame')
		frame.pack(side=TOP, fill=BOTH, expand=1)
		label = TLabel(frame, image='messagebox_' + self.image, style='FlatLabel')
		label.pack(side=LEFT, padx=5, pady=5)
		label = TLabel(frame, text=self.message, name='msg', style='FlatLabel', justify='center', anchor='center')
		label.pack(side=RIGHT, fill=BOTH, expand=1)

		frame = TFrame(top, name='bot', style='FlatFrame')
		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, pady=0)
			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)

		frame = TFrame(top, name='mid', style='FlatFrame', borderwidth=1)
		frame.pack(side=TOP, fill=X)

		root.resizable (width=0, height=0)

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

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