# -*- 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 types import StringType

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 app.conf import const
import app

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

from tkext import UpdatedButton
import tkext
import tkMessageBox
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 = '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)
