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

from __future__ import with_statement
from datetime import datetime
from HTMLParser import HTMLParser
htmlparser = HTMLParser()
from time import gmtime, sleep, strftime, time
import errno
import math
import os
import re
import signal
import socket
import string
import subprocess as sp
import sys
import tarfile
import textwrap
import threading
import warnings
import xml.parsers.expat
import zipfile

import demjson_compat as demjson

import ICCProfile as ICCP
import audio
import config
from config import (defaults, getbitmap, getcfg, geticon, get_data_path,
					get_default_dpi, get_verified_path, hascfg, pyname, setcfg,
					confighome, appbasename, logdir, set_default_app_dpi)
from debughelpers import (Error, DownloadError, Info, UnloggedError,
						  UnloggedInfo, UnloggedWarning, Warn, getevtobjname,
						  getevttype, handle_error)
from log import log as log_, safe_print
from meta import name as appname
from options import debug
from ordereddict import OrderedDict
from network import ScriptingClientSocket, get_network_addr
from util_io import StringIOu as StringIO
from util_os import get_program_file, launch_file, waccess
from util_str import box, safe_str, safe_unicode, wrap
from util_xml import dict2xml
from wxaddons import (CustomEvent, FileDrop as _FileDrop, gamma_encode,
					  get_parent_frame, get_platform_window_decoration_size, wx,
					  BetterWindowDisabler, BetterTimer, EVT_BETTERTIMER)
from wexpect import split_command_line
from wxfixes import (GenBitmapButton, GenButton, GTKMenuItemGetFixedLabel,
					 PlateButton, ThemedGenButton, adjust_font_size_for_gcdc,
					 get_bitmap_disabled, get_dc_font_size, get_gcdc_font_size,
					 platebtn, set_bitmap_labels, wx_Panel, get_dialogs,
					 set_maxsize)
from lib.agw import labelbook, pygauge
from lib.agw.gradientbutton import GradientButton, CLICK, HOVER
from lib.agw.fourwaysplitter import (_TOLERANCE, FLAG_CHANGED, FLAG_PRESSED,
									 NOWHERE, FourWaySplitter,
									 FourWaySplitterEvent)
import localization as lang
import util_str

import floatspin
try:
	from wx.lib.agw import aui
	from wx.lib.agw.aui import AuiDefaultTabArt
except ImportError:
	from wx import aui
	from wx.aui import PyAuiTabArt as AuiDefaultTabArt
import wx.lib.filebrowsebutton as filebrowse
from wx.lib.agw import hyperlink
from wx.lib import fancytext
from wx.lib.statbmp import GenStaticBitmap
import wx.html

taskbar = None
if sys.platform == "win32" and sys.getwindowsversion() >= (6, 1):
	try:
		import taskbar
	except Exception, exception:
		safe_print(exception)


numpad_keycodes = [wx.WXK_NUMPAD0,
				   wx.WXK_NUMPAD1,
				   wx.WXK_NUMPAD2,
				   wx.WXK_NUMPAD3,
				   wx.WXK_NUMPAD4,
				   wx.WXK_NUMPAD5,
				   wx.WXK_NUMPAD6,
				   wx.WXK_NUMPAD7,
				   wx.WXK_NUMPAD8,
				   wx.WXK_NUMPAD9,
				   wx.WXK_NUMPAD_ADD,
				   wx.WXK_NUMPAD_DECIMAL,
				   wx.WXK_NUMPAD_ENTER,
				   wx.WXK_NUMPAD_EQUAL,
				   wx.WXK_NUMPAD_DIVIDE,
				   wx.WXK_NUMPAD_MULTIPLY,
				   wx.WXK_NUMPAD_SEPARATOR,
				   wx.WXK_NUMPAD_SUBTRACT]

nav_keycodes = [wx.WXK_DOWN,
				wx.WXK_END,
				wx.WXK_HOME,
				wx.WXK_LEFT,
				wx.WXK_PAGEDOWN,
				wx.WXK_PAGEUP,
				wx.WXK_RIGHT,
				wx.WXK_TAB,
				wx.WXK_UP,
				wx.WXK_NUMPAD_DOWN,
				wx.WXK_NUMPAD_END,
				wx.WXK_NUMPAD_HOME,
				wx.WXK_NUMPAD_LEFT,
				wx.WXK_NUMPAD_PAGEDOWN,
				wx.WXK_NUMPAD_PAGEUP,
				wx.WXK_NUMPAD_RIGHT,
				wx.WXK_NUMPAD_UP]

processing_keycodes = [wx.WXK_ESCAPE, wx.WXK_RETURN, wx.WXK_INSERT,
					   wx.WXK_DELETE, wx.WXK_BACK]

modifier_keycodes = [wx.WXK_SHIFT, wx.WXK_CONTROL, wx.WXK_ALT, wx.WXK_COMMAND]


def Property(func):
	return property(**func())


class AboutDialog(wx.Dialog):

	def __init__(self, *args, **kwargs):
		kwargs["style"] = wx.DEFAULT_DIALOG_STYLE & ~(wx.RESIZE_BORDER | 
													  wx.MAXIMIZE_BOX)
		kwargs["name"] = "aboutdialog"
		wx.Dialog.__init__(self, *args, **kwargs)

		# Set an icon so text isn't crammed to the left of the titlebar under
		# Windows (other platforms?)
		self.SetIcons(config.get_icon_bundle([256, 48, 32, 16], appname))

		self.Sizer = wx.BoxSizer(wx.VERTICAL)
		self.panel = wx.ScrolledWindow(self, style=wx.VSCROLL)
		self.Sizer.Add(self.panel, 1, flag=wx.EXPAND)

		self.panel.Sizer = wx.BoxSizer(wx.VERTICAL)

		if "gtk3" in wx.PlatformInfo:
			# Fix background color not working for panels under GTK3
			self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

	if "gtk3" in wx.PlatformInfo:
		OnEraseBackground = wx_Panel.__dict__["OnEraseBackground"]
	
	def OnClose(self, event):
		self.Hide()

	def Layout(self):
		self.Sizer.SetSizeHints(self)
		self.panel.SetScrollRate(2, 2)
		self.Sizer.Layout()
		if wx.Platform == "__WXGTK__":
			for child in self.panel.Children:
				# Fix height of children
				child.MinSize = child.MinSize[0], child.Size[1]

	def add_items(self, items):
		self.line = wx.Panel(self, size=(-1,1))
		self.line.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT))
		self.Sizer.Add(self.line, flag=wx.EXPAND)
		self.btnpanel = wx.Panel(self)
		self.btnpanel.Sizer = wx.GridSizer(1, 1, 0, 0)
		self.Sizer.Add(self.btnpanel, flag=wx.EXPAND)
		self.ok = wx.Button(self.btnpanel, -1, lang.getstr("close"))
		self.btnpanel.Sizer.Add(self.ok, flag=wx.ALL | wx.ALIGN_CENTER,
								border=12)
		self.Bind(wx.EVT_BUTTON, self.OnClose, id=self.ok.GetId())
		for item in items:
			if (not isinstance(item, wx.Window) and
				hasattr(item, "__iter__") and
				filter(lambda subitem: not isinstance(subitem, (int, float)),
					   item)):
				sizer = wx.BoxSizer(wx.HORIZONTAL)
				self.add_item(sizer, self.panel.Sizer)
				for subitem in item:
					self.add_item(subitem, sizer)
			else:
				self.add_item(item, self.panel.Sizer)
		self.ok.SetDefault()
		self.ok.SetFocus()

	def add_item(self, item, sizer):
		if isinstance(item, (HyperLinkCtrl, ThemedGenButton)):
			item.BackgroundColour = self.panel.BackgroundColour
		if isinstance(item, wx.Window):
			pointsize = 10
			font = item.GetFont()
			if item.GetLabel() and font.GetPointSize() > pointsize:
				font.SetPointSize(pointsize)
				item.SetFont(font)
		flag = wx.ALIGN_CENTER_HORIZONTAL
		if isinstance(item, (wx.Panel, wx.PyPanel)):
			flag |= wx.EXPAND
		elif sizer is self.panel.Sizer:
			flag |= wx.LEFT | wx.RIGHT
		sizer.Add(item, 0, flag, 12)


class AnimatedBitmap(wx.PyControl):

	""" Animated bitmap """

	def __init__(self, parent, id=wx.ID_ANY, bitmaps=None, range=(0, -1),
				 loop=True, pos=wx.DefaultPosition, size=wx.DefaultSize,
				 style=wx.NO_BORDER):
		"""
		Create animated bitmap
		
		bitmaps should be an array of bitmaps.
		range should be the indexes of the range of frames that should be looped.
		-1 means last frame.
		
		"""
		self.dpiscale = getcfg("app.dpi") / get_default_dpi()
		if self.dpiscale > 1:
			size = tuple(int(round(v * self.dpiscale)) if v != -1 else v
						 for v in size)
		wx.PyControl.__init__(self, parent, id, pos, size, style)
		self._minsize = size
		self.SetBitmaps(bitmaps or [], range, loop)
		# Avoid flickering under Windows
		self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
		self.Bind(wx.EVT_PAINT, self.OnPaint)
		self._timer = BetterTimer(self)
		self.Bind(EVT_BETTERTIMER, self.OnTimer, self._timer)
		self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
	
	def OnDestroy(self, event):
		self._timer.Stop()
		del self._timer

	def DoGetBestSize(self):
		return self._minsize

	def OnPaint(self, event):
		dc = wx.BufferedPaintDC(self)
		dc.SetBackground(wx.Brush(self.Parent.BackgroundColour))     
		dc.Clear()
		if self._bitmaps:
			if self.frame > len(self._bitmaps) - 1:
				self.frame = len(self._bitmaps) - 1
			if self.dpiscale > 1:
				dc.SetUserScale(self.dpiscale, self.dpiscale)
			dc.DrawBitmap(self._bitmaps[self.frame], 0, 0, True)

	def OnTimer(self, event):
		first_frame, last_frame = self.range
		if first_frame < 0:
			first_frame += len(self._bitmaps)
		if last_frame < 0:
			last_frame += len(self._bitmaps)
		frame = self.frame
		if frame < last_frame:
			frame += 1
		elif self.loop:
			frame = first_frame
		if frame != self.frame:
			self.frame = frame
			self.Refresh()

	def Play(self, fps=24):
		self._timer.Start(1000.0 / fps)

	def Stop(self):
		self._timer.Stop()

	def SetBitmaps(self, bitmaps, range=(0, -1), loop=True):
		w, h = self._minsize
		for bitmap in bitmaps:
			w = max(bitmap.Size[0], w)
			h = max(bitmap.Size[0], h)
		self._minsize = w, h
		self._bitmaps = bitmaps
		self.loop = loop
		self.range = range
		self.frame = 0


class AuiBetterTabArt(AuiDefaultTabArt):

	def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False):
		"""
		Draws a single tab.

		:param `dc`: a :class:`DC` device context;
		:param `wnd`: a :class:`Window` instance object;
		:param `page`: the tab control page associated with the tab;
		:param Rect `in_rect`: rectangle the tab should be confined to;
		:param integer `close_button_state`: the state of the close button on the tab;
		:param bool `paint_control`: whether to draw the control inside a tab (if any) on a :class:`MemoryDC`.
		"""

		# if the caption is empty, measure some temporary text
		caption = page.caption
		if not caption:
			caption = "Xj"

		dc.SetFont(self._selected_font)
		if hasattr(dc, "GetFullMultiLineTextExtent"):
			# Phoenix
			selected_textx, selected_texty = dc.GetMultiLineTextExtent(caption)
		else:
			# Classic
			selected_textx, selected_texty, dummy = dc.GetMultiLineTextExtent(caption)

		dc.SetFont(self._normal_font)
		if hasattr(dc, "GetFullMultiLineTextExtent"):
			# Phoenix
			normal_textx, normal_texty = dc.GetMultiLineTextExtent(caption)
		else:
			# Classic
			normal_textx, normal_texty, dummy = dc.GetMultiLineTextExtent(caption)

		control = page.control

		# figure out the size of the tab
		tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap,
											 page.active, close_button_state, control)

		tab_height = self._tab_ctrl_height - 3
		tab_width = tab_size[0]
		tab_x = in_rect.x
		tab_y = in_rect.y + in_rect.height - tab_height

		caption = page.caption

		# select pen, brush and font for the tab to be drawn

		if page.active:
		
			dc.SetFont(self._selected_font)
			textx, texty = selected_textx, selected_texty
		
		else:
		
			dc.SetFont(self._normal_font)
			textx, texty = normal_textx, normal_texty

		if not page.enabled:
			dc.SetTextForeground(self._tab_disabled_text_colour)
			pagebitmap = page.dis_bitmap
		else:
			dc.SetTextForeground(self._tab_text_colour(page))
			pagebitmap = page.bitmap
			
		# create points that will make the tab outline

		clip_width = tab_width
		if tab_x + clip_width > in_rect.x + in_rect.width:
			clip_width = in_rect.x + in_rect.width - tab_x

		# since the above code above doesn't play well with WXDFB or WXCOCOA,
		# we'll just use a rectangle for the clipping region for now --
		dc.SetClippingRegion(tab_x, tab_y, clip_width+1, tab_height-3)

		border_points = [wx.Point() for i in xrange(6)]
		agwFlags = self.GetAGWFlags()
		
		if agwFlags & aui.AUI_NB_BOTTOM:
		
			border_points[0] = wx.Point(tab_x,             tab_y)
			border_points[1] = wx.Point(tab_x,             tab_y+tab_height-6)
			border_points[2] = wx.Point(tab_x+2,           tab_y+tab_height-4)
			border_points[3] = wx.Point(tab_x+tab_width-2, tab_y+tab_height-4)
			border_points[4] = wx.Point(tab_x+tab_width,   tab_y+tab_height-6)
			border_points[5] = wx.Point(tab_x+tab_width,   tab_y)
		
		else: #if (agwFlags & aui.AUI_NB_TOP) 
		
			border_points[0] = wx.Point(tab_x,             tab_y+tab_height-4)
			border_points[1] = wx.Point(tab_x,             tab_y+2)
			border_points[2] = wx.Point(tab_x+2,           tab_y)
			border_points[3] = wx.Point(tab_x+tab_width-2, tab_y)
			border_points[4] = wx.Point(tab_x+tab_width,   tab_y+2)
			border_points[5] = wx.Point(tab_x+tab_width,   tab_y+tab_height-4)
		
		# TODO: else if (agwFlags & aui.AUI_NB_LEFT) 
		# TODO: else if (agwFlags & aui.AUI_NB_RIGHT) 

		drawn_tab_yoff = border_points[1].y
		drawn_tab_height = border_points[0].y - border_points[1].y

		if page.active:
		
			# draw active tab

			# draw base background colour
			r = wx.Rect(tab_x, tab_y, tab_width, tab_height)
			dc.SetPen(self._base_colour_pen)
			dc.SetBrush(self._base_colour_brush)
			dc.DrawRectangle(r.x+1, r.y+1, r.width-1, r.height-4)

			# this white helps fill out the gradient at the top of the tab
			dc.SetPen( wx.Pen(self._tab_gradient_highlight_colour) )
			dc.SetBrush( wx.Brush(self._tab_gradient_highlight_colour) )
			dc.DrawRectangle(r.x+2, r.y+1, r.width-3, r.height-4)

			# these two points help the rounded corners appear more antialiased
			dc.SetPen(self._base_colour_pen)
			dc.DrawPoint(r.x+2, r.y+1)
			dc.DrawPoint(r.x+r.width-2, r.y+1)

			# set rectangle down a bit for gradient drawing
			r.SetHeight(r.GetHeight()/2)
			r.x += 2
			r.width -= 3
			r.y += r.height
			r.y -= 2

			# draw gradient background
			top_colour = self._tab_bottom_colour
			bottom_colour = self._tab_top_colour
			dc.GradientFillLinear(r, bottom_colour, top_colour, wx.NORTH)
		
		else:
		
			# draw inactive tab

			r = wx.Rect(tab_x, tab_y+1, tab_width, tab_height-3)

			# start the gradent up a bit and leave the inside border inset
			# by a pixel for a 3D look.  Only the top half of the inactive
			# tab will have a slight gradient
			r.x += 2
			r.y += 1
			r.width -= 3
			r.height /= 2

			# -- draw top gradient fill for glossy look
			top_colour = self._tab_inactive_top_colour
			bottom_colour = self._tab_inactive_bottom_colour
			dc.GradientFillLinear(r, bottom_colour, top_colour, wx.NORTH)

			r.y += r.height
			r.y -= 1

			# -- draw bottom fill for glossy look
			top_colour = self._tab_inactive_bottom_colour
			bottom_colour = self._tab_inactive_bottom_colour
			dc.GradientFillLinear(r, top_colour, bottom_colour, wx.SOUTH)
		
		# draw tab outline
		dc.SetPen(self._border_pen)
		dc.SetBrush(wx.TRANSPARENT_BRUSH)
		dc.DrawPolygon(border_points)

		# there are two horizontal grey lines at the bottom of the tab control,
		# this gets rid of the top one of those lines in the tab control
		if page.active:
		
			if agwFlags & aui.AUI_NB_BOTTOM:
				dc.SetPen(wx.Pen(self._background_bottom_colour))
				
			# TODO: else if (agwFlags & aui.AUI_NB_LEFT) 
			# TODO: else if (agwFlags & aui.AUI_NB_RIGHT) 
			else: # for aui.AUI_NB_TOP
				dc.SetPen(self._base_colour_pen)
				
			dc.DrawLine(border_points[0].x+1,
						border_points[0].y,
						border_points[5].x,
						border_points[5].y)
		
		text_offset = tab_x + 8
		close_button_width = 0

		if close_button_state != aui.AUI_BUTTON_STATE_HIDDEN:
			close_button_width = self._active_close_bmp.GetWidth()

			if agwFlags & aui.AUI_NB_CLOSE_ON_TAB_LEFT:
				text_offset += close_button_width - 5
				
		bitmap_offset = 0
		
		if pagebitmap.IsOk():
		
			bitmap_offset = tab_x + 8
			if agwFlags & aui.AUI_NB_CLOSE_ON_TAB_LEFT and close_button_width:
				bitmap_offset += close_button_width - 5

			# draw bitmap
			dc.DrawBitmap(pagebitmap,
						  bitmap_offset,
						  drawn_tab_yoff + (drawn_tab_height/2) - (pagebitmap.GetHeight()/2),
						  True)

			text_offset = bitmap_offset + pagebitmap.GetWidth()
			text_offset += 3 # bitmap padding

		else:

			if agwFlags & aui.AUI_NB_CLOSE_ON_TAB_LEFT == 0 or not close_button_width:
				text_offset = tab_x + 8
		
		draw_text = aui.ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width)

		ypos = drawn_tab_yoff + (drawn_tab_height)/2 - (texty/2) - 1

		offset_focus = text_offset     

		if control is not None:
			try:
				if control.GetPosition() != wx.Point(text_offset+1, ypos):
					control.SetPosition(wx.Point(text_offset+1, ypos))

				if not control.IsShown():
					control.Show()

				if paint_control:
					bmp = aui.TakeScreenShot(control.GetScreenRect())
					dc.DrawBitmap(bmp, text_offset+1, ypos, True)
					
				controlW, controlH = control.GetSize()
				text_offset += controlW + 4
				textx += controlW + 4
			except wx.PyDeadObjectError:
				pass
			
		# draw tab text
		if hasattr(dc, "GetFullMultiLineTextExtent"):
			# Phoenix
			rectx, recty = dc.GetMultiLineTextExtent(draw_text)
		else:
			# Classic
			rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text)
		textfg = dc.GetTextForeground()
		shadow = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)
		dc.SetTextForeground(shadow)
		dc.DrawLabel(draw_text, wx.Rect(text_offset + 1, ypos + 1, rectx, recty))
		dc.SetTextForeground(textfg)
		dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty))

		# draw focus rectangle
		if (agwFlags & aui.AUI_NB_NO_TAB_FOCUS) == 0:
			self.DrawFocusRectangle(dc, page, wnd, draw_text, offset_focus, bitmap_offset, drawn_tab_yoff, drawn_tab_height, rectx, recty)
		
		out_button_rect = wx.Rect()
		
		# draw close button if necessary
		if close_button_state != aui.AUI_BUTTON_STATE_HIDDEN:
		
			bmp = self._disabled_close_bmp

			if close_button_state == aui.AUI_BUTTON_STATE_HOVER:
				bmp = self._hover_close_bmp
			elif close_button_state == aui.AUI_BUTTON_STATE_PRESSED:
				bmp = self._pressed_close_bmp

			shift = (agwFlags & aui.AUI_NB_BOTTOM and [1] or [0])[0]

			if agwFlags & aui.AUI_NB_CLOSE_ON_TAB_LEFT:
				rect = wx.Rect(tab_x + 4, tab_y + (tab_height - bmp.GetHeight())/2 - shift,
							   close_button_width, tab_height)
			else:
				rect = wx.Rect(tab_x + tab_width - close_button_width - 1,
							   tab_y + (tab_height - bmp.GetHeight())/2 - shift,
							   close_button_width, tab_height)

			rect = aui.IndentPressedBitmap(rect, close_button_state)
			dc.DrawBitmap(bmp, rect.x, rect.y, True)

			out_button_rect = rect
		
		out_tab_rect = wx.Rect(tab_x, tab_y, tab_width, tab_height)

		dc.DestroyClippingRegion()

		return out_tab_rect, out_button_rect, x_extent

	def SetDefaultColours(self, base_colour=None):
		"""
		Sets the default colours, which are calculated from the given base colour.

		:param `base_colour`: an instance of :class:`Colour`. If defaulted to ``None``, a colour
		 is generated accordingly to the platform and theme.
		"""

		if base_colour is None:
			base_colour = aui.GetBaseColour()

		self.SetBaseColour( base_colour )
		self._border_colour = aui.StepColour(base_colour, 75)
		self._border_pen = wx.Pen(self._border_colour)

		self._background_top_colour = aui.StepColour(self._base_colour, 90)
		self._background_bottom_colour = aui.StepColour(self._base_colour, 120)
		
		self._tab_top_colour = self._base_colour
		self._tab_bottom_colour = aui.StepColour(self._base_colour, 130)
		self._tab_gradient_highlight_colour = aui.StepColour(self._base_colour, 130)

		self._tab_inactive_top_colour = self._background_top_colour
		self._tab_inactive_bottom_colour = aui.StepColour(self._base_colour, 110)
		
		self._tab_text_colour = lambda page: page.text_colour
		self._tab_disabled_text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)


class AutocompleteComboBox(wx.ComboBox):

	def __init__(self, *args, **kwargs):
		wx.ComboBox.__init__(self, *args, **kwargs)
		self.Bind(wx.EVT_TEXT, self.OnText)
		self.Bind(wx.EVT_CHAR, self.OnChar)
		self.Bind(wx.EVT_COMBOBOX, self.OnComboBox) 
		self._ignore_text_evt = False
		self._popup_shown = None
		if hasattr(wx, "EVT_COMBOBOX_DROPDOWN"):
			self.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.OnComboBoxDropDown)
		if hasattr(wx, "EVT_COMBOBOX_CLOSEUP"):
			self.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.OnComboBoxCloseUp)

	def OnComboBox(self, event):
		self._ignore_text_evt = True
		event.Skip()

	def OnComboBoxDropDown(self, event):
		self._popup_shown = True
		event.Skip()

	def OnComboBoxCloseUp(self, event):
		self._popup_shown = False
		event.Skip()
		
	def OnChar(self, event):
		if event.GetKeyCode() == 8 or event.ControlDown():  # Backspace or modifier
			self._ignore_text_evt = True
		event.Skip()

	def OnText(self, event):
		if self._ignore_text_evt:
			self._ignore_text_evt = False
			return
		current_text = event.GetString().lower()
		found = False
		for i, choice in enumerate(self.Items):
			if choice.lower().startswith(current_text):
				self._ignore_text_evt = True
				if self._popup_shown is not None:
					self.SetSelection(i)
				self.SetValue(choice)
				self.SetInsertionPoint(len(current_text))
				self.SetMark(len(current_text), len(choice))
				found = True
				break
		if not found:
			event.Skip()


class BaseApp(wx.App):

	""" Application base class implementing common functionality. """

	_exithandlers = []
	_query_end_session = None

	def __init__(self, *args, **kwargs):
		# Fix sys.prefix for SetInstallPrefix used inside wx.App which does not
		# decode to Unicode when calling SetInstallPrefix. DisplayCAL when
		# bundled by Py2App can't be run from a path containing Unicode
		# characters under Mac OS X otherwise.
		prefix = sys.prefix
		sys.prefix = safe_unicode(sys.prefix)

		wx.App.__init__(self, *args, **kwargs)

		# Restore prefix
		sys.prefix = prefix

		if (not kwargs.get("clearSigInt", True) or
			len(args) == 4 and not args[3]):
			# Install our own SIGINT handler so we can do cleanup on receiving
			# SIGINT
			safe_print("Installing SIGINT handler")
			signal.signal(signal.SIGINT, self.signal_handler)
			# This timer allows processing of SIGINT / CTRL+C, because normally
			# with clearSigInt=False, SIGINT / CTRL+C are only processed during
			# UI events
			self._signal_timer = wx.Timer(self)
			self.Bind(wx.EVT_TIMER, lambda e: None, self._signal_timer)
			self._signal_timer.Start(100)

	def signal_handler(self, signum, frame):
		if signum == signal.SIGINT:
			safe_print("Received SIGINT")
			safe_print("Sending query to end session...")
			self.ProcessEvent(wx.CloseEvent(wx.EVT_QUERY_END_SESSION.evtType[0]))

	def OnInit(self):
		self.AppName = pyname
		set_default_app_dpi()
		# We use a lock so we can make sure the exit handlers are executed
		# properly before we actually exit when receiving OS
		# logout/reboot/shutdown events. This is needed because it is not
		# guaranteed that when we receive EVT_QUERY_END_SESSION and close
		# the main application window (which will normally exit the main event
		# loop unless BaseApp.ExitOnFrameDelete is False) that OnExit (which'll
		# execute the exit handlers) will run right away because normally it'll
		# run after the main event loop exits (i.e. in the next iteration of the
		# main loop after an implicit or explicit call of ExitMainLoop).
		# Also, we need to call OnExit explicitly when receiving EVT_END_SESSION
		# because there will be no next iteration of the event loop (the OS
		# simply kills the application after the handler is executed). If OnExit
		# was already called before as a result of receiving
		# EVT_QUERY_END_SESSION and exiting the main event loop (remember this
		# is not guaranteed because of the way the event loop works!) and did
		# already finish, this'll do nothing and we can just exit. If OnExit is
		# still executing the exit handlers and thus holds the lock, the lock
		# makes sure that we wait for OnExit to finish before we explicitly call
		# sys.exit(0) (which'll make sure exit handlers registered by atexit
		# will run) to finally exit for good before the OS tries to kill the
		# application.
		self._lock = threading.Lock()
		self.Bind(wx.EVT_QUERY_END_SESSION, self.query_end_session)
		self.Bind(wx.EVT_END_SESSION, self.end_session)
		if os.getenv("XDG_SESSION_TYPE") == "wayland":
			# Fix erroneous extra spacing around window contents under
			# Wayland. First frame will get size right, but not min size,
			# so create a 'dummy' frame, call Show() which is needed so
			# we can fix subsequently created windows, then instantly get
			# rid of it. Ugly.
			dummy = wx.Frame(None)
			# May not be needed, but we don't want to risk actually
			# showing our dummy frame
			dummy.SetTransparent(0)
			# Need to call Show() to 'fix' subsequently created windows
			dummy.Show()
			# Now we can get rid of it
			dummy.Close()
		return True

	def MacOpenFiles(self, paths):
		if (self.TopWindow and
			isinstance(getattr(self.TopWindow, "droptarget", None), FileDrop)):
			self.TopWindow.droptarget.OnDropFiles(0, 0, paths)

	def MacReopenApp(self):
		if self.TopWindow and self.TopWindow.IsShownOnScreen():
			self.TopWindow.Raise()

	def process_argv(self, count=0):
		paths = []
		for arg in sys.argv[1:]:
			if os.path.isfile(arg):
				paths.append(safe_unicode(arg))
				if len(paths) == count:
					break
		if paths:
			self.MacOpenFiles(paths)
			return paths

	def OnExit(self):
		safe_print("Executing BaseApp.OnExit()")
		with self._lock:
			if BaseApp._exithandlers:
				safe_print("Running application exit handlers")
				BaseApp._run_exitfuncs()
		if hasattr(wx.App, "OnExit"):
			return wx.App.OnExit(self)
		else:
			return 0

	@staticmethod
	def _run_exitfuncs():
		"""run any registered exit functions

		_exithandlers is traversed in reverse order so functions are executed
		last in, first out.
		"""
		# Inspired by python's 'atexit' module.
		# It's important that the exit handlers run *before* the actual main
		# loop exits, otherwise they might not run at all if the app is closed
		# externally (e.g. if the OS asks the app to close on logout).
		# Using the 'atexit' module will NOT work!

		exc_info = None
		while BaseApp._exithandlers:
			func, args, kwargs = BaseApp._exithandlers.pop()
			try:
				func(*args, **kwargs)
			except SystemExit:
				exc_info = sys.exc_info()
			except:
				import traceback
				safe_print("Error in BaseApp._run_exitfuncs:")
				safe_print(traceback.format_exc())

		if exc_info is not None:
			raise exc_info[0], exc_info[1], exc_info[2]

	@staticmethod
	def register_exitfunc(func, *args, **kwargs):
		BaseApp._exithandlers.append((func, args, kwargs))

	def query_end_session(self, event):
		safe_print("Received query to end session")
		if event.CanVeto():
			safe_print("Can veto")
		else:
			safe_print("Cannot veto")
		if self.TopWindow and self.TopWindow is not self._query_end_session:
			if not isinstance(self.TopWindow, wx.Dialog):
				safe_print("Trying to close main top-level application window...")
				if self.TopWindow.Close(force=not event.CanVeto()):
					self.TopWindow.listening = False
					self._query_end_session = self.TopWindow
					safe_print("Main top-level application window processed close event")
					return
				else:
					safe_print("Failed to close main top-level application window")
			if event.CanVeto():
				event.Veto()
				safe_print("Vetoed query to end session")

	def end_session(self, event):
		safe_print("Ending session")
		self.ExitMainLoop()
		# We may need to call OnExit() explicitly because there is not
		# guaranteed to be a next iteration of the main event loop
		try:
			self.OnExit()
		except:
			# Yes, this can fail with a TypeError, amazingly enough :-(
			# Apparently sometimes, wx already shuts down the application
			# and OnExit will be None. We assume in this case that OnExit
			# already ran.
			pass
		# Calling sys.exit makes sure that exit handlers registered by atexit
		# will run
		safe_print("Calling sys.exit(0)")
		sys.exit(0)


active_window = None
responseformats = {}

class BaseFrame(wx.Frame):

	""" Main frame base class. """
	
	def __init__(self, *args, **kwargs):
		wx.Frame.__init__(self, *args, **kwargs)
		self.init()

	def FindWindowByName(self, name):
		""" wxPython Phoenix FindWindowByName descends into the parent windows,
		we don't want that """
		for child in self.GetAllChildren():
			if hasattr(child, "Name") and child.Name == name:
				return child

	def __OnDestroy(self, event):
		if event.GetEventObject() is self:
			self.listening = False
		event.Skip()

	def close_all(self):
		windows = list(wx.GetTopLevelWindows())
		while windows:
			win = windows.pop()
			if (isinstance(win, (AboutDialog, BaseInteractiveDialog,
								   ProgressDialog)) or
				  win.__class__ is wx.Dialog) and win.IsShown():
				if win.IsModal():
					wx.CallAfter(win.EndModal, wx.ID_CANCEL)
					safe_print("Closed modal dialog", win)
				else:
					wx.CallAfter(win.Close)
					safe_print("Closed dialog", win)
			elif isinstance(win, wx.Frame):
				wx.CallAfter(win.Close)
				safe_print("Closed", win)

	def init(self):
		self.Bind(wx.EVT_ACTIVATE, self.activate_handler)
		self.Bind(wx.EVT_WINDOW_DESTROY, self.__OnDestroy)

	def init_menubar(self):
		self.MenuBar = wx.MenuBar()
		filemenu = wx.Menu()
		quit = filemenu.Append(-1 if wx.VERSION < (2, 9) else wx.ID_EXIT,
							   "&%s\tCtrl+Q" % lang.getstr("menuitem.quit"))
		self.Bind(wx.EVT_MENU, lambda event: self.Close(), quit)
		if sys.platform != "darwin":
			self.MenuBar.Append(filemenu, "&%s" % lang.getstr("menu.file"))

	def activate_handler(self, event):
		global active_window
		active_window = self

	def listen(self):
		if isinstance(getattr(sys, "_appsocket", None), socket.socket):
			addr, port = sys._appsocket.getsockname()
			if addr == "0.0.0.0":
				try:
					addr = get_network_addr()
				except socket.error:
					pass
			safe_print(lang.getstr("app.listening", (addr, port)))
			self.listening = True
			self.listener = threading.Thread(target=self.connection_handler,
											 name="ScriptingHost.ConnectionHandler")
			self.listener.start()

	def connect(self, ip, port):
		if getattr(self, "conn", None):
			self.conn.disconnect()
		conn = ScriptingClientSocket()
		conn.settimeout(3)
		try:
			conn.connect((ip, port))
		except socket.error, exception:
			del conn
			return exception
		return conn

	def connection_handler(self):
		""" Handle socket connections """
		self._msghandlercount = 0
		while self and getattr(self, "listening", False):
			try:
				# Wait for connection
				conn, addrport = sys._appsocket.accept()
			except socket.timeout:
				continue
			except socket.error, exception:
				if exception.errno == errno.EWOULDBLOCK:
					sleep(.05)
					continue
				break
			if (addrport[0] != "127.0.0.1" and
				not getcfg("app.allow_network_clients")):
				# Network client disallowed
				conn.close()
				safe_print(lang.getstr("app.client.network.disallowed", addrport))
				sleep(.2)
				continue
			try:
				conn.settimeout(.2)
			except socket.error, exception:
				conn.close()
				safe_print(lang.getstr("app.client.ignored", exception))
				sleep(.2)
				continue
			safe_print(lang.getstr("app.client.connect", addrport))
			self._msghandlercount += 1
			threading.Thread(target=self.message_handler,
							 name="ScriptingHost.MessageHandler-%d" %
								  self._msghandlercount,
							 args=(conn, addrport)).start()
		sys._appsocket.close()

	def message_handler(self, conn, addrport):
		""" Handle messages sent via socket """
		responseformats[conn] = "plain"
		buffer = ""
		while self and getattr(self, "listening", False):
			# Wait for incoming message
			try:
				incoming = conn.recv(4096)
			except socket.timeout:
				continue
			except socket.error, exception:
				if exception.errno == errno.EWOULDBLOCK:
					sleep(.05)
					continue
				break
			else:
				if not incoming:
					break
				buffer += incoming
				while "\n" in buffer and self and getattr(self, "listening", False):
					end = buffer.find("\n")
					line = buffer[:end].strip()
					buffer = buffer[end + 1:]
					if line:
						command_timestamp = datetime.now().strftime("%Y-%m-%dTH:%M:%S.%f")
						line = safe_unicode(line, "UTF-8")
						safe_print(lang.getstr("app.incoming_message",
											   addrport + (line, )))
						data = split_command_line(line)
						response = None
						# Non-UI commands
						if data[0] == "getappname" and len(data) == 1:
							response = pyname
						elif data[0] == "getcfg" and len(data) < 3:
							if len(data) == 2:
								# Return cfg value
								if data[1] in defaults:
									if responseformats[conn].startswith("xml"):
										response = {"name": data[1],
													"value": getcfg(data[1])}
									else:
										response = {data[1]: getcfg(data[1])}
								else:
									response = "invalid"
							else:
								# Return whole cfg
								if responseformats[conn] != "plain":
									response = []
								else:
									response = OrderedDict()
								for name in sorted(defaults):
									value = getcfg(name, False)
									if value is not None:
										if responseformats[conn] != "plain":
											response.append({"name": name,
															 "value": value})
										else:
											response[name] = value
						elif data[0] == "getcommands" and len(data) == 1:
							response = sorted(self.get_commands())
						elif data[0] == "getdefault" and len(data) == 2:
							if data[1] in defaults:
								if responseformats[conn] != "plain":
									response = {"name": data[1],
												"value": defaults[data[1]]}
								else:
									response = {data[1]: defaults[data[1]]}
							else:
								response = "invalid"
						elif data[0] == "getdefaults" and len(data) == 1:
							if responseformats[conn] != "plain":
								response = []
							else:
								response = OrderedDict()
							for name in sorted(defaults):
								if responseformats[conn] != "plain":
									response.append({"name": name,
													 "value": defaults[name]})
								else:
									response[name] = defaults[name]
						elif data[0] == "getvalid" and len(data) == 1:
							if responseformats[conn] != "plain":
								response = {}
								for section, options in (("ranges",
														  config.valid_ranges),
														 ("values",
														  config.valid_values)):
									valid = response[section] = []
									for name, values in options.iteritems():
										valid.append({"name": name,
													  "values": values})
							else:
								response = {"ranges": config.valid_ranges,
											"values": config.valid_values}
							if responseformats[conn] == "plain":
								valid = []
								for section, options in response.iteritems():
									valid.append("[%s]" % section)
									for name, values in options.iteritems():
										valid.append("%s = %s" %
													 (name,
													  " ".join(demjson.encode(value)
															   for value in values)))
								response = valid
						elif (data[0] == "setresponseformat" and
							  len(data) == 2 and
							  data[1] in ("json", "json.pretty", "plain", "xml",
										  "xml.pretty")):
							responseformats[conn] = data[1]
							response = "ok"
						if response is not None:
							self.send_response(response, data, conn,
											   command_timestamp)
							continue
						# UI commands
						wx.CallAfter(self.finish_processing, data, conn,
									 command_timestamp)
		try:
			conn.shutdown(socket.SHUT_RDWR)
		except socket.error, exception:
			if exception.errno != errno.ENOTCONN:
				safe_print("Warning - could not shutdown connection:", exception)
		safe_print(lang.getstr("app.client.disconnect", addrport))
		conn.close()
		responseformats.pop(conn)

	def get_app_state(self, format):
		win = self.get_top_window()
		if isinstance(win, wx.Dialog) and win.IsShown():
			if isinstance(win, (DirDialog, FileDialog)):
				response = format_ui_element(win, format)
				if format != "plain":
					response.update({"message": win.Message, "path": win.Path})
				else:
					response = [response, demjson.encode(win.Message),
								"path", demjson.encode(win.Path)]
			elif isinstance(win, (AboutDialog, BaseInteractiveDialog,
								  ProgressDialog)):
				response = format_ui_element(win, format)
				if format == "plain":
					response = [response]
				if hasattr(win, "message") and win.message:
					if format != "plain":
						response["message"] = win.message.Label
					else:
						response.append(demjson.encode(win.message.Label))
				# Ordering in tab order
				for child in win.GetAllChildren():
					if (child and isinstance(child, (FlatShadedButton,
													 GenButton, wx.Button)) and
						child.TopLevelParent is win and
						child.IsShownOnScreen() and child.IsEnabled()):
						if format != "plain":
							if not "buttons" in response:
								response["buttons"] = []
							response["buttons"].append(child.Label)
						else:
							if not "buttons" in response:
								response.append("buttons")
							response.append(demjson.encode(child.Label))
			elif win.__class__ is wx.Dialog:
				response = format_ui_element(win, format)
				if format == "plain":
					response = [response]
			else:
				return "blocked"
			if format == "plain":
				response = " ".join(response)
			return response
		if hasattr(self, "worker") and self.worker.is_working():
			return "busy"
		return "idle"

	def get_commands(self):
		return self.get_common_commands()

	def get_common_commands(self):
		cmds = ["abort", "activate [window]",
				"alt", "cancel", "ok [filename]", "close [window]",
				"echo <string>", "exit [force]", "getactivewindow", "getappname",
				"getcellvalues [window] <grid>", "getcommands",
				"getcfg [option]", "getdefault <option>", "getdefaults",
				"getmenus", "getmenuitems [menu]", "getstate",
				"getuielement [window] <element>", "getuielements [window]",
				"getvalid", "getwindows",
				"interact [window] <element> [setvalue value]",
				"invokemenu <menu> <menuitem>",
				"restore-defaults [category...]",
				"setcfg <option> <value>", "setresponseformat <format>"]
		if hasattr(self, "update_controls"):
			cmds.append("refresh")
			if hasattr(self, "panel"):
				cmds.append("setlanguage <languagecode>")
		return cmds

	def get_scripting_hosts(self):
		scripting_hosts = []
		lockfilebasenames = [appbasename]
		for module in ["3DLUT-maker", "curve-viewer", "profile-info",
					   "scripting-client", "synthprofile", "testchart-editor",
					   "VRML-to-X3D-converter", "apply-profiles"]:
			lockfilebasenames.append("%s-%s" % (appbasename, module))
		for lockfilebasename in lockfilebasenames:
				lockfilename = os.path.join(confighome, "%s.lock" %
														lockfilebasename)
				if os.path.isfile(lockfilename):
					ports = []
					try:
						with open(lockfilename) as lockfile:
							for line in lockfile.read().splitlines():
								if line:
									if ":" in line:
										pid, port = line.split(":", 1)
									else:
										port = line
									if port:
										ports.append(port)
					except EnvironmentError, exception:
						# This shouldn't happen
						safe_print("Warning - could not read lockfile %s:" %
								   lockfilename, exception)
					else:
						for port in ports:
							scripting_hosts.append("127.0.0.1:%s %s" %
												   (port, lockfilebasename))
		scripting_hosts.sort()
		return scripting_hosts

	def get_top_window(self):
		windows = [active_window or self] + list(wx.GetTopLevelWindows())
		while windows:
			win = windows.pop()
			if win and isinstance(win, wx.Dialog) and win.IsShown():
				break
		return (win and win.IsShown() and win) or self

	def global_navigate(self):
		# We have wx.Window.Navigate, but it only works for siblings in the
		# same parent frame. Re-implement our own version.
		focus = self.FindFocus()
		if focus:
			children = self.GetAllChildren()
			if focus in children:
				start = i = children.index(focus)
				while True:
					if wx.GetKeyState(wx.WXK_SHIFT):
						i -= 1
					else:
						i += 1
					if i < 0:
						i = len(children) - 1
					elif i > len(children) - 1:
						i = 0
					if i == start:
						return False
					focus = children[i]
					if (focus and isinstance(focus, (wx.Control,
													 wx.grid.Grid)) and
						focus.AcceptsFocus() and not focus.HasFocus()):
						focus.SetFocus()
						return True
		return False

	def process_data(self, data):
		""" Override this method in derived classes """
		return "invalid"

	def finish_processing(self, data, conn, command_timestamp):
		if not responseformats.get(conn):
			# Client connection has broken down in the meantime
			return
		state = self.get_app_state("plain")
		dialog = isinstance(self.get_top_window(), wx.Dialog)
		if data[0] == "setcfg" and len(data) == 4 and data[-1] == "force":
			force_setcfg = data.pop()
		else:
			force_setcfg = False
		if ((state in ("blocked", "busy") or dialog) and
			data[0] not in ("abort", "activate", "alt", "cancel", "close",
							"exit", "getactivewindow", "getcellvalues",
							"getmenus", "getmenuitems",
							"getstate", "getuielement", "getuielements",
							"getwindows", "interact", "ok") and
			not force_setcfg):
			if dialog:
				state = "blocked"
			self.send_response(state, data, conn, command_timestamp)
			return
		win = None
		child = None
		response = "ok"
		if data[0] in ("abort", "close", "exit"):
			if (hasattr(self, "worker") and not self.worker.abort_all() and
				self.worker.is_working()):
				response = "failed"
			elif data[0] in ("close", "exit"):
				if len(data) == 2:
					if data[0] == "exit":
						win = None
					else:
						win = get_toplevel_window(data[1])
				else:
					win = self.get_top_window()
				if win:
					state = self.get_app_state("plain")
					if state == "blocked" or (state != "idle" and
											  data[0] == "exit"):
						response = state
					elif (state not in ("busy", "idle") and
						  win is not self.get_top_window()):
						response = "blocked"
					elif (isinstance(win, (AboutDialog, BaseInteractiveDialog,
										   ProgressDialog)) or
						  win.__class__ is wx.Dialog):
						if win.IsModal():
							wx.CallAfter(win.EndModal, wx.ID_CANCEL)
						else:
							wx.CallAfter(win.Close)
					elif isinstance(win, wx.Frame):
						wx.CallAfter(win.Close)
					else:
						response = "failed"
				else:
					if data[0] == "exit" and len(data) == 2:
						wx.CallAfter(self.close_all)
					else:
						response = "invalid"
			else:
				response = "invalid"
		elif data[0] == "activate" and len(data) < 3:
			response = "ok"
			if len(data) < 2:
				win = self.get_top_window()
			else:
				try:
					id_name_label = int(data[1])
				except ValueError:
					id_name_label = data[1]
				for win in reversed(wx.GetTopLevelWindows()):
					if (win.Id == id_name_label or win.Name == id_name_label or
						win.Label == id_name_label) and win.IsShown():
						break
				else:
					win = None
			if win and win.IsShown():
				if win.IsIconized():
					win.Restore()
				win.Raise()
			else:
				response = "invalid"
		elif data[0] in ("alt", "cancel", "ok") and (len(data) == 1 or
													 (data[0] == "ok" and
													  len(data) == 2)):
			response = "invalid"
			win = self.get_top_window()
			if isinstance(win, (DirDialog, FileDialog)):
				if data[0] in ("ok", "cancel"):
					if len(data) > 1:
						# Path
						win.SetPath(data[1])
					if win.IsModal():
						wx.CallAfter(win.EndModal,
									 {"ok": wx.ID_OK,
									  "cancel": wx.ID_CANCEL}[data[0]])
					elif data[0] == "cancel":
						wx.CallAfter(win.Close)
					response = "ok"
			elif isinstance(win, (AboutDialog, BaseInteractiveDialog,
								  ProgressDialog)):
				if hasattr(win, data[0]):
					ctrl = getattr(win, data[0])
					if ctrl.IsEnabled():
						if win.IsModal():
							wx.CallAfter(win.EndModal, ctrl.Id)
						elif isinstance(ctrl, (FlatShadedButton, GenButton,
											   wx.Button)):
							event = wx.CommandEvent(wx.EVT_BUTTON.typeId,
													ctrl.Id)
							event.SetEventObject(ctrl)
							wx.CallAfter(ctrl.ProcessEvent, event)
						response = "ok"
					else:
						response = "forbidden"
		elif (data[0] == "echo" and
			  "echo <string>" in self.get_common_commands() and len(data) > 1):
			txt = " ".join(data[1:])
			safe_print(txt)
		elif data[0] == "invokemenu" and len(data) == 3:
			if self.get_app_state("plain") == "idle":
				menubar = self.GetMenuBar()
				response = "invalid"
			else:
				menubar = None
				response = "forbidden"
			if menubar:
				try:
					menu_pos = int(data[1])
				except ValueError:
					menu_pos = menubar.FindMenu(data[1])
				if (menu_pos not in (wx.NOT_FOUND, None) and
					menu_pos > -1 and menu_pos < menubar.GetMenuCount() and
					menubar.IsEnabledTop(menu_pos)):
					menu = menubar.GetMenu(menu_pos)
					try:
						menuitem_id = int(data[2])
					except ValueError:
						menuitem_id = menu.FindItem(data[2])
					if menuitem_id not in (wx.NOT_FOUND, None):
						menuitem = menu.FindItemById(menuitem_id)
						if menuitem.IsEnabled():
							event = wx.CommandEvent(wx.EVT_MENU.typeId,
													menuitem_id)
							event.SetEventObject(menu)
							wx.CallAfter(self.ProcessEvent, event)
							response = "ok"
						else:
							response = "forbidden"
		elif data[0] == "getactivewindow" and len(data) == 1:
			win = self.get_top_window()
			response = format_ui_element(win, responseformats[conn])
			win = None
		elif data[0] == "getcellvalues" and len(data) in (2, 3):
			response = "invalid"
			if len(data) == 3:
				win = get_toplevel_window(data[1])
			else:
				win = self.get_top_window()
			if win:
				name = data[-1]
				child = get_widget(win, name)
				if (child and isinstance(child, wx.grid.Grid) and
					child.IsShownOnScreen()):
					response = []
					for row in xrange(child.GetNumberRows()):
						values = []
						for col in xrange(child.GetNumberCols()):
							values.append(child.GetCellValue(row, col))
						if responseformats[conn] == "plain":
							values = demjson.encode(values).strip("[]").replace('","', '" "')
						response.append(values)
				elif child is False:
					response = "forbidden"
				child = None
		elif data[0] == "getmenus" and len(data) == 1:
			menus = []
			menubar = self.GetMenuBar()
			if menubar:
				for i, (menu, label) in enumerate(menubar.GetMenus()):
					label = label.lstrip("&_")
					if responseformats[conn] != "plain":
						menus.append({"label": label, "position": i,
									  "enabled": menubar.IsEnabledTop(i)})
					else:
						menus.append("%s %s %s" % (i,
												   demjson.encode(label),
												   "enabled"
												   if menubar.IsEnabledTop(i)
												   else "disabled"))
			response = menus
		elif data[0] == "getmenuitems" and len(data) < 3:
			menuitems = []
			menulabels = []
			menus = []
			menubar = self.GetMenuBar()
			if menubar:
				if len(data) == 2:
					try:
						menu_pos_label = int(data[1])
					except ValueError:
						menu_pos_label = data[1]
				for i, (menu, label) in enumerate(menubar.GetMenus()):
					label = label.lstrip("&_")
					if (len(data) == 2 and label != menu_pos_label and
						i != menu_pos_label):
						continue
					if (responseformats[conn] != "plain" and
						not label in menulabels):
						menuitems = []
						menulabels.append(label)
						menus.append({"label": label, "position": i,
									  "enabled": menubar.IsEnabledTop(i),
									  "menuitems": menuitems})
					for menuitem in menu.GetMenuItems():
						if menuitem.IsSeparator():
							continue
						if responseformats[conn] != "plain":
							menuitems.append({"label": menuitem.Label,
											  "id": menuitem.Id,
											  "enabled": menubar.IsEnabledTop(i) and
														 menuitem.IsEnabled(),
											  "checkable": menuitem.IsCheckable(),
											  "checked": menuitem.IsChecked()})
						else:
							menuitems.append("%s %s %s %s %s%s%s" %
											 (i, demjson.encode(label),
											  menuitem.Id,
											  demjson.encode(menuitem.Label),
											  "enabled"
											  if menubar.IsEnabledTop(i) and
											  menuitem.IsEnabled()
											  else "disabled",
											  " checkable" if menuitem.IsCheckable()
											  else "",
											  " checked" if menuitem.IsChecked()
											  else ""))
			if responseformats[conn] != "plain":
				response = menus
			else:
				response = menuitems
		elif data[0] == "getstate" and len(data) == 1:
			response = self.get_app_state(responseformats[conn])
		elif data[0] == "getuielement" and len(data) in (2, 3):
			response = "invalid"
			if len(data) == 3:
				win = get_toplevel_window(data[1])
			else:
				win = self.get_top_window()
			if win:
				name = data[-1]
				child = get_widget(win, name)
				if child and child.IsShownOnScreen():
					response = format_ui_element(child, responseformats[conn])
				elif child is False:
					response = "forbidden"
				child = None
		elif data[0] == "getuielements" and len(data) < 3:
			uielements = []
			if len(data) == 2:
				win = get_toplevel_window(data[1])
			else:
				win = self.get_top_window()
			if win:
				# Ordering in tab order
				for child in win.GetAllChildren():
					if is_scripting_allowed(win, child):
						uielements.append(format_ui_element(child,
															responseformats[conn]))
				child = None
				response = uielements
			else:
				response = "invalid"
		elif data[0] == "getwindows" and len(data) == 1:
			windows = filter(lambda win: win.IsShown(), wx.GetTopLevelWindows())
			response = [format_ui_element(win, responseformats[conn])
						for win in windows]
			win = None
		elif data[0] == "interact" and len(data) > 1 and len(data) < 6:
			response = "invalid"
			value = None
			args = data[1:]
			if len(args) > 2 and args[-2] == "setvalue":
				value = args.pop()
				args.pop()
			name = args.pop()
			state = self.get_app_state("plain")
			if state == "blocked":
				win = None
				response = state
			elif args:
				# Window name specified
				win = get_toplevel_window(args[0])
				if state not in ("busy",
								 "idle") and win is not self.get_top_window():
					win = None
					response = "blocked"
			else:
				win = self.get_top_window()
			if win:
				child = get_widget(win, name)
				if child is False or (child and (not child.IsShownOnScreen() or
												 not child.IsEnabled())):
					response = "forbidden"
				elif child:
					event = None
					if value is not None:
						# Set value
						values = value.split(",", 2)
						if isinstance(child, floatspin.FloatSpin):
							try:
								child.SetValue(float(value))
							except:
								response = "failed"
							else:
								event = floatspin.EVT_FLOATSPIN
						elif (isinstance(child, (CustomCheckBox,
												 wx.CheckBox)) and
							  value in ("0", "1", "false", "true")):
							child.SetValue(bool(demjson.decode(value)))
							event = wx.EVT_CHECKBOX
						elif isinstance(child, wx.ComboBox):
							child.SetValue(value)
							event = wx.EVT_TEXT
						elif isinstance(child, wx.Choice):
							# NOTE: Check for wx.ComboBox first because it is a
							# subclass of wx.Choice!
							if value in child.Items:
								child.SetStringSelection(value)
								event = wx.EVT_CHOICE
						elif isinstance(child, (aui.AuiNotebook,
												labelbook.FlatBookBase,
												wx.Notebook)):
							for page_idx in xrange(child.GetPageCount()):
								if child.GetPageText(page_idx) == value:
									child.SetSelection(page_idx)
									event = wx.EVT_NOTEBOOK_PAGE_CHANGED
									break
						elif isinstance(child, wx.grid.Grid) and len(values) == 3:
							try:
								row, col = (int(v) for v in values[:2])
							except ValueError:
								row, col = -1, -1
							if (row > -1 and col > -1 and
								row < child.GetNumberRows() and
								col < child.GetNumberCols()):
								if (child.IsEditable() and
									not child.IsReadOnly(row, col)):
									child.SetCellValue(row, col, values[2])
									event = wx.grid.GridEvent(-1,
															  wx.grid.EVT_GRID_CELL_CHANGE.evtType[0],
															  child,
															  row,
															  col)
								else:
									response = "forbidden"
						elif isinstance(child, wx.ListCtrl):
							for row in xrange(child.GetItemCount()):
								item = []
								for col in xrange(child.GetColumnCount()):
									item.append(child.GetItem(row, col).GetText())
								if " ".join(item) == value:
									state = child.GetItemState(row, wx.LIST_STATE_SELECTED)
									child.Select(row, not state &
													  wx.LIST_STATE_SELECTED)
									if state & wx.LIST_STATE_SELECTED:
										event = wx.EVT_LIST_ITEM_DESELECTED
									else:
										event = wx.EVT_LIST_ITEM_SELECTED
									break
						elif (isinstance(child, wx.RadioButton) and
							  value in ("0", "1", "false", "true")):
							child.SetValue(bool(demjson.decode(value)))
							event = wx.EVT_RADIOBUTTON
						elif isinstance(child, wx.Slider):
							try:
								child.SetValue(int(value))
							except:
								response = "failed"
							else:
								event = wx.EVT_SLIDER
						elif isinstance(child, wx.SpinCtrl):
							try:
								child.SetValue(int(value))
							except:
								response = "failed"
							else:
								event = wx.EVT_SPINCTRL
						elif isinstance(child, wx.TextCtrl):
							child.ChangeValue(value)
							event = wx.EVT_TEXT
					elif isinstance(child, (FlatShadedButton,
											GenButton, wx.Button)):
						event = wx.EVT_BUTTON
					if event:
						if isinstance(child, CustomCheckBox):
							ctrl = child._cb
						else:
							ctrl = child
						events = [event]
						if event is wx.EVT_SPINCTRL:
							events.append(wx.EVT_TEXT)
						elif event is wx.EVT_TEXT and win.FindFocus() != ctrl:
							events.append(wx.EVT_KILL_FOCUS)
						for event in events:
							if not isinstance(event, wx.Event):
								event = wx.CommandEvent(event.typeId, ctrl.Id)
								if isinstance(child, (CustomCheckBox, wx.CheckBox,
													  wx.RadioButton)):
									event.SetInt(int(ctrl.Value))
								event.SetEventObject(ctrl)
							if not isinstance(ctrl, wx.Notebook):
								# Bus error under Mac OS X
								wx.CallAfter(ctrl.ProcessEvent, event)
						wx.CallLater(1, child.Refresh)
						response = "ok"
		elif data[0] == "setcfg" and len(data) == 3:
			# Set configuration option
			if data[1] in defaults:
				value = data[2]
				if value == "null":
					value = None
				elif value == "false":
					value = 0
				elif value == "true":
					value = 1
				elif defaults[data[1]] is not None:
					try:
						value = type(defaults[data[1]])(value)
					except (UnicodeEncodeError, ValueError):
						pass
				setcfg(data[1], value)
				if getcfg(data[1], False) != value:
					response = "failed"
			else:
				response = "invalid"
		else:
			try:
				response = self.process_data(data)
			except Exception, exception:
				safe_print(exception)
				if responseformats[conn] != "plain":
					response = {"class": exception.__class__.__name__,
								"error": safe_unicode(exception)}
				else:
					response = "error " + demjson.encode(safe_unicode(exception))
			else:
				# Some commands can be overriden, check response
				if response == "invalid":
					if (data[0] == "refresh" and len(data) == 1 and
						hasattr(self, "update_controls")):
						self.update_controls()
						self.update_layout()
						response = "ok"
					elif data[0] == "restore-defaults":
						for name in defaults:
							if len(data) > 1:
								include = False
								for category in data[1:]:
									if name.startswith(data[1]):
										include = True
										break
								if not include:
									continue
							setcfg(name, None)
						response = "ok"
					elif (data[0] == "setlanguage" and len(data) == 2 and
						  hasattr(self, "panel") and
						  hasattr(self, "update_controls")):
						setcfg("lang", data[1])
						self.panel.Freeze()
						self.setup_language()
						self.update_controls()
						self.update_layout()
						self.panel.Thaw()
						response = "ok"
		if (data[0].startswith("get") or data[0] in ("refresh",
													 "restore-defaults",
													 "setcfg") or
			response != "ok"):
			# No interaction with UI
			relayfunc = lambda func, *args: func(*args)
		else:
			# Interaction with UI
			# Prevent actual file dialogs blocking the UI - need to restore
			# original values after processing
			wx.DirDialog = DirDialog
			wx.FileDialog = FileDialog
			# Use CallLater so GUI methods have a chance to run before we send
			# our response
			relayfunc = lambda func, *args: wx.CallLater(55, func, *args)
			relayfunc(restore_path_dialog_classes)
		relayfunc(self.send_response, response, data, conn, command_timestamp,
				  child or win)

	def send_response(self, response, data, conn, command_timestamp, win=None):
		if not responseformats.get(conn):
			# Client connection has broken down in the meantime
			return
		if response == "invalid":
			safe_print(lang.getstr("app.incoming_message.invalid"))
		if responseformats[conn] != "plain":
			if not isinstance(response, (basestring, list)):
				response = [response]
			command = {"name": data[0], "timestamp": command_timestamp}
			if data[1:]:
				command["arguments"] = data[1:]
			response = {"command": command,
						"result": response,
						"timestamp": datetime.now().strftime("%Y-%m-%dTH:%M:%S.%f")}
			if win:
				response["object"] = format_ui_element(win, responseformats[conn])
		if responseformats[conn].startswith("json"):
			response = demjson.encode(response,
									  compactly=responseformats[conn] == "json")
		elif responseformats[conn].startswith("xml"):
			response = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
						(responseformats[conn] == "xml.pretty" and "\n" or "") +
						dict2xml(response, "response",
								 pretty=responseformats[conn] == "xml.pretty"))
		else:
			if isinstance(response, dict):
				response = ["%s = %s" % (name, value) for name, value in
							response.iteritems()]
			if isinstance(response, list):
				response = "\n".join(response)
		try:
			conn.sendall("%s\4" % safe_str(response, "UTF-8"))
		except socket.error, exception:
			safe_print(exception)

	def send_command(self, scripting_host_name_suffix, command):
		lock_name = appbasename
		scripting_host = appname
		if scripting_host_name_suffix:
			lock_name += "-" + scripting_host_name_suffix
			scripting_host += "-" + scripting_host_name_suffix
		try:
			for host in self.get_scripting_hosts():
				ip_port, name = host.split(None, 1)
				if name == lock_name:
					ip, port = ip_port.split(":", 1)
					port = int(port)
					conn = self.connect(ip, port)
					if isinstance(conn, Exception):
						raise conn
					# Check if we're actually connected to the right
					# application (if it terminated unexpectedly, something
					# else may have grabbed the port)
					conn.send_command("getappname")
					response = conn.get_single_response()
					if response == scripting_host:
						conn.send_command(command)
						response = conn.get_single_response()
						safe_print("%s %s returned" % (scripting_host, command),
								   response)
					else:
						safe_print("Warning - %s not running "
								   "under expected port" % scripting_host, port)
					del conn
					return response
			else:
				safe_print("Warning - %s not running?" % scripting_host)
		except Exception, exception:
			safe_print("Warning - couldn't talk to %s:" % scripting_host,
					   exception)
			return exception

	def focus_handler(self, event=None):
		# IMPORTANT: event can either be EVT_TEXT, EVT_SET_FOCUS, EVT_CLOSE,
		# EVT_MENU or EVT_BUTTON.
		# This means the available event methods will be different, e.g.
		# only EVT_SET_FOCUS will have a GetWindow method, and EVT_MENU's
		# GetEventObject method returns the menu item which doesn't have
		# most methods that controls would have.
		if not event:
			event = CustomEvent(wx.EVT_SET_FOCUS.evtType[0], self)
		if debug:
			if hasattr(event.GetEventObject(), "GetId"):
				safe_print("[D] focus_handler called for ID %s %s %s, event type "
						   "%s %s" % (event.GetEventObject().GetId(), 
									  getevtobjname(event, self), 
									  event.GetEventObject().__class__,
									  event.GetEventType(), getevttype(event)))
			else:
				safe_print("[D] focus_handler called for %s %s, event type "
						   "%s %s" % (getevtobjname(event, self), 
									  event.GetEventObject().__class__,
									  event.GetEventType(), getevttype(event)))
			if (hasattr(event, "GetWindow") and event.GetWindow() and
				event.GetEventObject() != event.GetWindow()):
					safe_print("[D] Focus moving from control ID %s %s %s" %
							   (event.GetWindow().GetId(), 
								event.GetWindow().GetName(), 
								event.GetWindow().__class__))
			if getattr(self, "last_focused_ctrl", None):
				safe_print("[D] Last focused control: ID %s %s %s" %
						   (self.last_focused_ctrl.GetId(),
							self.last_focused_ctrl.GetName(),
							self.last_focused_ctrl.__class__))
		if (getattr(self, "last_focused_ctrl", None) and
			isinstance(self.last_focused_ctrl, wx.ComboBox) and
			self.last_focused_ctrl != event.GetEventObject() and
			self.last_focused_ctrl.IsShownOnScreen() and
			event.GetEventObject() and
			(not hasattr(event.GetEventObject(), "IsShownOnScreen") or
			 event.GetEventObject().IsShownOnScreen())):
			catchup_event = wx.FocusEvent(wx.EVT_KILL_FOCUS.evtType[0], 
										  self.last_focused_ctrl.GetId())
			if debug:
				safe_print("[D] Last focused control ID %s %s %s processing "
						   "catchup event type %s %s" % 
						   (self.last_focused_ctrl.GetId(), 
							self.last_focused_ctrl.GetName(), 
							self.last_focused_ctrl.__class__, 
							catchup_event.GetEventType(), 
							getevttype(catchup_event)))
			if self.last_focused_ctrl.ProcessEvent(catchup_event):
				if debug:
					safe_print("[D] Last focused control processed catchup "
							   "event")
				self.last_focused_ctrl = None
		if (hasattr(event.GetEventObject(), "GetId") and
			callable(event.GetEventObject().GetId) and
			isinstance(event.GetEventObject(), wx.Control) and
			event.GetEventObject() != getattr(self, "last_focused_ctrl", None) and
			hasattr(event.GetEventObject(), "IsShownOnScreen") and
			event.GetEventObject().IsShownOnScreen()):
		   	if debug:
				safe_print("[D] Setting last focused control to ID %s %s %s"
						   % (event.GetEventObject().GetId(),
							  getevtobjname(event, self),
							  event.GetEventObject().__class__))
			self.last_focused_ctrl = event.GetEventObject()
		if (isinstance(event, wx.FocusEvent) or
			isinstance(event.GetEventObject(), wx.ComboBox)):
			event.Skip()
	
	def setup_language(self):
		"""
		Substitute translated strings for menus, controls, labels and tooltips.
		
		"""
		if not hasattr(self, "_menulabels"):
			# Needed for Phoenix because custom attributes attached to menus
			# are not retained
			self._menulabels = {}
		if not hasattr(self, "_ctrllabels"):
			# Needed for Phoenix because custom attributes attached to controls
			# may not be retained
			self._ctrllabels = {}
		if not hasattr(self, "_tooltipstrings"):
			# Needed for Phoenix because custom attributes attached to controls
			# may not be retained
			self._tooltipstrings = {}
		
		# Title
		if not hasattr(self, "_Title"):
			# Backup un-translated label
			title = self._Title = self.Title
		else:
			# Restore un-translated label
			title = self._Title
		translated = lang.getstr(title)
		if translated != title:
			self.Title = translated
		
		# Menus
		menubar = self.menubar if hasattr(self, "menubar") else self.GetMenuBar()
		if menubar:
			for menu, label in menubar.GetMenus():
				menu_pos = menubar.FindMenu(label)
				if not menu in self._menulabels:
					# Backup un-translated label
					self._menulabels[menu] = label
				menubar.SetMenuLabel(menu_pos, "&" + lang.getstr(
									 GTKMenuItemGetFixedLabel(self._menulabels[menu])))
				self.setup_menu_language(menu)
			if sys.platform == "darwin":
				wx.GetApp().SetMacHelpMenuTitleName(lang.getstr("menu.help"))
			if isinstance(menubar, wx.MenuBar):
				self.SetMenuBar(menubar)
		
		# Controls and labels
		for child in self.GetAllChildren():
			if isinstance(child, (wx.StaticText, wx.Control,
								  BetterStaticFancyText,
								  BitmapBackgroundPanelText)):
				if not child in self._ctrllabels:
					# Backup un-translated label
					label = self._ctrllabels[child] = child.Label
				else:
					# Restore un-translated label
					label = self._ctrllabels[child]
				translated = lang.getstr(label)
				if translated != label:
					if isinstance(child, wx.Button):
						translated = translated.replace("&", "&&")
					elif (isinstance(child, BetterStaticFancyText) and
						  child.Name.endswith("_info_text")):
						if lang.getcode() == "ko":
							# Korean text runs too wide on info panels with
							# default wrap of 119
							child.maxlen = 70
						else:
							child.maxlen = 119
						if child.Name == "display_instrument_warmup_info_text":
							# Fix spacing - exactly one blank line between
							# warmup info and instrument/disptech info text
							translated += '\n&#160;'
					child.Label = translated
				if child.ToolTip:
					if not child in self._tooltipstrings:
						# Backup un-translated tooltip
						tooltipstr = self._tooltipstrings[child] = child.ToolTip.Tip
					else:
						# Restore un-translated tooltip
						tooltipstr = self._tooltipstrings[child]
					translated = lang.getstr(tooltipstr)
					if translated != tooltipstr:
						child.SetToolTipString(translated)
			elif isinstance(child, FileBrowseBitmapButtonWithChoiceHistory):
				if child.history:
					selection = child.textControl.GetSelection()
					child.textControl.Clear()
					for path in child.history:
						child.textControl.Append(child.GetName(path))
					child.textControl.SetSelection(selection)

	def setup_menu_language(self, menu):
		if not hasattr(self, "_menuitems"):
			self._menuitems = {}
		if not menu in self._menuitems:
			# Backup un-translated labels
			self._menuitems[menu] = [(item, item.Label) for item in 
									 menu.GetMenuItems()]
		for item, label in self._menuitems[menu]:
			if item.Label:
				label = GTKMenuItemGetFixedLabel(label)
				if item.Accel:
					item.Text = lang.getstr(label) + "\t" + \
								item.Accel.ToString()
				else:
					item.Text = lang.getstr(label)
			if item.SubMenu:
				self.setup_menu_language(item.SubMenu)
	
	def update_layout(self):
		""" Update main window layout. """
		border_lr = self.Size[0] - self.ClientSize[0]
		border_tb = self.Size[1] - self.ClientSize[1]
		clientarea = self.GetDisplay().ClientArea
		sw = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
		if sys.platform not in ("darwin", "win32"): # Linux
			if os.getenv("XDG_SESSION_TYPE") == "wayland":
				# Client-side decorations
				safety_margin = 0
			else:
				# Assume server-side decorations
				safety_margin = 40
		else:
			safety_margin = 20
		w, h = 0, 0
		for child in self.Children:
			if child.IsShown():
				if child.Sizer:
					size = child.Sizer.CalcMin()
				else:
					size = child.BestSize
				w = max(size[0], w)
				h += max(size[1], 0)
		minsize = (min(clientarea[2] - border_lr, w + sw),
				   min(clientarea[3] - border_tb - safety_margin, h))
		clientsize = (min(clientarea[2] - border_lr, self.ClientSize[0]),
					  min(clientarea[3] - border_tb - safety_margin,
						  self.ClientSize[1]))
		if not self.IsIconized() and not self.IsMaximized():
			if os.getenv("XDG_SESSION_TYPE") == "wayland":
				self.MaxSize = (-1, -1)
			if ((minsize[0] > clientsize[0] or minsize[1] != clientsize[1] or not
				 getattr(self, "_layout", False))):
				if not getattr(self, "_layout", False):
					clientsize = minsize
				elif os.getenv("XDG_SESSION_TYPE") != "wayland":
					# XXX this causes brief flickering to previous height on
					# manual resize under Wayland
					clientsize = clientsize[0], minsize[1]
				self.Sizer.SetMinSize((max(minsize[0], clientsize[0]),
									   max(minsize[1], clientsize[1])))
				self.GetSizer().SetSizeHints(self)
				self.GetSizer().Layout()
				self._layout = True
			else:
				self.Layout()
			if os.getenv("XDG_SESSION_TYPE") == "wayland":
				self.MaxSize = self.Size
				wx.CallAfter(set_maxsize, self, (-1, -1))
		self.Sizer.SetMinSize((minsize[0], minsize[1]))
		if hasattr(self, "MinClientSize"):
			# wxPython >= 2.9
			self.MinClientSize = minsize
		else:
			# wxPython < 2.9
			self.SetMinSize(self.ClientToWindowSize(minsize))
	
	def set_child_ctrls_as_attrs(self, parent=None):
		"""
		Set child controls and labels as attributes of the frame.
		
		Will also set a maximum font size of 11 pt.
		parent is the window over which children will be iterated and
		defaults to self.
		
		"""
		if not parent:
			parent = self
		scale = getcfg("app.dpi") / get_default_dpi()
		for child in parent.GetAllChildren():
			if debug:
				safe_print(child.__class__, child.Name)
			if isinstance(child, (wx.StaticText, wx.Control, 
								  filebrowse.FileBrowseButton,
								  floatspin.FloatSpin)):
				if (isinstance(child, wx.Choice) and
					sys.platform not in ("darwin", "win32") and
					child.MinSize[1] == -1):
					# wx.Choice can have varying height based on GTK theme if
					# no initial items. Simply always set min height to that
					# of a wx.Choice with items.
					if not hasattr(BaseFrame, "_choiceheight"):
						choice = wx.Choice(self, choices=[u"|"])
						BaseFrame._choiceheight = choice.Size[1]
						choice.Destroy()
					child.MinSize = child.MinSize[0], BaseFrame._choiceheight
				elif isinstance(child, wx.BitmapButton):
					if "gtk3" in wx.PlatformInfo:
						# GTK3 doesn't respect NO_BORDER in hovered state when
						# using wx.BitmapButton and looks ugly
						orig_child = child
						child = GenBitmapButton(child.Parent, -1, child.BitmapLabel,
												child.Position, child.MinSize,
												child.WindowStyle, child.Validator or
																   wx.DefaultValidator,
												child.Name)
						child.BackgroundColour = orig_child.Parent.BackgroundColour
						child.Enabled = orig_child.Enabled
						# wxPython Classic / Phoenix
						for tt_attr in ("ToolTipString", "ToolTip"):
							if hasattr(orig_child, tt_attr):
								break
						orig_tt = getattr(orig_child, tt_attr, None)
						if orig_tt:
							if isinstance(orig_tt, wx.ToolTip):
								orig_tt = orig_tt.Tip
							setattr(child, tt_attr, orig_tt)
						orig_child.ContainingSizer.Replace(orig_child, child)
						orig_child.Destroy()
					else:
						set_bitmap_labels(child)
				elif (isinstance(child, wx._SpinCtrl) and
					  not hasattr(child, "_spinwidth") and
					  "gtk3" in wx.PlatformInfo):
					if not getattr(wx.SpinCtrl, "_spinwidth", 0):
						spin = wx.SpinCtrl(self, -1)
						text = wx.TextCtrl(self, -1)
						wx.SpinCtrl._spinwidth = spin.Size[0] - text.Size[0] + 11
						spin.Destroy()
						text.Destroy()
					child.MinSize = (child.MinSize[0] + wx.SpinCtrl._spinwidth,
									 child.MinSize[1])
				elif (isinstance(child, wx._StaticText) and
					  "gtk3" in wx.PlatformInfo):
					if child.__class__.SetLabel is not wx.StaticText.__dict__['SetLabel']:
						child.__class__.SetLabel = wx.StaticText.__dict__['SetLabel']
					if child.__class__.Label is not wx.StaticText.__dict__['Label']:
						child.__class__.Label = property(child.__class__.GetLabel,
														 child.__class__.SetLabel)
				elif (isinstance(child, wx.ComboBox) and
					  "gtk3" in wx.PlatformInfo):
					# ComboBox is not wide enough to accomodate its text
					# box under GTK3
					child.MinSize = max(child.MinSize[0],
										170 * scale), child.MinSize[1]
				child.SetMaxFontSize(11)
				if sys.platform == "darwin":
					# Work around ComboBox issues on Mac OS X
					# (doesn't receive EVT_KILL_FOCUS)
					if isinstance(child, wx.ComboBox):
						if child.IsEditable():
							if debug:
								safe_print("[D]", child.Name,
										   "binds EVT_TEXT to focus_handler")
							child.Bind(wx.EVT_TEXT, self.focus_handler)
					else:
						child.Bind(wx.EVT_SET_FOCUS, self.focus_handler)
				if not hasattr(self, child.Name):
					setattr(self, child.Name, child)
			if (sys.platform == "win32" and sys.getwindowsversion() >= (6, ) and
				isinstance(child, wx.Panel)):
				# No need to enable double buffering under Linux and Mac OS X.
				# Under Windows, enabling double buffering on the panel seems
				# to work best to reduce flicker.
				child.SetDoubleBuffered(True)
			if (sys.platform != "darwin" and isinstance(child, wx.Panel) and
				child.AcceptsFocus()):
				# Has no children that can accept focus, so there is no reason
				# why this should be able to receive focus, ever.
				# Move focus to the next control.
				child.Bind(wx.EVT_SET_FOCUS,
						   lambda event: event.EventObject.AcceptsFocus() and
										 event.EventObject.Navigate(int(not wx.GetKeyState(wx.WXK_SHIFT))) or
										 event.Skip())

	def getcfg(self, name, fallback=True, raw=False, cfg=config.cfg):
		return getcfg(name, fallback, raw, cfg)

	def hascfg(self, name, fallback=True, cfg=config.cfg):
		return hascfg(name, fallback, cfg)

	def setcfg(self, name, value, cfg=config.cfg):
		setcfg(name, value, cfg)


class BaseInteractiveDialog(wx.Dialog):

	""" Base class for informational and confirmation dialogs """
	
	def __init__(self, parent=None, id=-1, title=appname, msg="", 
				 ok=None, bitmap=None, pos=(-1, -1), size=(400, -1), 
				 show=True, log=True, 
				 style=wx.DEFAULT_DIALOG_STYLE, nowrap=False, wrap=70,
				 name=wx.DialogNameStr, bitmap_margin=None):
		if not ok:
			ok = lang.getstr("ok")
		self._log = log
		self._msg = msg
		if parent:
			pos = list(pos)
			i = 0
			for coord in pos:
				if coord > -1:
					pos[i] += parent.GetScreenPosition()[i]
				i += 1
			pos = tuple(pos)
			if title == appname:
				appid = get_appid_from_window_hierarchy(parent)
				base_appid = appname.lower()
				appid2title = {base_appid + "-3dlut-maker": "3dlut.frame.title",
							   base_appid + "-curve-viewer": "calibration.lut_viewer.title",
							   base_appid + "-profile-info": "profile.info",
							   base_appid + "-scripting-client": "scripting-client",
							   base_appid + "-synthprofile": "synthicc.create",
							   base_appid + "-testchart-editor": "testchart.edit",
							   base_appid + "-vrml-to-x3d-converter": "vrml_to_x3d_converter"}
				title = lang.getstr(appid2title.get(appid, "window.title"))
		scale = getcfg("app.dpi") / get_default_dpi()
		if scale > 1 and size == (400, -1):
			size = size[0] * scale, size[1]
		wx.Dialog.__init__(self, parent, id, title, pos, size, style, name)
		self.taskbar = None
		if sys.platform == "win32":
			bgcolor = self.BackgroundColour
			self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
			if taskbar:
				if parent and parent.IsShownOnScreen():
					taskbarframe = parent
				else:
					taskbarframe = self
				if hasattr(taskbarframe, "taskbar"):
					self.taskbar = taskbarframe.taskbar
				else:
					self.taskbar = taskbar.Taskbar(taskbarframe)
		self.SetPosition(pos)  # yes, this is needed
		
		self.Bind(wx.EVT_SHOW, self.OnShow, self)

		margin = 12

		if bitmap_margin is None:
			bitmap_margin = margin

		self.sizer0 = wx.BoxSizer(wx.VERTICAL)
		self.SetSizer(self.sizer0)
		if bitmap:
			self.sizer1 = wx.FlexGridSizer(1, 2, 0, 0)
		else:
			self.sizer1 = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer3 = wx.FlexGridSizer(0, 1, 0, 0)
		self.sizer0.Add(self.sizer1, flag = wx.ALIGN_LEFT | wx.RIGHT,
						border=margin)
		self.buttonpanel = wx.Panel(self)
		self.buttonpanel.SetSizer(wx.BoxSizer(wx.VERTICAL))
		self.buttonpanel.Sizer.Add(self.sizer2, 1, flag=wx.ALL | wx.EXPAND, 
								   border=margin)
		if sys.platform == "win32":
			self.buttonpanel_line = wx.Panel(self, size=(-1,1))
			self.buttonpanel_line.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT))
			self.sizer0.Add(self.buttonpanel_line, flag=wx.TOP | wx.EXPAND,
							border=margin)
			self.buttonpanel.SetBackgroundColour(bgcolor)
		self.sizer0.Add(self.buttonpanel, flag=wx.EXPAND)

		if bitmap_margin:
			flags = wx.LEFT | wx.TOP
		else:
			flags = 0
		if bitmap:
			self.bitmap = wx.StaticBitmap(self, -1, bitmap)
			self.sizer1.Add(self.bitmap, flag=flags, border=margin)
			if self.taskbar:
				state = None
				if bitmap is geticon(32, "dialog-error"):
					state = taskbar.TBPF_ERROR
				elif bitmap is geticon(32, "dialog-warning"):
					state = taskbar.TBPF_PAUSED
				if state is not None:
					if (state == taskbar.TBPF_ERROR or
						not isinstance(self.Parent, ProgressDialog) or
						self.Parent.paused):
						self.taskbar.set_progress_state(state)
					if not isinstance(self.Parent, ProgressDialog):
						self.taskbar.set_progress_value(self.taskbar.maxv)

		self.sizer1.Add(self.sizer3, flag=wx.ALIGN_LEFT | flags | wx.TOP, border=margin)
		msg = msg.replace("&", "&&")
		self.message = wx.StaticText(self, -1, msg if nowrap else
											   util_str.wrap(msg, wrap))
		if sys.platform == "win32":
			# Prevent text cutoff (wxWidgets bug)
			self.message.MinSize = (self.message.Size[0] + math.ceil(1 * scale),
									self.message.MinSize[1])
		self.sizer3.Add(self.message)

		btnwidth = 80

		self.ok = wx.Button(self.buttonpanel, wx.ID_OK, ok)
		self.sizer2.Add((-1, 1), 1)
		self.sizer2.Add(self.ok, flag=wx.ALIGN_RIGHT)
		self.Bind(wx.EVT_BUTTON, self.OnClose, id=wx.ID_OK)

		self.buttonpanel.Layout()
		self.sizer0.SetSizeHints(self)
		self.sizer0.Layout()
		self.pos = pos
		if show:
			self.ok.SetDefault()
			self.ShowModalThenDestroy()

	def ShowModalThenDestroy(self):
		result = self.ShowModal()
		self.Destroy()
		return result

	def OnShow(self, event):
		event.Skip()
		if not getattr(event, "IsShown", getattr(event, "GetShow", bool))():
			return
		if self._log:
			safe_print(box(self._msg))
		app = wx.GetApp()
		# Make sure taskbar button flashes under Windows
		topwindow = app.GetTopWindow()
		topwindow_shown = topwindow and topwindow.IsShown()
		if topwindow_shown:
			window = topwindow
		else:
			window = self.Parent or self
		# Only request user attention if toplevel window has a non-empty title
		if ((not app.IsActive() or (self.Parent and not topwindow_shown and
									getattr(topwindow, "Title", ""))) and
			hasattr(window, "RequestUserAttention")):
			window.RequestUserAttention()
			if window != self:
				self.RequestUserAttention()

	def OnClose(self, event):
		if event.GetEventObject() == self:
			id = wx.ID_CANCEL
		else:
			id = event.GetId()
		if self._log and isinstance(self, ConfirmDialog):
			if hasattr(self, "FindWindow"):
				# wxPython 4
				ctrl = self.FindWindow(id)
			else:
				# wxPython 3
				ctrl = self.FindWindowById(id)
			if ctrl:
				safe_print("->", ctrl.Label)
		self.EndModal(id)

	def EndModal(self, id):
		# Re-enable other windows
		if hasattr(self, "_disabler"):
			del self._disabler
		if self.IsModal():
			wx.Dialog.EndModal(self, id)
		else:
			# Process wx.WindowModalDialogEvent
			event = wx.WindowModalDialogEvent(wx.wxEVT_WINDOW_MODAL_DIALOG_CLOSED,
											  self.Id)
			event.SetInt(id)
			self.ProcessEvent(event)
			self.Hide()

	def Show(self, show=True):
		if show:
			self.set_position()
		return wx.Dialog.Show(self, show)

	def ShowModal(self):
		self.set_position()
		result = wx.Dialog.ShowModal(self)
		if self:
			self.update_taskbar()
		return result

	def update_taskbar(self):
		if self and self.taskbar:
			state = None
			if (isinstance(self.Parent, ProgressDialog) and
				self.Parent.timer.IsRunning()):
				if self.Parent.indeterminate:
					state = taskbar.TBPF_INDETERMINATE
				elif not self.Parent.paused:
					state = taskbar.TBPF_NORMAL
			else:
				state = taskbar.TBPF_NOPROGRESS
			if state is not None:
				self.taskbar.set_progress_state(state)

	def ShowWindowModal(self):
		self.set_position()
		self._disabler = BetterWindowDisabler(self, self.Parent)
		if hasattr(wx.Dialog, "ShowWindowModal") and sys.platform == "darwin":
			# wx 2.9+
			wx.Dialog.ShowWindowModal(self)
		else:
			self.Show()

	def ShowWindowModalBlocking(self):
		"""
		Blocking ShoWindowModal implementation.
		
		Normally, ShowWindowModal is only implemented under macOS and doesn't
		block. This version blocks, while still allowing interaction with
		windows other than the parent (i.e. like ShowModal under platforms
		other than macOS).
		
		"""
		result = {"dlg": None, "retcode": wx.ID_CANCEL}
		def OnCloseWindowModalDialog(event):
			result["dlg"] = event.GetDialog()
			result["retcode"] = event.GetReturnCode()
		self.Unbind(wx.EVT_WINDOW_MODAL_DIALOG_CLOSED)
		self.Bind(wx.EVT_WINDOW_MODAL_DIALOG_CLOSED, OnCloseWindowModalDialog)
		self.ShowWindowModal()
		while self and result["dlg"] is not self:
			wx.Yield()
			sleep(1.0 / 60)
		return result["retcode"]

	def set_position(self):
		if self.Parent and self.Parent.IsIconized():
			self.Parent.Restore()
		if not getattr(self, "pos", None) or self.pos == (-1, -1):
			self.Center(wx.BOTH)
		elif self.pos[0] == -1:
			self.Center(wx.HORIZONTAL)
		elif self.pos[1] == -1:
			self.Center(wx.VERTICAL)


if not hasattr(wx, "WindowModalDialogEvent") or sys.platform != "darwin":
	# Fake it or overwrite it, as we can't use GetDialog/GetReturnCode otherwise

	wx.wxEVT_WINDOW_MODAL_DIALOG_CLOSED = wx.NewEventType()
	wx.EVT_WINDOW_MODAL_DIALOG_CLOSED = wx.PyEventBinder(wx.wxEVT_WINDOW_MODAL_DIALOG_CLOSED, 1)

	class WindowModalDialogEvent(wx.PyCommandEvent):

		def __init__(self, commandType=wx.wxEVT_NULL, id=0):
			wx.PyCommandEvent.__init__(self, commandType, id)

		def GetDialog(self):
			return wx.FindWindowById(self.Id)

		def GetReturnCode(self):
			return self.GetInt()

	wx.WindowModalDialogEvent = WindowModalDialogEvent
	

class HtmlInfoDialog(BaseInteractiveDialog):

	def __init__(self, parent=None, id=-1, title=appname, msg="", html="",
				 ok="OK", bitmap=None, pos=(-1, -1), size=(400, -1), 
				 show=True, log=True, bitmap_margin=None):
		BaseInteractiveDialog.__init__(self, parent, id, title, msg, ok, 
									   bitmap, pos, size, False, log,
									   bitmap_margin=bitmap_margin)

		scale = getcfg("app.dpi") / config.get_default_dpi()
		if scale < 1:
			scale = 1
		htmlwnd = HtmlWindow(self, -1, size=(332 * scale, 200 * scale),
							 style=wx.BORDER_THEME)
		htmlwnd.SetPage(html)
		self.sizer3.Add(htmlwnd, 1, flag=wx.TOP | wx.ALIGN_LEFT | wx.EXPAND,
						border=12)
		self.sizer0.SetSizeHints(self)
		self.sizer0.Layout()
		if show:
			self.ok.SetDefault()
			self.ShowModalThenDestroy()


class HtmlWindow(wx.html.HtmlWindow):

	def __init__(self, *args, **kwargs):
		wx.html.HtmlWindow.__init__(self, *args, **kwargs)
		scale = max(getcfg("app.dpi") / config.get_default_dpi(), 1)
		if "gtk3" in wx.PlatformInfo:
			size = int(round(wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT).PointSize * scale))
		else:
			size = -1
		self.SetStandardFonts(size)
		self.Bind(wx.html.EVT_HTML_LINK_CLICKED,
				  lambda event: launch_file(event.GetLinkInfo().Href))

	def SetPage(self, source):
		""" Set displayed page with system default colors """
		html = safe_unicode(source, "UTF-8")
		bgcolor, text, linkcolor, vlinkcolor = get_html_colors()
		if not u"<body" in html:
			html = "<body>%s</body>" % html
		html = re.sub(r"<body[^>]*",
					  u'<body bgcolor="%s" text="%s" link="%s" alink="%s" vlink="%s"' %
					  (bgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
					   text.GetAsString(wx.C2S_HTML_SYNTAX),
					   linkcolor.GetAsString(wx.C2S_HTML_SYNTAX),
					   linkcolor.GetAsString(wx.C2S_HTML_SYNTAX),
					   vlinkcolor.GetAsString(wx.C2S_HTML_SYNTAX)), html)
		wx.html.HtmlWindow.SetPage(self, html)


class BitmapBackgroundBitmapButton(wx.BitmapButton):
	
	""" A BitmapButton that will use its parent bitmap background.
	
	The parent needs to have a GetBitmap() method.
	
	"""

	def __init__(self, *args, **kwargs):
		wx.BitmapButton.__init__(self, *args, **kwargs)
		self.Bind(wx.EVT_PAINT, self.OnPaint)

	def OnPaint(self, dc):
		dc = wx.PaintDC(self)
		try:
			dc = wx.GCDC(dc)
		except Exception, exception:
			pass
		dc.DrawBitmap(self.Parent.GetBitmap(), 0, -self.GetPosition()[1])
		dc.DrawBitmap(self.GetBitmapLabel(), 0, 0)


bitmaps = {}

class BitmapBackgroundPanel(wx.PyPanel):
	
	""" A panel with a background bitmap """

	def __init__(self, *args, **kwargs):
		wx.PyPanel.__init__(self, *args, **kwargs)
		self._bitmap = None
		self.alpha = 1.0
		self.blend = False
		self.borderbtmcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW)
		self.bordertopcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)
		self.drawborderbtm = False
		self.drawbordertop = False
		self.repeat_sub_bitmap_h = None
		self.scalebitmap = (True, False)
		self.scalequality = wx.IMAGE_QUALITY_NORMAL
		if "gtk3" in wx.PlatformInfo:
			# Fix background color not working for panels under GTK3
			self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
		else:
			self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
		self.Bind(wx.EVT_PAINT, self.OnPaint)
		self.Bind(wx.EVT_SIZE, self.OnSize)

	if "gtk3" in wx.PlatformInfo:
		OnEraseBackground = wx_Panel.__dict__["OnEraseBackground"]

	def AcceptsFocus(self):
		return False

	def AcceptsFocusFromKeyboard(self):
		return False
	
	def GetBitmap(self):
		return self._bitmap

	def SetBitmap(self, bitmap):
		self._bitmap = bitmap

	def OnPaint(self, event):
		if sys.platform != "win32":
			# AutoBufferedPaintDCFactory is the magic needed for crisp text
			# rendering in HiDPI mode under OS X and Linux
			cls = wx.AutoBufferedPaintDCFactory
		else:
			cls = wx.BufferedPaintDC
		dc = cls(self)
		self._draw(dc)

	def OnSize(self,event):
		self.Refresh()
		event.Skip()
	
	def _draw(self, dc):
		bgcolor = self.BackgroundColour
		bbr = wx.Brush(bgcolor, wx.SOLID)
		dc.SetBackground(bbr)
		dc.SetBackgroundMode(wx.SOLID)
		dc.Clear()
		dc.SetTextForeground(self.GetForegroundColour())
		bmp = self._bitmap
		if bmp:
			if self.alpha < 1.0 or self.blend:
				key = (id(bmp), bgcolor, self.alpha)
				bmp = bitmaps.get(key)
				if not bmp:
					bmp = self._bitmap
					image = bmp.ConvertToImage()
					if self.alpha < 1.0:
						if not image.HasAlpha():
							image.InitAlpha()
						alphabuffer = image.GetAlphaBuffer()
						for i, byte in enumerate(alphabuffer):
							if byte > "\0":
								alphabuffer[i] = chr(int(round(ord(byte) *
															   self.alpha)))
					if self.blend:
						databuffer = image.GetDataBuffer()
						for i, byte in enumerate(databuffer):
							if byte > "\0":
								databuffer[i] = chr(int(round(ord(byte) *
															  (bgcolor[i % 3] /
															   255.0))))
					bmp = image.ConvertToBitmap()
					bitmaps[key] = bmp
			if True in self.scalebitmap:
				img = bmp.ConvertToImage()
				img.Rescale(self.GetSize()[0] if self.scalebitmap[0]
							else img.GetSize()[0],
							self.GetSize()[1]  if self.scalebitmap[1]
							else img.GetSize()[1], quality=self.scalequality)
				bmp = img.ConvertToBitmap()
			dc.DrawBitmap(bmp, 0, 0)
			if (self.repeat_sub_bitmap_h and self.Size[0] > bmp.Size[0] and
				bmp.Size[0] >= self.repeat_sub_bitmap_h[0] and
				bmp.Size[1] >= self.repeat_sub_bitmap_h[1]):
				sub_bmp = bmp.GetSubBitmap(self.repeat_sub_bitmap_h)
				sub_img = sub_bmp.ConvertToImage()
				sub_img.Rescale(self.GetSize()[0] -
								bmp.GetSize()[0],
								bmp.GetSize()[1] if self.scalebitmap[1]
								else sub_bmp.GetSize()[1],
								quality=self.scalequality)
				dc.DrawBitmap(sub_img.ConvertToBitmap(), bmp.GetSize()[0], 0)
		if self.drawbordertop:
			pen = wx.Pen(self.bordertopcolor, 1, wx.SOLID)
			pen.SetCap(wx.CAP_BUTT)
			dc.SetPen(pen)
			dc.DrawLine(0, 0, self.GetSize()[0], 0)
		if self.drawborderbtm:
			pen = wx.Pen(self.borderbtmcolor, 1, wx.SOLID)
			pen.SetCap(wx.CAP_BUTT)
			dc.SetPen(pen)
			dc.DrawLine(0, self.GetSize()[1] - 1, self.GetSize()[0], self.GetSize()[1] - 1)


class BitmapBackgroundPanelText(BitmapBackgroundPanel):
	
	""" A panel with a background bitmap and text label """

	def __init__(self, *args, **kwargs):
		BitmapBackgroundPanel.__init__(self, *args, **kwargs)
		self.label_x = None
		self.label_y = None
		self.textalpha = 1.0
		self.textshadow = True
		self.textshadowcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)
		self.use_gcdc = False
		self._label = ""
	
	def _set_font(self, dc):
		font = self.GetFont()
		if self.use_gcdc:
			# NOTE: Drawing text to wx.GCDC has problems with unicode chars
			# being replaced with boxes under wxGTK
			try:
				dc = wx.GCDC(dc)
			except Exception, exception:
				pass
		font.SetPointSize(get_dc_font_size(font.GetPointSize(), dc))
		dc.SetFont(font)
		return dc
 	
	def GetLabel(self):
		return self._label

	@Property
	def Label():
		def fget(self):
			return self.GetLabel()

		def fset(self, label):
			self.SetLabel(label)

		return locals()
	
	def SetLabel(self, label):
		self._label = label
	
	def _draw(self, dc):
		BitmapBackgroundPanel._draw(self, dc)
		dc.SetBackgroundMode(wx.TRANSPARENT)
		dc = self._set_font(dc)
		label = self.Label.splitlines()
		for i, line in enumerate(label):
			w1, h1 = self.GetTextExtent(line)
			w2, h2 = dc.GetTextExtent(line)
			if self.label_x is None:
				w = (max(w1, w2) - min(w1, w2)) / 2.0 + min(w1, w2)
				x = self.GetSize()[0] / 2.0 - w / 2.0
			else:
				x = self.label_x
			h = (max(h1, h2) - min(h1, h2)) / 2.0 + min(h1, h2)
			if self.label_y is None:
				y = self.GetSize()[1] / 2.0 - h / 2.0
			else:
				y = self.label_y + h * i
			if self.textshadow:
				dc.SetTextForeground(self.textshadowcolor)
				dc.DrawText(line, x + 1, y + 1)
			color = self.GetForegroundColour()
			if self.textalpha < 1:
				bgcolor = self.BackgroundColour
				bgblend = (1.0 - self.textalpha)
				blend = self.textalpha
				color = wx.Colour(int(round(bgblend * bgcolor.Red() +
											blend * color.Red())),
								  int(round(bgblend * bgcolor.Green() +
											blend * color.Green())),
								  int(round(bgblend * bgcolor.Blue() +
											blend * color.Blue())))
			dc.SetTextForeground(color)
			dc.DrawText(line, x, y)


class ConfirmDialog(BaseInteractiveDialog):

	""" Confirmation dialog with OK and Cancel buttons """

	def __init__(self, parent=None, id=-1, title=appname, msg="", 
				 ok=None, cancel=None, bitmap=None, pos=(-1, -1), 
				 size=(400, -1), alt=None, log=False, 
				 style=wx.DEFAULT_DIALOG_STYLE, nowrap=False, wrap=70,
				 name=wx.DialogNameStr, bitmap_margin=None):
		if not ok:
			ok = lang.getstr("ok")
		if not cancel:
			cancel = lang.getstr("cancel")
		BaseInteractiveDialog.__init__(self, parent, id, title, msg, ok, 
									   bitmap, pos, size, show=False, 
									   log=log, style=style,
									   nowrap=nowrap, wrap=wrap,
									   name=name,
									   bitmap_margin=bitmap_margin)

		self.Bind(wx.EVT_CLOSE, self.OnClose, self)

		margin = 12

		if alt:
			self.alt = wx.Button(self.buttonpanel, -1, alt)
			self.sizer2.Insert(1, (margin, margin))
			self.sizer2.Insert(1, self.alt)
			self.Bind(wx.EVT_BUTTON, self.OnClose, id=self.alt.GetId())

		if ok != cancel:
			self.cancel = wx.Button(self.buttonpanel, wx.ID_CANCEL, cancel)
			self.sizer2.Insert(1, (margin, margin))
			self.sizer2.Insert(1, self.cancel)
			self.Bind(wx.EVT_BUTTON, self.OnClose, id=wx.ID_CANCEL)
		
		self.buttonpanel.Layout()
		self.sizer0.SetSizeHints(self)
		self.sizer0.Layout()


class FileBrowseBitmapButtonWithChoiceHistory(filebrowse.FileBrowseButtonWithHistory):

	def __init__(self, *arguments, **namedarguments):
		self.history = namedarguments.get("history") or []
		if 'history' in namedarguments:
			del namedarguments["history"]

		self.historyCallBack = None
		if callable(self.history):
			self.historyCallBack = self.history
			self.history = []
		name = namedarguments.get('name', 'fileBrowseButtonWithHistory')
		if 'name' in namedarguments:
			del namedarguments['name']
		filebrowse.FileBrowseButton.__init__(self, *arguments, **namedarguments)
		self.SetName(name)
		self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
		if "__WXGTK__" in wx.PlatformInfo and wx.VERSION < (2, 9):
			self.textControl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
			self.browseButton.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

	def OnSetFocus(self, event):
		if event.EventObject is self:
			floatspin.focus_next_keyboard_focusable_control(self)
		event.Skip()

	def OnKeyDown(self, event):
		if event.KeyCode == wx.WXK_TAB:
			floatspin.focus_next_keyboard_focusable_control(event.EventObject)
		else:
			event.Skip()
	
	def Disable(self):
		self.Enable(False)
	
	def Enable(self, enable=True):
		self.textControl.Enable(enable and self.history != [""])
		self.browseButton.Enable(enable)

	def GetName(self, path):
		"""
		Return a name for a path. Return value may be a translated string.
		
		"""
		name = None
		if os.path.splitext(path)[1].lower() in (".icc", ".icm"):
			try:
				profile = ICCP.ICCProfile(path)
			except (IOError, ICCP.ICCProfileInvalidError):
				pass
			else:
				name = profile.getDescription()
		if not name:
			name = os.path.basename(path)
		return lang.getstr(name)

	def GetValue(self):
		"""
		retrieve current value of text control
		"""
		if self.textControl.GetSelection() > -1:
			return self.history[self.textControl.GetSelection()]
		return ""
	
	GetPath = GetValue

	def OnChanged(self, evt):
		self.textControl.SetToolTipString(self.history[self.textControl.GetSelection()])
		if self.callCallback and self.changeCallback:
			self.changeCallback(evt)

	def SetBackgroundColour(self,color):
		wx.Panel.SetBackgroundColour(self,color)

	def SetHistory(self, value=(), selectionIndex=None, control=None):
		"""Set the current history list"""
		if control is None:
			control = self.textControl
		if self.history == value:
			return
		value = list(value)

		if os.getenv("XDG_SESSION_TYPE") == "wayland":
			# When the number of items in a dropdown popup menu exceeds
			# the available display client area height, the popup menu
			# gets shown at weird positions or not at all under Wayland.
			# Work-around this wx bug by truncating the choices. Yuck.

			# Text height multiplier of 1.6 matches default popup menu item
			# height under GNOME
			line_height = math.ceil(control.GetTextExtent(u"69Gg")[1] * 1.6)

			# Find smallest display client area height
			max_height = sys.maxint
			for i in xrange(wx.Display.GetCount()):
				max_height = min(wx.Display(i).ClientArea[3], max_height)

			# Check if combined height of items exceeds available client area.
			# Account for possible current value which is not in list and
			# for GNOME top bar (assume item height for the latter).
			if line_height * len(value) + 2 > max_height:
				max_entries = int(max_height / line_height) - 2
				safe_print("Discarding entries to work around wxGTK Wayland "
						   "dropdown popup menu bug:",
						   ", ".join(value[max_entries:]))
				value = value[:max_entries]

		index = control.GetSelection()
		if self.history and index > -1:
			tempValue = self.history[index]
		else:
			tempValue = ""
		self.history = []
		control.Clear()
		for path in value + [tempValue]:
			if path:
				self.history.append(path)
				control.Append(self.GetName(path))
		self.setupControl(selectionIndex, control)
	
	def SetMaxFontSize(self, pointsize=11):
		self.textControl.SetMaxFontSize(pointsize)

	def SetValue(self, value, callBack=1, clear_on_empty_value=False):
		if not value:
			if clear_on_empty_value and self.history:
				index = self.textControl.GetSelection()
				if index > -1:
					self.history.pop(index)
					self.textControl.Delete(index)
		if not value in self.history:
			self.history.append(value)
			self.textControl.Append(self.GetName(value))
		self.setupControl(self.history.index(value))
		if callBack:
			self.changeCallback(CustomEvent(wx.EVT_CHOICE.typeId, 
											self.textControl))
	
	def SetPath(self, path):
		self.SetValue(path, 0, clear_on_empty_value=True)

	def createBrowseButton(self):
		"""Create the browse-button control"""
		button = GenBitmapButton(self, -1, geticon(16, "document-open"), 
								 style=wx.NO_BORDER)
		if sys.platform == "win32":
			button.BackgroundColour = self.BackgroundColour
		button.SetToolTipString(self.toolTip)
		button.Bind(wx.EVT_BUTTON, self.OnBrowse)
		return button

	def createDialog(self, parent, id, pos, size, style, name=None):
		"""Setup the graphic representation of the dialog"""
		wx.Panel.__init__ (self, parent, id, pos, size, style)
		self.SetMinSize(size) # play nice with sizers
		if sys.platform == "win32":
			self.BackgroundColour = parent.BackgroundColour

		box = wx.BoxSizer(wx.HORIZONTAL)

		self.textControl = self.createTextControl()
		if sys.platform == "darwin" and wx.VERSION > (2, 9):
			# Prevent 1px cut-off at left-hand side
			box.Add((1, 1))
		box.Add(self.textControl, 1, wx.ALIGN_CENTER_VERTICAL | wx.TOP |
									 wx.BOTTOM, 4)

		self.browseButton = self.createBrowseButton()
		box.Add(self.browseButton, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 8)
		if self.history:
			history = self.history
			self.history = []
			self.SetHistory(history)

		self.SetAutoLayout(True)
		self.SetSizer(box)
		self.Layout()

	def createLabel(self):
		return (0, 0)

	def createTextControl(self):
		"""Create the text control"""
		textControl = wx.Choice(self, -1)
		textControl.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
		if self.changeCallback:
			textControl.Bind(wx.EVT_CHOICE, self.OnChanged)
		return textControl
	
	def setupControl(self, selectionIndex=None, control=None):
		if control is None:
			control = self.textControl
		if selectionIndex is None:
			selectionIndex = len(self.history) - 1
		control.SetSelection(selectionIndex)
		toolTip = (self.history[selectionIndex] if selectionIndex > -1 else
				   self.toolTip)
		control.SetToolTipString(toolTip)
		control.Enable(self.browseButton.Enabled and self.history != [""])


class PathDialog(ConfirmDialog):

	def __init__(self, parent, msg=u"", name="pathdialog"):
		ConfirmDialog.__init__(self, parent, msg=msg,
							   ok=lang.getstr("browse"),
							   cancel=lang.getstr("cancel"), name=name)
		self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
		self.ok.Bind(wx.EVT_BUTTON, self.filedialog_handler)

	def OnDestroy(self, event):
		self.filedialog.Destroy()
		event.Skip()

	def __getattr__(self, name):
		return getattr(self.filedialog, name)

	def filedialog_handler(self, event):
		self.EndModal(self.filedialog.ShowModal())


_DirDialog = wx.DirDialog

class DirDialog(PathDialog):

	""" wx.DirDialog cannot be interacted with programmatically after
	ShowModal(), a functionality we need for scripting. """

	def __init__(self, *args, **kwargs):
		PathDialog.__init__(self, *args[0:2], name="dirdialog")
		self.filedialog = _DirDialog(self, *args[1:], **kwargs)


_FileDialog = wx.FileDialog

class FileDialog(PathDialog):

	""" wx.FileDialog cannot be interacted with programmatically after
	ShowModal(), a functionality we need for scripting. """

	def __init__(self, *args, **kwargs):
		PathDialog.__init__(self, *args[0:2], name="filedialog")
		self.filedialog = _FileDialog(self, *args[1:], **kwargs)


class FileDrop(_FileDrop):
	
	def __init__(self, parent, drophandlers=None):
		self.parent = parent
		_FileDrop.__init__(self, drophandlers)
		self.unsupported_handler = self.drop_unsupported_handler

	def drop_unsupported_handler(self):
		"""
		Drag'n'drop handler for unsupported files. 
		
		Shows an error message.
		
		"""
		if (not hasattr(self.parent, "worker") or
			not self.parent.worker.is_working()):
			InfoDialog(self.parent, msg=lang.getstr("error.file_type_unsupported") +
										"\n\n" + "\n".join(self._filenames), 
					   ok=lang.getstr("ok"), 
					   bitmap=geticon(32, "dialog-error"))


class FlatShadedButton(GradientButton):

	__bitmap = None
	_enabled = True

	def __init__(self, parent, id=wx.ID_ANY, bitmap=None, label="",
				 pos=wx.DefaultPosition, size=wx.DefaultSize,
				 style=wx.NO_BORDER, validator=wx.DefaultValidator,
				 name="gradientbutton", bgcolour=None, fgcolour=None):
		self.dpiscale = getcfg("app.dpi") / get_default_dpi()
		GradientButton.__init__(self, parent, id, bitmap, label, pos, size,
								style, validator, name)
		self._bgcolour = bgcolour  # Original bgcolour
		self._fgcolour = fgcolour  # Original fgcolour
		self._setcolours(bgcolour, fgcolour)
		self._set_bitmap_labels(self.__bitmap)
		self.SetFont(adjust_font_size_for_gcdc(self.GetFont()))
		self._update_best_size()

	def _set_bitmap_labels(self, bitmap):
		if bitmap:
			self._bitmapdisabled = get_bitmap_disabled(bitmap)
			img = bitmap.ConvertToImage()
			img = img.AdjustChannels(1.1, 1.1, 1.1)
			bitmap = img.ConvertToBitmap()
		else:
			self._bitmapdisabled = bitmap
		self._bitmaphover = bitmap
		self._bitmapselected = bitmap
		self._bitmapfocus = bitmap
	
	def _setcolours(self, bgcolour=None, fgcolour=None):
		self.BackgroundColour = bgcolour or wx.Colour(0x22, 0x22, 0x22)
		self.ForegroundColour = fgcolour or wx.Colour(0x99, 0x99, 0x99)
	
	def Disable(self):
		self.Enable(False)
	
	def DoGetBestSize(self):
		"""
		Overridden base class virtual. Determines the best size of the
		button based on the label and bezel size.
		"""

		if not getattr(self, "_lastBestSize", None):
			dummy = u"FfGgJjPpYy"
			label = self.GetLabel() or dummy
			
			dc = wx.MemoryDC(wx.EmptyBitmap(1, 1))
			try:
				dc = wx.GCDC(dc)
			except:
				pass
			dc.SetFont(self.GetFont())
			retWidth, retHeight = dc.GetTextExtent(label)
			if label == dummy:
				retWidth = 0
			elif wx.VERSION < (2, 9):
				retWidth += 5
			
			bmpWidth = bmpHeight = 0
			if self._bitmap:
				if label != dummy:
					constant = 10
					if wx.VERSION < (2, 9):
						retWidth += 5
				else:
					constant = 0
				# Pin the bitmap height to 10
				bmpWidth, bmpHeight = self._bitmap.GetWidth()+constant * self.dpiscale, 10 * self.dpiscale
				retWidth += bmpWidth
				retHeight = max(bmpHeight, retHeight)

			self._lastBestSize = wx.Size(retWidth + 25 * self.dpiscale, retHeight + 15 * self.dpiscale)
		return self._lastBestSize

	def OnGainFocus(self, event):
		"""
		Handles the ``wx.EVT_SET_FOCUS`` event for L{GradientButton}.

		:param `event`: a `wx.FocusEvent` event to be processed.
		"""
		
		self._hasFocus = True
		self._mouseAction = HOVER
		self.Refresh()
		self.Update()

	def OnLoseFocus(self, event):
		"""
		Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{GradientButton}.

		:param `event`: a `wx.MouseEvent` event to be processed.
		"""

		self._hasFocus = False
		pos = wx.GetMousePosition()
		if (self.IsShownOnScreen() and
			not self.ClientRect.Contains(self.ScreenToClient(pos))):
			# Note: ScreenToClient will only work if top level parent is shown
			self._mouseAction = None
		self.Refresh()
		event.Skip()

	def OnPaint(self, event):
		"""
		Handles the ``wx.EVT_PAINT`` event for L{GradientButton}.

		:param `event`: a `wx.PaintEvent` event to be processed.
		"""

		if sys.platform != "win32":
			# AutoBufferedPaintDCFactory is the magic needed for crisp text
			# rendering in HiDPI mode under OS X and Linux
			cls = wx.AutoBufferedPaintDCFactory
		else:
			cls = wx.BufferedPaintDC
		dc = cls(self)
		gc = wx.GraphicsContext.Create(dc)
		dc.SetBackground(wx.Brush(self.Parent.BackgroundColour))        
		dc.Clear()
		
		clientRect = self.GetClientRect()
		capture = wx.Window.GetCapture()

		x, y, width, height = clientRect     

		fgcolour = self.ForegroundColour
		
		if capture != self:

			if self._mouseAction == HOVER or self._hasFocus:
				fill = self.LightColour(self.BackgroundColour, 3)
				fgcolour = self.LightColour(fgcolour, 15)
			else:
				fill = self.BackgroundColour
			
			shadowOffset = 0
		else:
			fill = self.LightColour(self.BackgroundColour, 3)

			shadowOffset = 1

		gc.SetPen(wx.TRANSPARENT_PEN)
		gc.SetBrush(wx.Brush(fill))
		gc.DrawRoundedRectangle(x, y, width, height, 8 * self.dpiscale)

		if self._enabled:
			if capture != self:
				if self._mouseAction == HOVER:
					bitmap = self._bitmaphover
				else:
					bitmap = self._bitmap
			else:
				bitmap = self._bitmapselected
		else:
			bitmap = self._bitmapdisabled

		font = gc.CreateFont(self.GetFont(), fgcolour)
		gc.SetFont(font)
		label = self.GetLabel()
		if label and bitmap:
			label = " " + label
		# Note: Using self.GetTextExtent instead of gc.GetTextExtent seems
		# to fix sporadic segfaults with wxPython Phoenix up to 4.0.0a2
		# under Windows (fixed in 4.0.0a3), but self.GetTextExtent is NOT
		# an equivalent replacement for gc.GetTextExtent.
		tw, th = gc.GetTextExtent(label)

		if bitmap:
			bw, bh = bitmap.GetWidth(), bitmap.GetHeight()
			if tw:
				tw += 5
				if wx.VERSION < (2, 9):
					tw += 5
		else:
			bw = bh = 0
			
		pos_x = (width-bw-tw)/2+shadowOffset      # adjust for bitmap and text to centre        
		if bitmap:
			pos_y =  (height-bh)/2+shadowOffset
			gc.DrawBitmap(bitmap, pos_x, pos_y, bw, bh) # draw bitmap if available
			pos_x = pos_x + 5   # extra spacing from bitmap

		gc.DrawText(label, pos_x + bw + shadowOffset, (height-th)/2-.5+shadowOffset) 

	BitmapLabel = property(lambda self: self._bitmap,
						   lambda self, bitmap: self.SetBitmap(bitmap))
	
	def Enable(self, enable=True):
		if enable:
			# Restore original colors
			self._setcolours(self._bgcolour, self._fgcolour)
		else:
			# Dim
			self._setcolours(wx.Colour(0x40, 0x40, 0x40),
							 wx.Colour(0x66, 0x66, 0x66))
		GradientButton.Enable(self, enable)
		self._enabled = enable

	@Property
	def _bitmap():
		def fget(self):
			return self.__bitmap

		def fset(self, bitmap):
			if bitmap is not self.__bitmap:
				self.__bitmap = bitmap
				self._set_bitmap_labels(bitmap)

		return locals()

	def SetBitmap(self, bitmap):
		self._bitmap = bitmap
		self._update_best_size()
		self.Refresh()

	def SetBitmapDisabled(self, bitmap):
		self._bitmapdisabled = bitmap
		self._update_best_size()
		self.Refresh()

	def SetBitmapFocus(self, bitmap):
		self._bitmapfocus = bitmap
		self._update_best_size()
		self.Refresh()

	def SetBitmapHover(self, bitmap):
		self._bitmaphover = bitmap
		self._update_best_size()
		self.Refresh()

	def SetBitmapSelected(self, bitmap):
		self._bitmapselected = bitmap
		self._update_best_size()
		self.Refresh()

	def SetLabel(self, label):
		wx.PyControl.SetLabel(self, label)
		self._update_best_size()

	def _update_best_size(self):
		if hasattr(self, "_lastBestSize"):
			del self._lastBestSize
			self.MinSize = self.DoGetBestSize()

	Label = property(wx.PyControl.GetLabel, SetLabel)


class BorderGradientButton(GradientButton):

	def __init__(self, parent, id=wx.ID_ANY, bitmap=None, label="",
				 pos=wx.DefaultPosition, size=wx.DefaultSize,
				 style=wx.NO_BORDER, validator=wx.DefaultValidator,
				 name="gradientbutton"):
		self.dpiscale = getcfg("app.dpi") / get_default_dpi()
		self.use_sierra_style = sys.platform == "darwin"
		GradientButton.__init__(self, parent, id, bitmap, label, pos, size,
								style, validator, name)
		self.SetFont(adjust_font_size_for_gcdc(self.GetFont()))
		self._bitmapdisabled = self._bitmap
		self._bitmapfocus = self._bitmap
		self._bitmaphover = self._bitmap
		self._bitmapselected = self._bitmap
		set_bitmap_labels(self, focus=False)
		if self.use_sierra_style:
			# Use Sierra-like color scheme
			sel = self._bitmap.ConvertToImage()
			sel.Invert()
			self.SetBitmapFocus(sel.ConvertToBitmap())
			sel = sel.AdjustChannels(1, 1, 1, .8)
			sel = sel.ConvertToBitmap()
			self.SetBitmapSelected(sel)
		self._enabled = True

	BitmapLabel = property(lambda self: self._bitmap,
						   lambda self, bitmap: self.SetBitmap(bitmap))

	def DarkColour(self, colour, percent):
		"""
		Return dark contrast of `colour`. The colour returned is from the scale of
		`colour` ==> white.

		:param `colour`: the input colour to be brightened;
		:param `percent`: determines how dark the colour will be. `percent` = 100
		 returns black, `percent` = 0 returns `colour`.
		"""

		rd = colour.Red()
		gd = colour.Green()
		bd = colour.Blue()
		high = 100

		# We take the percent way of the colour from colour -. black
		i = percent
		r = ((i*rd*100)/high)/100
		g = ((i*gd*100)/high)/100
		b = ((i*bd*100)/high)/100

		return wx.Colour(r, g, b)
	
	def Disable(self):
		self.Enable(False)

	def DoGetBestSize(self):
		"""
		Overridden base class virtual. Determines the best size of the
		button based on the label and bezel size.
		"""

		label = self.GetLabel()
		if not label:
			return wx.Size(112, 48)
		
		dc = wx.MemoryDC(wx.EmptyBitmap(1, 1))
		try:
			dc = wx.GCDC(dc)
		except:
			pass
		dc.SetFont(self.GetFont())
		retWidth, retHeight = dc.GetTextExtent(label)
		
		bmpWidth = bmpHeight = 0
		constant = 15 * self.dpiscale
		if self._bitmap:
			bmpWidth, bmpHeight = self._bitmap.GetWidth()+20 * self.dpiscale, self._bitmap.GetHeight()
			retWidth += bmpWidth
			retHeight = max(bmpHeight, retHeight)

		return wx.Size(retWidth+constant, retHeight+constant) 
	
	def Enable(self, enable=True):
		self._enabled = enable
		GradientButton.Enable(self, enable)
		self.Update()

	def IsEnabled(self):
		return self._enabled

	Enabled = property(IsEnabled, Enable)

	def OnPaint(self, event):
		"""
		Handles the ``wx.EVT_PAINT`` event for L{GradientButton}.

		:param `event`: a `wx.PaintEvent` event to be processed.
		"""

		if sys.platform != "win32":
			# AutoBufferedPaintDCFactory is the magic needed for crisp text
			# rendering in HiDPI mode under OS X and Linux
			cls = wx.AutoBufferedPaintDCFactory
		else:
			cls = wx.BufferedPaintDC
		dc = cls(self)
		gc = wx.GraphicsContext.Create(dc)
		dc.SetBackground(wx.Brush(self.GetParent().GetBackgroundColour()))        
		dc.Clear()
		
		clientRect = self.GetClientRect()
		gradientRect = wx.Rect(*clientRect)
		capture = wx.Window.GetCapture()

		x, y, width, height = clientRect        
		
		gradientRect.SetHeight(gradientRect.GetHeight()/2 + ((capture==self and [1] or [0])[0]))

		fgcolor = self.ForegroundColour

		# Determine fill color based on state and platform
		if self.use_sierra_style:
			# Use Sierra-like color scheme
			if capture == self and self._mouseAction in (CLICK, HOVER):
				# Pressed/selected state
				topStart = wx.Colour(*gamma_encode(74, 150, 253))
				topEnd = wx.Colour(*gamma_encode(8, 103, 221))
				fgcolor = wx.Colour(255, 255, 255, 204)
			elif capture != self and self._hasFocus:
				# Focus state
				topStart = wx.Colour(*gamma_encode(105, 177, 250))
				topEnd = wx.Colour(*gamma_encode(12, 128, 255))
				fgcolor = wx.WHITE
			else:
				# Normal state
				topStart = wx.WHITE
				topEnd = wx.WHITE
		elif capture != self:
			# Normal, hover or focus state
			if self._mouseAction == HOVER:
				# Hover state
				topStart = self._pressedTopColour
				topEnd = self._pressedBottomColour
				topStart = wx.Colour(topStart.red, topStart.green, topStart.blue, 25)
				topEnd = wx.Colour(topEnd.red, topEnd.green, topEnd.blue, 25)
			else:
				# Normal or focus state
				topStart, topEnd = self._topStartColour, self._bottomEndColour
		else:
			# Pressed/selected state
			topStart = self._pressedTopColour
			topEnd = self._pressedBottomColour
			topStart = wx.Colour(topStart.red, topStart.green, topStart.blue, 51)
			topEnd = wx.Colour(topEnd.red, topEnd.green, topEnd.blue, 51)

		# Determine border color and width based on state and platform
		borderwidth = 1
		if self.use_sierra_style:
			# Use Sierra-like color scheme
			if capture == self and self._mouseAction in (CLICK, HOVER):
				# Pressed/selected state
				bordercolor_top = wx.Colour(*gamma_encode(35, 125, 254))
				bordercolor = wx.Colour(*gamma_encode(2, 63, 221))
			elif capture != self and self._hasFocus:
				# Focus state
				bordercolor_top = wx.Colour(*gamma_encode(74, 160, 249))
				bordercolor = wx.Colour(*gamma_encode(4, 95, 255))
			else:
				# Normal state
				##bordercolor_top = wx.Colour(*gamma_encode(200, 200, 200))
				##bordercolor = wx.Colour(*gamma_encode(172, 172, 172))
				if sys.platform == "darwin":
					bordercolor_top = wx.Colour(*gamma_encode(0, 0, 0, 55))
				else:
					bordercolor_top = wx.Colour(*gamma_encode(0, 0, 0, 11))
				bordercolor = wx.Colour(*gamma_encode(0, 0, 0, 83))
		else:
			# Normal, hover, focus or pressed/selected state
			if self._mouseAction == HOVER or self._hasFocus:
				# Hover, focus or pressed/selected state
				bordercolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
				if (sys.platform == "win32" and capture != self and
					self._mouseAction != HOVER):
					# Focus state
					borderwidth = 2
			else:
				# Normal state
				bordercolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW)
			if capture == self:
				# Pressed/selected state
				bordercolor = self.DarkColour(bordercolor, 71)

		if not self.IsEnabled():
			fgcolor = wx.Colour(fgcolor.red, fgcolor.green, fgcolor.blue, 102)
			bordercolor.Set(bordercolor.red, bordercolor.green,
							bordercolor.blue,
							int(round(bordercolor.alpha * 153 / 255.0)))

		brush = gc.CreateLinearGradientBrush(0, 1, 0, height - 1,
											 topStart, topEnd)
		gc.SetBrush(brush)

		if self.use_sierra_style:
			# Use Sierra-like color scheme
			##shadowcolor = wx.Colour(*gamma_encode(228, 228, 228))
			if sys.platform == "darwin":
				shadowcolor = wx.Colour(*gamma_encode(0, 0, 0, 8))
			else:
				shadowcolor = wx.Colour(*gamma_encode(0, 0, 0, 19))

			# Draw Sierra-like shadow
			gc.SetPen(wx.Pen(shadowcolor))
			gc.DrawRoundedRectangle(borderwidth, borderwidth,
									width - 2 - borderwidth,
									height - 1 - borderwidth,
									(height - 1 - borderwidth) / 2.0)

			# Draw borders
			gc.SetPen(wx.Pen(bordercolor))
			gc.DrawRoundedRectangle(borderwidth, borderwidth,
									width - 2 - borderwidth,
									height - 2 - borderwidth,
									(height - 2 - borderwidth) / 2.0)
			gc.SetPen(wx.Pen(bordercolor_top))
			gc.DrawRoundedRectangle(borderwidth, borderwidth,
									width - 2 - borderwidth,
									height - 2 - borderwidth * 2,
									(height - 2 - borderwidth * 2) / 2.0)

			# Draw fill
			gc.SetPen(wx.TRANSPARENT_PEN)
			gc.DrawRoundedRectangle(borderwidth * 2, borderwidth * 2,
									width - 2 - borderwidth * 2,
									height - 2 - borderwidth * 2,
									(height - 2 - borderwidth * 2) / 2.0)
		else:
			# Draw border and fill
			gc.SetPen(wx.Pen(bordercolor, borderwidth))
			gc.DrawRoundedRectangle(borderwidth, borderwidth,
									width - 2 - borderwidth,
									height - 2 - borderwidth,
									(height - 2 - borderwidth) / 2.0)

		shadowOffset = 0

		font = gc.CreateFont(self.GetFont(), fgcolor)
		gc.SetFont(font)
		label = self.GetLabel()
		if label and self._bitmap:
			label = " " + label
		# Note: Using self.GetTextExtent instead of gc.GetTextExtent seems
		# to fix sporadic segfaults with wxPython Phoenix up to 4.0.0a2
		# under Windows (fixed in 4.0.0a3), but self.GetTextExtent is NOT
		# an equivalent replacement for gc.GetTextExtent.
		tw, th = gc.GetTextExtent(label)

		if self._bitmap:
			bw, bh = self._bitmap.GetWidth(), self._bitmap.GetHeight()
			if tw:
				tw += 5
				if wx.VERSION < (2, 9):
					tw += 5
		else:
			bw = bh = 0
			
		pos_x = (width-bw-tw)/2+shadowOffset      # adjust for bitmap and text to centre        
		if self.IsEnabled():
			if capture == self and self._mouseAction in (CLICK, HOVER):
				bitmap = self._bitmapselected
			elif capture != self and self._hasFocus:
				bitmap = self._bitmapfocus
			elif self._mouseAction == HOVER and not get_dialogs(True):
				bitmap = self._bitmaphover
			else:
				bitmap = self._bitmap
		else:
			bitmap = self._bitmapdisabled
		if bitmap:
			pos_y =  (height-bh)/2+shadowOffset
			gc.DrawBitmap(bitmap, pos_x, pos_y, bw, bh) # draw bitmap if available
			pos_x = pos_x + 4   # extra spacing from bitmap

		gc.DrawText(label, pos_x + bw + shadowOffset, (height-th)/2.0-1+shadowOffset) 

	def SetBitmap(self, bitmap):
		self._bitmap = bitmap

	def SetBitmapDisabled(self, bitmap):
		self._bitmapdisabled = bitmap

	def SetBitmapFocus(self, bitmap):
		self._bitmapfocus = bitmap

	def SetBitmapHover(self, bitmap):
		self._bitmaphover = bitmap

	def SetBitmapSelected(self, bitmap):
		self._bitmapselected = bitmap

	def Update(self):
		pass


class CustomGrid(wx.grid.Grid):

	def __init__(self, *args, **kwargs):
		wx.grid.Grid.__init__(self, *args, **kwargs)
		self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
		self.Bind(wx.EVT_SIZE, self.OnResize)
		self.Bind(wx.grid.EVT_GRID_ROW_SIZE, self.OnResize)
		self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.OnResize)
		self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnCellLeftClick)
		self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.OnCellLeftClick)
		self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
		self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnCellSelect)
		self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClick)
		self.GetGridCornerLabelWindow().Bind(wx.EVT_PAINT, self.OnPaintCornerLabel)
		self.GetGridColLabelWindow().Bind(wx.EVT_PAINT, self.OnPaintColLabels)
		self.GetGridRowLabelWindow().Bind(wx.EVT_PAINT, self.OnPaintRowLabels)
		self.SetDefaultCellAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
		self.SetDefaultCellBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
		self.SetDefaultEditor(CustomCellEditor(self))
		self._default_cell_renderer = CustomCellRenderer()
		self.SetDefaultRenderer(self._default_cell_renderer)
		self.SetRowLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)

		self.headerbitmap = None
		if sys.platform == "darwin" or "gtk3" in wx.PlatformInfo:
			# wxMac draws a too short native header
			self.rendernative = False
			self.style = "Mavericks"
		else:
			self.rendernative = True
			self.style = ""
		self._default_col_label_renderer = CustomColLabelRenderer()
		self._default_row_label_renderer = CustomRowLabelRenderer()
		self._col_label_renderers = {}
		self._overwrite_cell_values = True
		self._row_label_renderers = {}
		self.alternate_cell_background_color = True
		self.alternate_col_label_background_color = False
		self.alternate_row_label_background_color = True
		self.draw_col_labels = True
		self.draw_row_labels = True
		self.draw_horizontal_grid_lines = True
		self.draw_vertical_grid_lines = True
		self.selection_alpha = 1.0
		self.show_cursor_outline = True

	if not hasattr(wx.grid.Grid, "CalcCellsExposed"):
		# wxPython < 2.8.10
		def CalcCellsExposed(self, region):
			rows = self.CalcRowLabelsExposed(region)
			cols = self.CalcColLabelsExposed(region)
			cells = []
			for row in rows:
				for col in cols:
					if row > -1 and col > -1:
						cells.append((row, col))
			return cells

	if not hasattr(wx.grid.Grid, "CalcColLabelsExposed"):
		# wxPython < 2.8.10
		def CalcColLabelsExposed(self, region):
			x, y = self.CalcUnscrolledPosition((0,0))
			ri = wx.RegionIterator(region)
			cols = []
			while ri:
				rect = ri.GetRect()
				rect.Offset((x,0))
				colPos = self.GetColPos(self.XToCol(rect.left))
				while colPos < self.GetNumberCols() and colPos >= 0:
					col = self.GetColAt(colPos)
					cl, cr = self.GetColLeftRight(col)
					if cr < rect.left:
						continue
					if cl > rect.right:
						break
					cols.append(col)
					colPos += 1
				ri.Next()
			return cols

	if not hasattr(wx.grid.Grid, "CalcRowLabelsExposed"):
		# wxPython < 2.8.10
		def CalcRowLabelsExposed(self, region):
			x, y = self.CalcUnscrolledPosition((0,0))
			ri = wx.RegionIterator(region)
			rows = []
			while ri:
				rect = ri.GetRect()
				rect.Offset((0,y))
				row = self.YToRow(rect.top)
				while row < self.GetNumberRows() and row >= 0:
					rt, rb = self.GetRowTopBottom(row)
					if rb < rect.top:
						continue
					if rt > rect.bottom:
						break
					rows.append(row)
					row += 1
				ri.Next()
			return rows

	def GetColLeftRight(self, col, c=0):
		left = 0
		while c < col:
			left += self.GetColSize(c)
			c += 1
		right = left + self.GetColSize(col) - 1
		return left, right

	def GetRowTopBottom(self, row, r=0):
		top = 0
		while r < row:
			top += self.GetRowSize(r)
			r += 1
		bottom = top + self.GetRowSize(row) - 1
		return top, bottom

	def OnCellLeftClick(self, event):
		if (event.CmdDown() or event.ControlDown() or event.ShiftDown() or
			not isinstance(self.GetCellEditor(self.GetGridCursorRow(),
											   self.GetGridCursorCol()),
							CustomCellEditor)):
			event.Skip()
		else:
			self.SetGridCursor(event.Row, event.Col)

	def OnCellSelect(self, event):
		row, col = event.GetRow(), event.GetCol()
		self._anchor_row = row
		self._overwrite_cell_values = True
		self.SelectBlock(event.Row, event.Col, event.Row, event.Col)
		self.Refresh()
		event.Skip()

	def OnKeyDown(self, event):
		keycode = event.KeyCode
		if event.CmdDown() or event.ControlDown():
			# CTRL (Linux/Mac/Windows) / CMD (Mac)
			if keycode == 65:
				# A
				self.SelectAll()
				return
			elif keycode in (67, 88):
				# C / X
				clip = []
				cells = self.GetSelection()
				i = -1
				start_col = self.GetNumberCols()
				for cell in cells:
					row = cell[0]
					col = cell[1]
					if i < row:
						clip.append([])
						i = row
						offset = 0
					# Skip cols without label
					if self.GetColLabelSize() and not self.GetColLabelValue(col):
						offset += 1
						continue
					if col - offset < start_col:
						start_col = col - offset
					while len(clip[-1]) - 1 < col - offset:
						clip[-1].append("")
					clip[-1][col - offset] = self.GetCellValue(row, col)
				for i, row in enumerate(clip):
					clip[i] = "\t".join(row[start_col:])
				clipdata = wx.TextDataObject()
				clipdata.SetText("\n".join(clip))
				wx.TheClipboard.Open()
				wx.TheClipboard.SetData(clipdata)
				wx.TheClipboard.Close()
				return
			elif keycode == 86 and self.IsEditable():
				# V
				do = wx.TextDataObject()
				wx.TheClipboard.Open()
				success = wx.TheClipboard.GetData(do)
				wx.TheClipboard.Close()
				if success:
					lines = do.GetText().splitlines()
					for i, line in enumerate(lines):
						lines[i] = re.sub(" +", "\t", line).split("\t")
					# Translate from selected cells into a grid with None values
					# for not selected cells
					grid = []
					cells = self.GetSelection()
					i = -1
					start_col = self.GetNumberCols()
					for cell in cells:
						row = cell[0]
						col = cell[1]
						if i < row:
							grid.append([])
							i = row
							offset = 0
						# Skip read-only cells and cols without label
						if (self.IsReadOnly(row, col) or
							(self.GetColLabelSize() and
							 not self.GetColLabelValue(col))):
							offset += 1
							continue
						if col - offset < start_col:
							start_col = col - offset
						while len(grid[-1]) - 1 < col - offset:
							grid[-1].append(None)
						grid[-1][col - offset] = cell
					for i, row in enumerate(grid):
						grid[i] = row[start_col:]
					# 'Paste' values from clipboard
					self.BeginBatch()
					for i, row in enumerate(grid):
						for j, cell in enumerate(row):
							if (cell is not None and len(lines) > i and
								len(lines[i]) > j):
								self.SetCellValue(cell[0], cell[1], lines[i][j])
								self.ProcessEvent(wx.grid.GridEvent(-1,
																	wx.grid.EVT_GRID_CELL_CHANGE.evtType[0],
																	self,
																	cell[0],
																	cell[1]))
					self.EndBatch()
				return
		elif self.IsEditable() and not self.IsCurrentCellReadOnly():
			if isinstance(self.GetCellEditor(self.GetGridCursorRow(),
											 self.GetGridCursorCol()),
						  CustomCellEditor):
				ch = None
				if keycode in [wx.WXK_NUMPAD0, wx.WXK_NUMPAD1, wx.WXK_NUMPAD2,
							   wx.WXK_NUMPAD3, wx.WXK_NUMPAD4, wx.WXK_NUMPAD5,
							   wx.WXK_NUMPAD6, wx.WXK_NUMPAD7, wx.WXK_NUMPAD8,
							   wx.WXK_NUMPAD9]:
					ch = chr(ord('0') + keycode - wx.WXK_NUMPAD0)
				elif keycode in (wx.WXK_NUMPAD_DECIMAL,
								 wx.WXK_NUMPAD_SEPARATOR):
					ch = "."
				elif keycode < 256 and keycode >= 32:
					ch = safe_unicode(chr(keycode))
				if ch is not None or keycode in (wx.WXK_BACK, wx.WXK_DELETE):
					changed = 0
					batch = False
					for i, (row, col) in enumerate(self.GetSelection()):
						if row > -1 and col > -1 and not self.IsReadOnly(row, col):
							if changed and not batch:
								self.BeginBatch()
								batch = True
							if (self._overwrite_cell_values or
								keycode == wx.WXK_DELETE):
								value = ""
							else:
								value = self.GetCellValue(row, col)
							if keycode == wx.WXK_BACK:
								value = value[:-1]
							elif keycode != wx.WXK_DELETE:
								value += ch
							self.SetCellValue(row, col, value)
							self.ProcessEvent(wx.grid.GridEvent(-1,
																wx.grid.EVT_GRID_CELL_CHANGE.evtType[0],
																self,
																row,
																col))
							changed += 1
					if batch:
						self.EndBatch()
					self._overwrite_cell_values = False
					if not changed:
						wx.Bell()
					return
			elif (event.KeyCode == wx.WXK_RETURN or
				  event.KeyCode == wx.WXK_NUMPAD_ENTER):
				self.EnableCellEditControl()
				return
		event.Skip()

	def OnKillFocus(self, event):
		self.Refresh()
		event.Skip()

	def OnLabelLeftClick(self, event):
		row, col = event.GetRow(), event.GetCol()
		if row == -1 and col > -1:
			# Col label clicked
			self.SetFocus()
			self.SetGridCursor(max(self.GetGridCursorRow(), 0), col)
			self.MakeCellVisible(max(self.GetGridCursorRow(), 0), col)
		elif col == -1 and row > -1:
			# Row label clicked
			if self.select_row(row, event.ShiftDown(),
							   event.ControlDown() or event.CmdDown()):
				return
		event.Skip()

	def OnPaintColLabels(self, evt):
		window = evt.GetEventObject()
		dc = wx.PaintDC(window)

		cols = self.CalcColLabelsExposed(window.GetUpdateRegion())
		if -1 in cols:
			return

		x, y = self.CalcUnscrolledPosition((0,0))
		pt = dc.GetDeviceOrigin()
		dc.SetDeviceOrigin(pt.x-x, pt.y)
		leftoffset = 0
		for i, col in enumerate(cols):
			left, right = self.GetColLeftRight(col, cols[0] if i > 0 else 0)
			if i > 0:
				left += leftoffset
				right += leftoffset
			else:
				leftoffset = left
			rect = wx.Rect()
			rect.left = left
			rect.right = right
			rect.y = 0
			rect.height = self.GetColLabelSize()

			renderer = self._col_label_renderers.get(col,
													 self._default_col_label_renderer)

			renderer.Draw(self, dc, rect, col)

	def OnPaintCornerLabel(self, evt):
		window = evt.GetEventObject()
		dc = wx.PaintDC(window)

		rect = wx.Rect()
		rect.width = self.GetRowLabelSize()
		rect.height = self.GetColLabelSize()
		rect.x = 0
		rect.y = 0
		self._default_col_label_renderer.Draw(self, dc, rect)

	def OnPaintRowLabels(self, evt):
		window = evt.GetEventObject()
		dc = wx.PaintDC(window)

		rows = self.CalcRowLabelsExposed(window.GetUpdateRegion())
		if -1 in rows:
			return

		x, y = self.CalcUnscrolledPosition((0,0))
		pt = dc.GetDeviceOrigin()
		dc.SetDeviceOrigin(pt.x, pt.y-y)
		topoffset = 0
		for i, row in enumerate(rows):
			top, bottom = self.GetRowTopBottom(row, rows[0] if i > 0 else 0)
			if i > 0:
				top += topoffset
				bottom += topoffset
			else:
				topoffset = top
			rect = wx.Rect()
			rect.top = top
			rect.bottom = bottom
			rect.x = 0
			rect.width = self.GetRowLabelSize()

			renderer = self._row_label_renderers.get(row,
													 self._default_row_label_renderer)

			renderer.Draw(self, dc, rect, row)

	def OnResize(self, event):
		region = wx.Region(*self.ClientRect.Get())
		for row, col in self.CalcCellsExposed(region):
			cell_renderer = self.GetCellRenderer(row, col)
			if (isinstance(cell_renderer, CustomCellRenderer) and
				cell_renderer._selectionbitmaps):
				# On resize, we need to tell any CustomCellRenderer that its
				# bitmaps are no longer valid and are garbage collected
				cell_renderer._selectionbitmaps = {}
		event.Skip()
        
	def SetColLabelRenderer(self, col, renderer):
		"""
		Register a renderer to be used for drawing the label for the
		given column.
		"""
		if renderer is None:
			if col in self._col_label_renderers:
				del self._col_label_renderers[col]
		else:
			self._col_label_renderers[col] = renderer

	def SetDefaultCellBackgroundColour(self, color):
		# Set alpha to 0 so we can detect the default color
		if not isinstance(color, wx.Colour):
			color = wx.Colour(color)
		color.Set(color.Red(), color.Green(), color.Blue(), 0)
		wx.grid.Grid.SetDefaultCellBackgroundColour(self, color)

	def SetRowLabelRenderer(self, row, renderer):
		"""
		Register a renderer to be used for drawing the label for the
		given row.
		"""
		if renderer is None:
			if row in self._row_label_renderers:
				del self._row_label_renderers[row]
		else:
			self._row_label_renderers[row] = renderer

	def select_row(self, row, shift=False, ctrl=False):
		self.SetFocus()
		if not shift and not ctrl:
			self.SetGridCursor(row, max(self.GetGridCursorCol(), 0))
			self.SelectRow(row)
			self._anchor_row = row
		self.MakeCellVisible(row, max(self.GetGridCursorCol(), 0))
		if self.IsSelection():
			if shift:
				self.BeginBatch()
				rows = self.GetSelectionRows()
				sel = range(min(self._anchor_row, row),
							max(self._anchor_row, row) + 1)
				desel = []
				add = []
				for i in rows:
					if i not in sel:
						desel.append(i)
				for i in sel:
					if i not in rows:
						add.append(i)
				if len(desel) >= len(add):
					# in this case deselecting rows will take as long or longer than selecting, so use SelectRow to speed up the operation
					self.SelectRow(row)
				else:
					for i in desel:
						self.DeselectRow(i)
				for i in add:
					self.SelectRow(i, True)
				self.EndBatch()
				return False
			elif ctrl:
				if self.IsInSelection(row, 0):
					self.DeselectRow(row)
					return True
				else:
					self.SelectRow(row, True)
		return False


class CustomCheckBox(wx.Panel):

	"""
	A custom checkbox where the label is independent from the checkbox itself.
	
	Works around wxMac not taking into account text (foreground) color on
	a default checkbox label.
	
	"""

	def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition,
				 size=wx.DefaultSize, style=wx.NO_BORDER,
				 validator=wx.DefaultValidator, name="CustomCheckBox"):
		wx.Panel.__init__(self, parent, id, pos, size, style, name)
		self.BackgroundColour = parent.BackgroundColour
		self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
		self._cb = wx.CheckBox(self, -1)
		self._enabled = True
		self.Sizer.Add(self._cb, flag=wx.ALIGN_CENTER_VERTICAL)
		if sys.platform == "darwin":
			self._label = wx.StaticText(self, -1, "")
			self.Sizer.Add(self._label, 1, wx.ALIGN_CENTER_VERTICAL)
			self._label.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
			self._label.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
			self._up = True
		else:
			self._label = self._cb
		self._label.Label = label

	def Bind(self, event_id, handler):
		self._cb.Bind(event_id, handler)

	def Disable(self):
		self.Enable(False)

	def Enable(self, enable=True):
		enable = bool(enable)
		if self.Enabled is enable:
			return
		self._enabled = enable
		self._cb.Enable(enable)
		if self._label is not self._cb:
			if not hasattr(self, "_fgcolor"):
				self._fgcolor = self.ForegroundColour
			color = self._fgcolor
			if not enable:
				bgcolor = self.Parent.BackgroundColour
				bgblend = .5
				blend = .5
				color = wx.Colour(int(round(bgblend * bgcolor.Red() +
											blend * color.Red())),
								  int(round(bgblend * bgcolor.Green() +
											blend * color.Green())),
								  int(round(bgblend * bgcolor.Blue() +
											blend * color.Blue())))
			self._label.ForegroundColour = color

	@Property
	def Enabled():
		def fget(self):
			return self.IsEnabled()

		def fset(self, enable=True):
			self.Enable(enable)

		return locals()

	def IsEnabled(self):
		return self._enabled

	def OnLeftDown(self, event):
		if not self.Enabled:
			return
		self._up = False
		self._cb.SetFocus()
		event.Skip()

	def OnLeftUp(self, event):
		if not self.Enabled:
			return
		if not self._up and self.ClientRect.Contains(event.GetPosition()):
			# if the checkbox was down when the mouse was released...
			self.SendCheckBoxEvent()
		self._up = True

	def SendCheckBoxEvent(self):
		""" Actually sends the event to the checkbox. """
		
		self.Value = not self.Value
		checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
									 self._cb.GetId())
		checkEvent.SetInt(int(self.Value))

		# Set the originating object for the event (the checkbox)
		checkEvent.SetEventObject(self._cb)

		# Watch for a possible listener of this event that will catch it and
		# eventually process it
		self._cb.GetEventHandler().ProcessEvent(checkEvent)

	def SetMaxFontSize(self, pointsize=11):
		self._label.SetMaxFontSize(pointsize)

	def GetValue(self):
		"""
		Returns the state of CustomCheckBox, True if checked, False
		otherwise.
		"""

		return self._cb.Value

	def IsChecked(self):
		"""
		This is just a maybe more readable synonym for GetValue: just as the
		latter, it returns True if the CustomCheckBox is checked and False
		otherwise.
		"""

		return self._cb.Value

	def SetValue(self, state):
		"""
		Sets the CustomCheckBox to the given state. This does not cause a
		wx.wxEVT_COMMAND_CHECKBOX_CLICKED event to get emitted.
		"""

		self._cb.Value = state

	def GetLabel(self):
		return self._label.Label

	def SetLabel(self, label):
		self._label.Label = label

	def SetForegroundColour(self, color):
		wx.Panel.SetForegroundColour(self, color)
		self._label.ForegroundColour = color

	@Property
	def Label():
		def fget(self):
			return self.GetLabel()

		def fset(self, label):
			self.SetLabel(label)

		return locals()

	@Property
	def Value():
		def fget(self):
			return self.GetValue()

		def fset(self, state):
			self.SetValue(state)

		return locals()

	@Property
	def ForegroundColour():
		def fget(self):
			return self.GetForegroundColour()

		def fset(self, color):
			self.SetForegroundColour(color)

		return locals()


class CustomCellEditor(wx.grid.PyGridCellEditor):

	def __init__(self, grid):
		wx.grid.PyGridCellEditor.__init__(self)
		self._grid = grid
		self._cell_renderer = CustomCellRenderer()

	def Create(self, parent, id, evtHandler):
		"""
		Called to create the control, which must derive from wx.Control.
		"""
		safe_print("CustomCellEditor.Create(%r, %r, %r) was called. This "
				   "should not happen, but is unlikely an issue." %
				   (parent, id, evtHandler))
		self.SetControl(wx.StaticText(parent, -1, ""))

	def SetSize(self, rect):
		"""
		Called to position/size the edit control within the cell rectangle.
		If you don't fill the cell (the rect) then be sure to override
		PaintBackground and do something meaningful there.
		"""
		safe_print("CustomCellEditor.SetSize(%r) was called. This should not "
				   "happen, but is unlikely an issue." % rect)
		self.Control.SetDimensions(rect.x, rect.y, rect.width, rect.height,
							   wx.SIZE_ALLOW_MINUS_ONE)

	def Show(self, show, attr):
		"""
		Show or hide the edit control.  You can use the attr (if not None)
		to set colours or fonts for the control.
		"""
		safe_print("CustomCellEditor.Show(%r, %r) was called. This should "
				   "not happen, but is unlikely an issue." % (show, attr))
		super(self.__class__, self).Show(show, attr)

	def PaintBackground(self, dc, rect, attr=None):
		"""
		Draws the part of the cell not occupied by the edit control.  The
		base  class version just fills it with background colour from the
		attribute.  In this class the edit control fills the whole cell so
		don't do anything at all in order to reduce flicker.
		"""
		self._cell_renderer.Draw(self._grid, attr, dc, rect,
								 self._grid.GetGridCursorRow(),
								 self._grid.GetGridCursorCol(), True)

	def BeginEdit(self, row, col, grid):
		"""
		Fetch the value from the table and prepare the edit control
		to begin editing.  Set the focus to the edit control.
		"""
		safe_print("CustomCellEditor.BeginEdit(%r, %r, %r) was called. This "
				   "should not happen, but is unlikely an issue." %
				   (row, col, grid))

	def EndEdit(self, row, col, grid, value=None):
		"""
		Complete the editing of the current cell. Returns True if the value
		has changed.  If necessary, the control may be destroyed.
		"""
		safe_print("CustomCellEditor.EndEdit(%r, %r, %r, %r) was called. This "
				   "should not happen, but is unlikely an issue." %
				   (row, col, grid, value))
		if wx.VERSION >= (2, 9):
			changed = None
		else:
			changed = False
		return changed

	def Reset(self):
		"""
		Reset the value in the control back to its starting value.
		"""
		safe_print("CustomCellEditor.Reset() was called. This should "
				   "not happen, but is unlikely an issue.")

	def IsAcceptedKey(self, evt):
		"""
		Return True to allow the given key to start editing: the base class
		version only checks that the event has no modifiers.  F2 is special
		and will always start the editor.
		"""
		return False

	def StartingKey(self, evt):
		"""
		If the editor is enabled by pressing keys on the grid, this will be
		called to let the editor do something about that first key if desired.
		"""
		safe_print("CustomCellEditor.StartingKey(%r) was called. This should "
				   "not happen, but is unlikely an issue." % evt)
		evt.Skip()

	def StartingClick(self):
		"""
		If the editor is enabled by clicking on the cell, this method will be
		called to allow the editor to simulate the click on the control if
		needed.
		"""

	def Destroy(self):
		"""final cleanup"""
		super(self.__class__, self).Destroy()

	def Clone(self):
		"""
		Create a new object which is the copy of this one
		*Must Override*
		"""
		return self.__class__()


class CustomCellRenderer(wx.grid.PyGridCellRenderer):

	def __init__(self, *args, **kwargs):
		wx.grid.PyGridCellRenderer.__init__(self)
		self.specialbitmap = getbitmap("theme/checkerboard-10x10x2-333-444")
		self._selectionbitmaps = {}

	def Clone(self):
		return self.__class__()

	def Draw(self, grid, attr, dc, rect, row, col, isSelected):
		orect = rect
		#if col == grid.GetNumberCols() - 1:
			## Last column
			#w = max(grid.ClientSize[0] - rect[0], rect[2])
			#rect = wx.Rect(rect[0], rect[1], w, rect[3])
		bgcolor = grid.GetCellBackgroundColour(row, col)
		col_label = grid.GetColLabelValue(col)
		is_default_bgcolor = bgcolor == grid.GetDefaultCellBackgroundColour()
		is_read_only = grid.IsReadOnly(row, col)
		if is_default_bgcolor:
			if (is_read_only or not grid.IsEditable()) and col_label:
				bgcolor = grid.GetLabelBackgroundColour()
			if row % 2 == 0 and grid.alternate_cell_background_color:
				bgcolor = wx.Colour(*[int(v * .98) for v in bgcolor])
			elif bgcolor.Alpha() < 255:
				# Make sure it's opaque
				bgcolor = wx.Colour(bgcolor.Red(), bgcolor.Green(),
									bgcolor.Blue())
		special = bgcolor.Get(True) == (0, 0, 0, 0)
		mavericks = getattr(grid, "style", None) == "Mavericks"
		if (not special and mavericks and bgcolor.Red() < 255):
			# Use Mavericks-like color scheme
			newcolor = [int(round(min(bgcolor.Get()[i] * (v / 102.0), 255)))
						for i, v in enumerate((98, 100, 102))]
			bgcolor.Set(*newcolor)
		rowselect = grid.GetSelectionMode() == wx.grid.Grid.wxGridSelectRows
		isCursor = (grid.show_cursor_outline and
					((row, col) == (grid.GetGridCursorRow(),
									grid.GetGridCursorCol()) or
					 (rowselect and row == grid.GetGridCursorRow())))
		if (isSelected or isCursor) and is_default_bgcolor and col_label:
			color = grid.GetSelectionBackground()
			if isSelected:
				textcolor = grid.GetSelectionForeground()
			else:
				textcolor = grid.GetCellTextColour(row, col)
			focus = grid.Parent.FindFocus()
			if not focus or grid not in (focus, focus.GetParent(),
										 focus.GetGrandParent()):
				if mavericks or sys.platform == "darwin" or "gtk3" in wx.PlatformInfo:
					# Use Mavericks-like color scheme
					color = wx.Colour(202, 202, 202)
					if isSelected:
						textcolor = wx.Colour(51, 51, 51)
				else:
					rgb = int((color.Red() + color.Green() + color.Blue()) / 3.0)
					color = wx.Colour(rgb, rgb, rgb)
			elif mavericks or sys.platform == "darwin" or "gtk3" in wx.PlatformInfo:
				# Use Mavericks-like color scheme
				color = wx.Colour(44, 93, 205)
				if isSelected:
					textcolor = wx.WHITE
			else:
				alpha = (grid.selection_alpha * 255 or
						 grid.GetSelectionBackground().Alpha())
				# Blend with bg color
				if alpha < 255:
					# Alpha blending
					bgblend = (255 - alpha) / 255.0
					blend = alpha / 255.0
					color = wx.Colour(int(round(bgblend * bgcolor.Red() +
												blend * color.Red())),
									  int(round(bgblend * bgcolor.Green() +
												blend * color.Green())),
									  int(round(bgblend * bgcolor.Blue() +
												blend * color.Blue())))
		else:
			color = bgcolor
			textcolor = grid.GetCellTextColour(row, col)
		if special:
			# Special
			image = self.specialbitmap.ConvertToImage()
			image.Rescale(rect[2], rect[3])
			dc.DrawBitmap(image.ConvertToBitmap(), rect[0], rect[1])
		else:
			if (isSelected or isCursor) and col_label:
				dc.SetBrush(wx.Brush(bgcolor))
				dc.SetPen(wx.TRANSPARENT_PEN)
				dc.DrawRectangleRect(rect)
				rect = orect
				if not rowselect:
					w = 60 + rect[3] * 2
					if rect.Width > w:
						rect.Left += (rect.Width - w) / 2.0
						rect.Width = w
				offset = 1  # Selection offset from cell boundary
				cb = 2  # Cursor border
				left = (not rowselect or
						col == 0 or not grid.GetColLabelValue(col - 1))
				right = (not rowselect or
						 col == grid.GetNumberCols() - 1 or
						 not grid.GetColLabelValue(col + 1))
				# We could use wx.GCDC, but it is buggy under wxGTK
				# (grid doesn't refresh properly when scrolling).
				# Implement our own supersampling antialiasing method
				# using wx.MemoryDC and cached bitmaps.
				# This has the drawback that it may use a lot of memory if
				# a grid has many cells with different dimensions or color
				# properties, but we make sure that old bitmaps are garbage
				# collected if the grid or rows/cols are resized by binding
				# resize events in CustomGrid initialization.
				key = rect[2:] + bgcolor.Get() + color.Get() + (left, right,
																not isSelected
																and isCursor)
				if not self._selectionbitmaps.get(key):
					scale = 4.0  # Supersampling factor
					offset *= scale
					cb *= scale
					x, y, w, h = 0, 0, rect[2] * scale, rect[3] * scale
					self._selectionbitmaps[key] = wx.EmptyBitmap(w, h)
					paintdc = dc
					dc = mdc = wx.MemoryDC(self._selectionbitmaps[key])
					dc.SetBrush(wx.Brush(bgcolor))
					dc.SetPen(wx.TRANSPARENT_PEN)
					dc.DrawRectangle(x, y, w, h)
					dc.SetBrush(wx.Brush(color))
					dc.SetPen(wx.TRANSPARENT_PEN)
					draw = dc.DrawRoundedRectangle
					draw(x + offset, y + offset, w - offset * 2,
						 h - offset * 2, h / 2.0 - offset)
					if not left:
						dc.DrawRectangle(x, y + offset, h / 2.0,
										 h - offset * 2)
					if not right:
						dc.DrawRectangle(x + w - h / 2.0, y + offset, h / 2.0,
										 h - offset * 2)
					if not isSelected and isCursor:
						dc.SetBrush(wx.Brush(bgcolor))
						draw(x + offset + cb, y + offset + cb,
							 w - (offset + cb) * 2, h - (offset + cb) * 2,
							 h / 2.0 - (offset + cb))
						if not left:
							dc.DrawRectangle(x, y + offset + cb, h / 2.0,
											 h - (offset + cb) * 2)
						if not right:
							dc.DrawRectangle(x + w - h / 2.0, y + offset + cb,
											 h / 2.0, h - (offset + cb) * 2)
					mdc.SelectObject(wx.NullBitmap)
					dc = paintdc
					img = self._selectionbitmaps[key].ConvertToImage()
					# Scale down to original size ("antialiasing")
					img.Rescale(rect[2], rect[3])
					self._selectionbitmaps[key] = img.ConvertToBitmap()
				bmp = self._selectionbitmaps.get(key)
				if bmp:
					dc.DrawBitmap(bmp, rect[0], rect[1])
			else:
				dc.SetBrush(wx.Brush(color))
				dc.SetPen(wx.TRANSPARENT_PEN)
				dc.DrawRectangleRect(rect)
		dc.SetFont(grid.GetCellFont(row, col))
		dc.SetTextForeground(textcolor)
		self.DrawLabel(grid, dc, orect, row, col)

	def DrawLabel(self, grid, dc, rect, row, col):
		align = grid.GetCellAlignment(row, col)
		if align[1] == wx.ALIGN_CENTER:
			align = align[0], wx.ALIGN_CENTER_VERTICAL
		dc.DrawLabel(grid.GetCellValue(row, col), rect,
					 align[0] | align[1])

	def GetBestSize(self, grid, attr, dc, row, col):
		dc.SetFont(grid.GetCellFont(row, col))
		return dc.GetTextExtent(grid.GetCellValue(row, col))


class CustomCellBoolRenderer(CustomCellRenderer):

	def __init__(self, *args, **kwargs):
		CustomCellRenderer.__init__(self)
		self._bitmap = geticon(16, "checkmark")
		self._bitmap_unchecked = geticon(16, "x")

	def DrawLabel(self, grid, dc, rect, row, col):
		if grid.GetCellValue(row, col) == "1":
			bitmap = self._bitmap
		else:
			bitmap = self._bitmap_unchecked
		x = rect[0] + int((rect[2] - self._bitmap.Size[0]) / 2.0)
		y = rect[1] + int((rect[3] - self._bitmap.Size[1]) / 2.0)
		dc.DrawBitmap(bitmap, x, y)

	def GetBestSize(self, grid, attr, dc, row, col):
		return self._bitmap.Size


class CustomColLabelRenderer(object):

	def __init__(self, bgcolor=None):
		self.bgcolor = bgcolor

	def Draw(self, grid, dc, rect, col=-1):
		if not grid.GetNumberCols():
			return
		orect = rect
		if col == grid.GetNumberCols() - 1:
			# Last column
			w = max(grid.ClientSize[0] - rect[0], rect[2])
			rect = wx.Rect(rect[0], rect[1], w, rect[3])
		mavericks = (getattr(grid, "style", None) == "Mavericks" or
					 sys.platform == "darwin")
		if grid.headerbitmap:
			img = grid.headerbitmap.ConvertToImage()
			img.Rescale(rect[2], rect[3], quality=wx.IMAGE_QUALITY_NORMAL)
			dc.DrawBitmap(img.ConvertToBitmap(), rect[0], 0)
			pen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW), 1,
						 wx.SOLID)
			pen.SetCap(wx.CAP_BUTT)
			dc.SetPen(pen)
			dc.DrawLine(rect[0], rect[1] + rect[3] - 1, rect[0] + rect[2],
						rect[1] + rect[3] - 1)
		elif grid.rendernative:
			render = wx.RendererNative.Get()
			render.DrawHeaderButton(grid, dc, rect)
		else:
			if self.bgcolor:
				color = self.bgcolor
			else:
				if mavericks:
					# Use Mavericks-like color scheme
					color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX)
				else:
					color = grid.GetLabelBackgroundColour()
				if col % 2 == 0 and grid.alternate_col_label_background_color:
					color = wx.Colour(*[int(v * .98) for v in color])
				elif color.Alpha() < 255:
					# Make sure it's opaque
					color = wx.Colour(color.Red(), color.Green(), color.Blue())
			dc.SetBrush(wx.Brush(color))
			dc.SetPen(wx.TRANSPARENT_PEN)
			dc.DrawRectangleRect(rect)
			pen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW))
			dc.SetPen(pen)
			if getattr(grid, "draw_horizontal_grid_lines", True) or (mavericks and
																	 not self.bgcolor):
				dc.DrawLine(rect[0], rect[1] + rect[3] - 1, rect[0] + rect[2] - 1,
							rect[1] + rect[3] - 1)
			if getattr(grid, "draw_vertical_grid_lines", True):
				dc.DrawLine(rect[0] + rect[2] - 1, rect[1],
							rect[0] + rect[2] - 1, rect[3])
		if getattr(grid, "draw_col_labels", True) and col > -1:
			dc.SetFont(grid.GetLabelFont())
			if mavericks and not self.bgcolor and (color.Red() == 255 and
												   color.Green() == 255 and
												   color.Blue() == 255):
				# Use Mavericks-like color scheme
				color = wx.Colour(80, 100, 120)
			else:
				color = grid.GetLabelTextColour()
			dc.SetTextForeground(color)
			align = grid.GetColLabelAlignment()
			if align[1] == wx.ALIGN_CENTER:
				align = align[0], wx.ALIGN_CENTER_VERTICAL
			dc.DrawLabel(" %s " % grid.GetColLabelValue(col), orect,
						 align[0] | align[1])


class CustomRowLabelRenderer(object):

	def __init__(self, bgcolor=None):
		self.bgcolor = bgcolor

	def Draw(self, grid, dc, rect, row):
		if self.bgcolor:
			color = self.bgcolor
		else:
			color = grid.GetLabelBackgroundColour()
			if row % 2 == 0 and grid.alternate_row_label_background_color:
				color = wx.Colour(*[int(v * .98) for v in color])
			elif color.Alpha() < 255:
				# Make sure it's opaque
				color = wx.Colour(color.Red(), color.Green(), color.Blue())
			if getattr(grid, "style", None) == "Mavericks" and color.Red() < 255:
				# Use Mavericks-like color scheme
				newcolor = [int(round(color.Get()[i] * (v / 102.0)))
							for i, v in enumerate((98, 100, 102))]
				color.Set(*newcolor)
		dc.SetBrush(wx.Brush(color))
		dc.SetPen(wx.TRANSPARENT_PEN)
		dc.DrawRectangleRect(rect)
		pen = wx.Pen(grid.GetGridLineColour())
		dc.SetPen(pen)
		if getattr(grid, "draw_horizontal_grid_lines", True):
			dc.DrawLine(rect[0], rect[1] + rect[3] - 1, rect[0] + rect[2] - 1,
						rect[1] + rect[3] - 1)
		if getattr(grid, "draw_vertical_grid_lines", True):
			dc.DrawLine(rect[0] + rect[2] - 1, rect[1], rect[0] + rect[2] - 1,
						rect[3])
		if getattr(grid, "draw_row_labels", True):
			dc.SetFont(grid.GetLabelFont())
			dc.SetTextForeground(grid.GetLabelTextColour())
			align = grid.GetRowLabelAlignment()
			if align[1] == wx.ALIGN_CENTER:
				align = align[0], wx.ALIGN_CENTER_VERTICAL
			dc.DrawLabel(" %s " % grid.GetRowLabelValue(row), rect,
						 align[0] | align[1])


class HStretchStaticBitmap(wx.StaticBitmap):

	"""
	A StaticBitmap that will automatically stretch horizontally.
	
	To be used with sizers.
	
	"""

	def __init__(self, *args, **kwargs):
		wx.StaticBitmap.__init__(self, *args, **kwargs)
		if sys.platform in ("darwin", "win32"):
			# Works (only) under Mac OS X / Windows.
			# Works better (no jump to correct width) under Mac OS X than
			# EVT_SIZE method.
			self.Bind(wx.EVT_PAINT, self.OnPaint)
		else:
			# Works under Mac OS X, Windows and Linux.
			# Under Mac OS X, this visibly jumps to the correct width when
			# first shown on screen.
			self.Bind(wx.EVT_SIZE, self.OnPaint)
		self._init = False

	def OnPaint(self, event):
		if self and self.GetBitmap() and self.GetBitmap().IsOk():
			if hasattr(self, "_bmp"):
				bmp = self._bmp
			else:
				bmp = self.GetBitmap()
				self._bmp = bmp
			w = self.Size[0]
			if getattr(self, "_width", -1) != w:
				img = bmp.ConvertToImage()
				img.Rescale(w, img.GetSize()[1])
				bmp = img.ConvertToBitmap()
				self._width = w
				# Avoid flicker under Windows
				if self._init:
					self.Freeze()
				self.SetBitmap(bmp)
				self.MinSize = self._bmp.GetSize()
				if self._init:
					self.IsFrozen() and self.Thaw()
				elif self.IsShownOnScreen():
					self._init = True
		if event:
			event.Skip()


class HyperLinkCtrl(hyperlink.HyperLinkCtrl):

	def __init__(self, *args, **kwargs):
		hyperlink.HyperLinkCtrl.__init__(self, *args, **kwargs)
		bgcolor, text, linkcolor, vlinkcolor = get_html_colors()
		self.SetColours(linkcolor, vlinkcolor, linkcolor)
		self.DoPopup(False)
		self.UpdateLink()
		self.Bind(hyperlink.EVT_HYPERLINK_RIGHT, self.OnPopup)

	def OnPopup(self, event):
		""" Pops up a menu with 'Copy link address' """
		menuPopUp = wx.Menu("", wx.MENU_TEAROFF)
		menuPopUp.Append(hyperlink.wxHYPERLINKS_POPUP_COPY,
						 lang.getstr("link.address.copy"))
		self.Bind(wx.EVT_MENU, self.OnPopUpCopy,
				  id=hyperlink.wxHYPERLINKS_POPUP_COPY)
		self.PopupMenu(menuPopUp, event.GetPosition())
		menuPopUp.Destroy()
		self.Unbind(wx.EVT_MENU, id=hyperlink.wxHYPERLINKS_POPUP_COPY)

	if sys.platform not in ("darwin", "win32") and "gtk3" in wx.PlatformInfo:
		def SetFont(self, font):
			scale = (getcfg("app.dpi") / get_default_dpi()) or 1
			font.PointSize *= scale
			hyperlink.HyperLinkCtrl.SetFont(self, font)


def fancytext_Renderer_getCurrentFont(self):
	# Use system font instead of fancytext default font
	font = self.fonts[-1]
	_font = self._font or wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
	if "encoding" in font:
		_font.SetEncoding(font["encoding"])
	if "family" in font:
		_font.SetFamily(font["family"])
	if "size" in font:
		_font.SetPointSize(font["size"])
	if "gtk3" in wx.PlatformInfo:
		scale = getcfg("app.dpi") / get_default_dpi()
		_font.SetPointSize(_font.GetPointSize() * scale)
	if "style" in font:
		_font.SetStyle(font["style"])
	if "weight" in font:
		_font.SetWeight(font["weight"])
	return _font

def fancytext_Renderer_getCurrentColor(self):
	# Use system text color instead of fancytext default color
	font = self.fonts[-1]
	if "color" in font:
		return wx.TheColourDatabase.FindColour(font["color"])
	else:
		return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)

fancytext.Renderer._font = None
fancytext.Renderer.getCurrentColor = fancytext_Renderer_getCurrentColor
fancytext.Renderer.getCurrentFont = fancytext_Renderer_getCurrentFont


def fancytext_RenderToRenderer(str, renderer, enclose=True):
	str = safe_str(str, "UTF-8")
	try:
		if enclose:
			str = '<?xml version="1.0"?><FancyText>%s</FancyText>' % str
		p = xml.parsers.expat.ParserCreate()
		p.returns_unicode = 1
		p.StartElementHandler = renderer.startElement
		p.EndElementHandler = renderer.endElement
		p.CharacterDataHandler = renderer.characterData
		p.Parse(str, 1)
	except xml.parsers.expat.error, err:
		raise ValueError('error parsing text text "%s": %s' % (str, err)) 

fancytext.RenderToRenderer = fancytext_RenderToRenderer


class BetterPyGauge(pygauge.PyGauge):

	def __init__(self, parent, id=wx.ID_ANY, range=100, pos=wx.DefaultPosition,
                 size=(-1,30), style=0, pd=None):
		self.pd = pd
		self.dpiscale = getcfg("app.dpi") / get_default_dpi()
		if self.dpiscale > 1:
			size = tuple(int(round(v * self.dpiscale)) if v != -1 else v
						 for v in size)
		pygauge.PyGauge.__init__(self, parent, id, range, pos, size, style)
		self._indeterminate = False
		self.gradientindex = 0
		self._gradients = []
		self._indeterminate_gradients = []
		self._timer = BetterTimer(self)
		self.Unbind(wx.EVT_TIMER)
		self.Bind(EVT_BETTERTIMER, self.OnTimer, self._timer)
		self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
		self.Start()
	
	def OnDestroy(self, event):
		self._timer.Stop()
		del self._timer

	def OnPaint(self, event):
		"""
		Handles the ``wx.EVT_PAINT`` event for L{PyGauge}.

		:param `event`: a `wx.PaintEvent` event to be processed.
		"""

		dc = wx.BufferedPaintDC(self)
		gc = wx.GraphicsContext.Create(dc)
		dc.SetBackground(wx.Brush(self.Parent.BackgroundColour))
		dc.Clear()

		rect = self.GetClientRect()

		if self._border_colour:
			gc.SetPen(wx.Pen(self._border_colour))
		else:
			gc.SetPen(wx.Pen(self.BackgroundColour))
		gc.SetBrush(wx.Brush(self.BackgroundColour))
		gc.DrawRoundedRectangle(rect.X, rect.Y, rect.Width - 1 * self.dpiscale,
								rect.Height - 1 * self.dpiscale,
								(rect.Height - 1 * self.dpiscale) / 2)

		pad = self.GetBorderPadding()
		if self._border_colour:
			pad += 1
		pad *= self.dpiscale
		if pad:
			rect.Deflate(pad, pad)

		if self._barGradientSorted:
			for i, gradient in enumerate(self._barGradientSorted):
				c1, c2 = gradient
				w = max(rect.Width * (float(self._valueSorted[i]) / self._range),
						rect.Height)
				gc.SetBrush(gc.CreateLinearGradientBrush(rect.X, rect.Y,
														 rect.Y + w,
														 rect.Y, c1, c2))
				gc.SetPen(wx.TRANSPARENT_PEN)
				gc.DrawRoundedRectangle(rect.X, rect.Y, w, rect.Height,
										(rect.Height) / 2)

	def OnTimer(self, event):
		gradient = self._barGradient
		if self._indeterminate:
			if self.gradientindex < len(self._indeterminate_gradients) - 1:
				self.gradientindex += 1
			else:
				self.gradientindex = 0
			self._barGradient = [self._indeterminate_gradients[self.gradientindex]]
		else:
			if self.gradientindex < len(self._gradients) - 1:
				self.gradientindex += 1
			else:
				self.gradientindex = 0
			self._barGradient = [self._gradients[self.gradientindex]]
		if hasattr(self, "_update_step") and not self._indeterminate:
			stop = True
			for i, v in enumerate(self._value):
				self._value[i] += self._update_step[i]
				
				if self._update_step[i] > 0:
					if self._value[i] > self._update_value[i]:
						self._value[i] = self._update_value[i]
					else: stop = False
				else:
					if self._value[i] < self._update_value[i]:
						self._value[i] = self._update_value[i]
					else: stop = False
			if self.pd and self.pd.taskbar:
				self.pd.taskbar.set_progress_value(int(round(self.GetValue())))
			if stop:
				del self._update_step
			updated = True
		else:
			updated = False
		if gradient != self._barGradient or updated:
			self.SortForDisplay()
			if self._indeterminate:
				self._valueSorted = [self.GetRange()]
			self.Refresh()

	def IsRunning(self):
		return self._timer.IsRunning()

	def Pulse(self):
		self._indeterminate = True

	def SetBarGradients(self, gradients):
		self._gradients = gradients

	def SetIndeterminateBarGradients(self, gradients):
		self._indeterminate_gradients = gradients

	def SetValue(self, value):
		self._indeterminate = False
		pygauge.PyGauge.SetValue(self, value)
		self.Refresh()

	def Start(self, milliseconds=67):
		self._timer.Start(milliseconds)

	def Stop(self):
		self._timer.Stop()

	def Update(self, value, time=0):
		"""
		Update the gauge by adding `value` to it over `time` milliseconds. The `time` parameter
		**must** be a multiple of 50 milliseconds.

		:param `value`: The value to be added to the gauge;
		:param `time`: The length of time in milliseconds that it will take to move the gauge.
		"""
		self._indeterminate = False
	   
		if type(value) != type([]):
			value = [value]
			 
		if len(value) != len(self._value):
			raise Exception("ERROR:\n len(value) != len(self.GetValue())")

		self._update_value = []
		self._update_step  = []
		for i, v in enumerate(self._value):
			if value[i] < 0 or value[i] > self._range:
				warnings.warn("Gauge value %r is invalid - must be between 0 "
							  "and %s" % (value[i], self._range), Warning)
				if value[i] < 0:
					value[i] = 0
				else:
					value[i] = self._range
		
			self._update_value.append(value[i])
			self._update_step.append((float(value[i]) - v)/(time/50))


class BetterStaticFancyTextBase(object):

	def GetLabel(self):
		return self._rawlabel

	@Property
	def Label():
		def fget(self):
			return self._rawlabel
		
		def fset(self, label):
			self.SetLabel(label)
		
		return locals()

	def SetLabel(self, label):
		self._rawlabel = label
		# Wrap ignoring tags, only break in whitespace
		wrapped = ""
		llen = 0
		intag = False
		hyphens = u"-\u2012\u2013\u2014\u2015"
		whitespace = "\n\t "
		for c in label:
			if c == "<":
				intag = True
			if c in hyphens:
				hyphen = True
			else:
				hyphen = False
			if intag and c == ">":
				intag = False
			elif not intag:
				llen += 1
				if llen > self.maxlen and (c in whitespace or hyphen):
					if hyphen:
						wrapped += c
					elif llen > self.maxlen + 1:
						for i, rc in enumerate(reversed(wrapped)):
							if rc == ">":
								intag = True
							elif not intag and rc in whitespace + hyphens:
								line = wrapped[-i:]
								llen = 1
								for rc in reversed(line):
									if rc == ">":
										intag = True
									if not intag:
										llen += 1
									elif rc == "<":
										intag = False
								wrapped = wrapped[:-i] + "\n" + line
								break
							elif intag and rc == "<":
								intag = False
					if llen > self.maxlen:
						c = "\n"
				if c == "\n":
					llen = 0
			wrapped += c
		self._label = wrapped

class BetterStaticFancyText(BetterStaticFancyTextBase, GenStaticBitmap):

	"""
	Based on wx.lib.fancytext functionality.
	
	Renders crisp on 'Retina' displays under OS X and in high DPI mode
	under Linux/Windows.
	
	"""

	_enabled = True
	_rawlabel = ""
	maxlen = 119

	def __init__(self, window, id, text, *args, **kargs):
		args = list(args)
		kargs.setdefault('name', 'staticFancyText')
		# Ignore background, always use parent background
		if 'background' in kargs:
			kargs.pop('background')
		elif args:
			args.pop(0)
		
		bmp = wx.EmptyBitmap(1, 1)
		GenStaticBitmap.__init__(self, window, id, bmp, *args, **kargs)
		self.Label = text

	def Disable(self):
		self.Enable(False)

	def Enable(self, enable=True):
		self._enabled = enable
		if sys.platform != "win32":
			return
		if enable:
			bmp = self._enabledbitmap
		else:
			bmp = self._enabledbitmap.ConvertToImage().AdjustChannels(1, 1, 1, .5).ConvertToBitmap()
		self.SetBitmap(bmp)

	def IsEnabled(self):
		return self._enabled

	Enabled = property(IsEnabled, Enable)

	def GetFont(self):
		return fancytext.Renderer._font or wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
    
	def OnPaint(self, event):
		if sys.platform != "win32":
			# AutoBufferedPaintDCFactory is the magic needed for crisp text
			# rendering in HiDPI mode under OS X and Linux
			cls = wx.AutoBufferedPaintDCFactory
		else:
			cls = wx.BufferedPaintDC
		dc = cls(self)
		dc.SetBackground(wx.Brush(self.Parent.BackgroundColour, wx.SOLID))
		dc.SetBackgroundMode(wx.TRANSPARENT)
		dc.Clear()
		if sys.platform != "win32":
			fancytext.RenderToDC(self._label, dc, 0, 0)
		elif self._bitmap:
			dc.DrawBitmap(self._bitmap, 0, 0, True)

	def SetFont(self, font):
		fancytext.Renderer._font = font

	def SetLabel(self, label):
		BetterStaticFancyTextBase.SetLabel(self, label)
		background = wx.Brush(self.Parent.BackgroundColour, wx.SOLID)
		try:
			bmp = fancytext.RenderToBitmap(self._label, background)
		except ValueError:
			# XML parsing error, strip all tags
			self._label = re.sub(r"<[^>]*?>", "", self._label)
			self._label = self._label.replace("<", "").replace(">", "")
			bmp = fancytext.RenderToBitmap(self._label, background)
		if sys.platform == "win32":
			self._enabledbitmap = bmp
			self.SetBitmap(bmp)
		else:
			self.Size = self.MinSize = bmp.Size
			self.Refresh()


# Better under GTK (no text cutoff)
class BetterStaticFancyText_SetLabelMarkup(BetterStaticFancyTextBase,
										   wx.Panel):

	def __init__(self, parent, id, text, *args, **kwargs):
		wx.Panel.__init__(self, parent, id, *args, **kwargs)
		self.maxlen = 119
		self._st = wx.StaticText(self, -1, u"")
		self.Label = text

	def SetLabel(self, label):
		BetterStaticFancyTextBase.SetLabel(self, label)
		markup = label.replace("<font", "<span")
		markup = markup.replace("</font>", "</span>")
		# Decode entities
		markup = htmlparser.unescape(markup)
		self._st.SetLabelMarkup(markup)
		# Figure out min size
		minw = 0
		minh = 0
		normal_font = self.Font
		bold_font = self.Font
		bold_font.Weight = wx.FONTWEIGHT_BOLD
		xh = self.GetTextExtent("X")[1]
		for i, line in enumerate(self._label.splitlines()):
			if line:
				# Account for bold text which is approximately 14% wider
				parts = re.findall("(<font weight='bold'>([^<]+)</font>|[^<]+)",
								   line)
				w, h = 0, 0
				for markup, bold in parts:
					if bold:
						text = bold
						font = bold_font
					else:
						text = markup
						font = normal_font
					# Strip all remaining tags
					text = re.sub(r"<[^>]*?>", "", text)
					text = text.replace("<", "").replace(">", "")
					# Decode entities
					text = htmlparser.unescape(text)
					te = self.GetFullTextExtent(text, font)[:2]
					w += te[0]
					h = max(te[1], h)
				minw = max(w, minw)
			else:
				# Deal with empty lines having text extent of (0, 0)
				h = xh
			minh += h
		self._st.MaxSize = (-1, -1)
		self._st.Size = self._st.MinSize = minw, minh
		self._st.MaxSize = minw, -1
		self.Layout()


if (wx.Platform == "__WXGTK__" and
	hasattr(wx.StaticText, "SetLabelMarkup")):
	BetterStaticFancyText = BetterStaticFancyText_SetLabelMarkup


class InfoDialog(BaseInteractiveDialog):

	""" Informational dialog with OK button """

	def __init__(self, parent=None, id=-1, title=appname, msg="", 
				 ok="OK", bitmap=None, pos=(-1, -1), size=(400, -1), 
				 show=True, log=True, bitmap_margin=None, nowrap=False,
				 wrap=70):
		BaseInteractiveDialog.__init__(self, parent, id, title, msg, ok, 
									   bitmap, pos, size, show, log,
									   nowrap=nowrap, wrap=wrap,
									   bitmap_margin=bitmap_margin)


class InvincibleFrame(BaseFrame):

	""" A frame that won't be destroyed when closed """

	def __init__(self, parent=None, id=-1, title="", pos=wx.DefaultPosition,
				 size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
				 name=wx.FrameNameStr):
		BaseFrame.__init__(self, parent, id, title, pos, size, style, name)
		self.Bind(wx.EVT_CLOSE, self.OnClose, self)

	def OnClose(self, event):
		self.Hide()


class LogWindow(InvincibleFrame):

	""" A log-type window with Clear and Save As buttons """

	def __init__(self, parent=None, id=-1, title=None, pos=wx.DefaultPosition,
				 size=wx.DefaultSize, logctrlstyle=wx.TE_MULTILINE |
												   wx.TE_RICH | wx.NO_BORDER |
												   wx.TE_READONLY):
		if not title:
			title = lang.getstr("infoframe.title")
		if pos == wx.DefaultPosition:
			pos = getcfg("position.info.x"), getcfg("position.info.y")
		if size == wx.DefaultSize:
			size = getcfg("size.info.w"), getcfg("size.info.h")
		InvincibleFrame.__init__(self, parent, id, 
								 title,
								 pos=pos,
								 style=wx.DEFAULT_FRAME_STYLE,
								 name="info")
		self.last_visible = False
		self.panel = wx.Panel(self, -1)
		# Fix wrong background color when disabled
		self.panel.Enable = lambda enable=True: None
		self.panel.Disable = lambda: None
		self.sizer = wx.BoxSizer(wx.VERTICAL)
		self.panel.SetSizer(self.sizer)
		self.log_txt = wx.TextCtrl(self.panel, -1, "", style=logctrlstyle)
		# Fix wrong background color under Linux when losing focus
		self.log_txt.BackgroundColour = self.log_txt.BackgroundColour
		# Fix wrong background color when disabled
		self.log_txt.Enable = lambda enable=True: None
		self.log_txt.Disable = lambda: None
		if u"phoenix" in wx.PlatformInfo:
			kwarg = "faceName"
		else:
			kwarg = "face"
		if sys.platform == "win32":
			font = wx.Font(9, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, 
													  wx.FONTWEIGHT_NORMAL,
													  **{kwarg: "Consolas"})
		elif sys.platform == "darwin":
			font = wx.Font(11, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, 
													   wx.FONTWEIGHT_NORMAL,
													   **{kwarg: "Monaco"})
		else:
			font = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, 
													   wx.FONTWEIGHT_NORMAL)
		self.log_txt.SetFont(font)
		self.log_txt.SetDefaultStyle(wx.TextAttr(self.log_txt.ForegroundColour,
												 self.log_txt.BackgroundColour,
												 font=font))
		bgcol = wx.Colour(int(self.log_txt.BackgroundColour.Red() * .5),
						  int(self.log_txt.BackgroundColour.Green() * .5),
						  int(self.log_txt.BackgroundColour.Blue() * .5))
		fgcol = wx.Colour(int(bgcol.Red() + self.log_txt.ForegroundColour.Red() * .5),
						  int(bgcol.Green() + self.log_txt.ForegroundColour.Green() * .5),
						  int(bgcol.Blue() + self.log_txt.ForegroundColour.Blue() * .5))
		self._1stcolstyle = wx.TextAttr(fgcol, self.log_txt.BackgroundColour,
										font=font)
		self.sizer.Add(self.log_txt, 1, flag=wx.EXPAND)
		self.log_txt.MinSize = (self.log_txt.GetTextExtent("=" * 95)[0] +
								wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X),
								defaults["size.info.h"] - 24 -
								max(0, self.Size[1] - self.ClientSize[1]))
		separator = BitmapBackgroundPanel(self.panel, size=(-1, 1))
		separator.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW))
		self.sizer.Add(separator, flag=wx.EXPAND)
		self.btnsizer = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer.Add(self.btnsizer, flag=wx.EXPAND)
		self.save_as_btn = GenBitmapButton(self.panel, -1, 
										   geticon(16, "document-save-as"), 
										   style = wx.NO_BORDER)
		self.save_as_btn.Bind(wx.EVT_BUTTON, self.OnSaveAs)
		self.save_as_btn.SetToolTipString(lang.getstr("save_as"))
		self.btnsizer.Add(self.save_as_btn, flag=wx.ALL, border=4)
		self.archive_btn = GenBitmapButton(self.panel, -1, 
										   geticon(16, "package-x-generic"), 
										   style = wx.NO_BORDER)
		self.archive_btn.Bind(wx.EVT_BUTTON, self.create_logs_archive)
		self.archive_btn.SetToolTipString(lang.getstr("archive.create"))
		self.btnsizer.Add(self.archive_btn, flag=wx.ALL, border=4)
		self.clear_btn = GenBitmapButton(self.panel, -1, 
										 geticon(16, "edit-delete"), 
										 style = wx.NO_BORDER)
		self.clear_btn.Bind(wx.EVT_BUTTON, self.OnClear)
		self.clear_btn.SetToolTipString(lang.getstr("clear"))
		self.btnsizer.Add(self.clear_btn, flag=wx.ALL, border=4)
		self.sizer.SetSizeHints(self)
		self.sizer.Layout()
		self.SetSaneGeometry(*pos + size)
		self.Bind(wx.EVT_MOVE, self.OnMove)
		self.Bind(wx.EVT_SIZE, self.OnSize)
		self.Children[0].Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
		self._tspattern = re.compile(r"(?:\d{4}-\d{2}-\d{2} )?(\d{2}:\d{2}:\d{2},\d{3} )")
		self._textwrapper = textwrap.TextWrapper(76)
		self._linecontinuation = " " * 13 + u"\u21b3 "
		self._warning_lstr = lang.getstr("warning").lower()
		self._error_lstr = lang.getstr("error").lower()

	def Log(self, txt):
		# TextCtrl.AppendText is an EXPENSIVE operation under OS X.
		# For that reason, assemble the text to be added before calling it.
		lines = []
		start = self.log_txt.GetLastPosition()
		ts = None
		for line in safe_unicode(txt).split("\n"):
			if sys.platform == "win32":
				# Formatting of boxes
				line = line.replace(u"\u2500", u"-")
			tsmatch = re.match(self._tspattern, line)
			if tsmatch:
				line = line[len(tsmatch.group()):]
				ts = tsmatch.group(1)
			elif line[:15] == self._linecontinuation:
				ts = line[:13]
				line = line[13:]
			else:
				s = time()
				ms = s - int(s)
				ts = strftime("%H:%M:%S,") + ("%.3f " % ms)[2:]
			if len(line) > 80:
				wrapped = self._textwrapper.wrap(line)
				i_last = len(wrapped) - 1
				for i, line in enumerate(wrapped):
					if i:
						line = self._linecontinuation + line
					else:
						line = ts + line
					if i < i_last:
						line = line.ljust(78 + 13) + u" \u21B2"
					lines.append(line)
			else:
				lines.append(ts + line)
		self.log_txt.AppendText("\n".join(lines) + "\n")
		# TextCtrl.SetStyle is an EXPENSIVE operation, especially under OS X.
		# Only set styles for (up to) the last 1000 lines.
		for line in lines[:-1000]:
			start += len(line + "\n")
		textattr = None
		warning_lstr = self._warning_lstr
		error_lstr = self._error_lstr
		for i, line in enumerate(lines[-1000:]):
			line_lower = line.lower()
			if ("brightness error" in line_lower or
				"white point error" in line_lower or
				"maximum neutral error" in line_lower or
				"average neutral error" in line_lower or
				"profile check complete" in line_lower):
				# These are not actual errors
				pass
			elif warning_lstr in line_lower or "warning" in line_lower:
				textattr = wx.TextAttr("#F07F00", font=self.log_txt.Font)
			elif error_lstr in line_lower or "error" in line_lower:
				textattr = wx.TextAttr("#FF3300", font=self.log_txt.Font)
			self.log_txt.SetStyle(start, start + 12, self._1stcolstyle)
			if textattr:
				self.log_txt.SetStyle(start + 12, start + len(line),
									  textattr)
				if not line.endswith(u" \u21B2"):
					textattr = None
			start += len(line + "\n")
	
	def ScrollToBottom(self):
		self.log_txt.ScrollLines(self.log_txt.GetNumberOfLines())

	def OnClear(self, event):
		self.log_txt.SetValue("")
	
	def OnClose(self, event):
		setcfg("log.show", 0)
		self.Hide()

	def OnDestroy(self, event):
		event.Skip()

	def OnMove(self, event=None):
		if self.IsShownOnScreen() and not self.IsMaximized() and not \
		   self.IsIconized():
			x, y = self.GetScreenPosition()
			setcfg("position.info.x", x)
			setcfg("position.info.y", y)
		if event:
			event.Skip()

	def OnSaveAs(self, event):
		defaultDir, defaultFile = (get_verified_path("last_filedialog_path")[0], 
								   appname)
		dlg = wx.FileDialog(self, lang.getstr("save_as"), 
							defaultDir=defaultDir, defaultFile=defaultFile, 
							wildcard=lang.getstr("filetype.log") + "|*.log", 
							style=wx.SAVE | wx.OVERWRITE_PROMPT)
		dlg.Center(wx.BOTH)
		result = dlg.ShowModal()
		path = dlg.GetPath()
		dlg.Destroy()
		if result == wx.ID_OK:
			if not waccess(path, os.W_OK):
				InfoDialog(self, msg=lang.getstr("error.access_denied.write",
												 path),
						   ok=lang.getstr("ok"),
						   bitmap=geticon(32, "dialog-error"))
				return
			filename, ext = os.path.splitext(path)
			if ext.lower() != ".log":
				path += ".log"
				if os.path.exists(path):
					dlg = ConfirmDialog(self, 
										msg=lang.getstr("dialog."
														"confirm_overwrite", 
														(path)), 
										ok=lang.getstr("overwrite"), 
										cancel=lang.getstr("cancel"), 
										bitmap=geticon(32, "dialog-warning"))
					result = dlg.ShowModal()
					dlg.Destroy()
					if result != wx.ID_OK:
						return
			setcfg("last_filedialog_path", path)
			try:
				file_ = open(path, "w")
				file_.write(self.log_txt.GetValue().encode("UTF-8", "replace"))
				file_.close()
			except Exception, exception:
				InfoDialog(self, msg=safe_unicode(exception), 
						   ok=lang.getstr("ok"), 
						   bitmap=geticon(32, "dialog-error"))

	def OnSize(self, event=None):
		if self.IsShownOnScreen() and not self.IsMaximized() and not \
		   self.IsIconized():
			w, h = self.ClientSize
			setcfg("size.info.w", w)
			setcfg("size.info.h", h)
		if event:
			event.Skip()

	def create_logs_archive(self, event):
		wildcard = lang.getstr("filetype.tgz") + "|*.tgz"
		wildcard += "|" + lang.getstr("filetype.zip") + "|*.zip"
		defaultDir, defaultFile = (get_verified_path("last_filedialog_path")[0], 
								   appname + "-logs")
		dlg = wx.FileDialog(self, lang.getstr("save_as"), 
							defaultDir=defaultDir, defaultFile=defaultFile, 
							wildcard=wildcard, 
							style=wx.SAVE | wx.OVERWRITE_PROMPT)
		dlg.Center(wx.BOTH)
		result = dlg.ShowModal()
		path = dlg.GetPath()
		dlg.Destroy()
		if result == wx.ID_OK:
			if not waccess(path, os.W_OK):
				InfoDialog(self, msg=lang.getstr("error.access_denied.write",
												 path),
						   ok=lang.getstr("ok"),
						   bitmap=geticon(32, "dialog-error"))
				return
			filename, ext = os.path.splitext(path)
			if path.lower().endswith(".tar.gz"):
				format = "tgz"
			else:
				format = ext[1:]
			if (ext.lower() != "." + format and
				(format != "tgz" or not path.lower().endswith(".tar.gz"))):
				path += "." + format
				if os.path.exists(path):
					dlg = ConfirmDialog(self, 
										msg=lang.getstr("dialog."
														"confirm_overwrite", 
														(path)), 
										ok=lang.getstr("overwrite"), 
										cancel=lang.getstr("cancel"), 
										bitmap=geticon(32, "dialog-warning"))
					result = dlg.ShowModal()
					dlg.Destroy()
					if result != wx.ID_OK:
						return
			setcfg("last_filedialog_path", path)
			if format == "tgz":
				# Create gzipped tar archive
				with tarfile.open(path, "w:gz", encoding="UTF-8") as tar:
					tar.add(logdir, arcname=os.path.basename(path))
			else:
				# Create ZIP archive
				try:
					with zipfile.ZipFile(path, 'w', zipfile.ZIP_DEFLATED) as zip:
						for filename in os.listdir(logdir):
							zip.write(os.path.join(logdir, filename), filename)
				except Exception, exception:
					InfoDialog(self, msg=safe_unicode(exception), 
							   ok=lang.getstr("ok"), 
							   bitmap=geticon(32, "dialog-error"))


class ProgressDialog(wx.Dialog):
	
	""" A progress dialog. """

	bitmaps = {}
	
	def __init__(self, title=appname, msg="", maximum=100, parent=None, style=None, 
				 handler=None, keyhandler=None, start_timer=True, pos=None,
				 pauseable=False, fancy=True, allow_close=False):
		if style is None:
			style = (wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME |
					 wx.PD_REMAINING_TIME | wx.PD_CAN_ABORT | wx.PD_SMOOTH)
		self._style = style
		wx.Dialog.__init__(self, parent, wx.ID_ANY, title,
						   name="progressdialog")
		if fancy:
			self.BackgroundColour = "#141414"
			self.ForegroundColour = "#FFFFFF"
		self.taskbar = None
		if sys.platform == "win32":
			if not fancy:
				bgcolor = self.BackgroundColour
				self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
			if sys.getwindowsversion() >= (6, ):
				# No need to enable double buffering under Linux and Mac OS X.
				# Under Windows, enabling double buffering on the panel seems
				# to work best to reduce flicker.
				self.SetDoubleBuffered(True)
		set_icons(self)
		self.Bind(wx.EVT_CLOSE, self.OnClose, self)
		if not pos:
			self.Bind(wx.EVT_MOVE, self.OnMove, self)
		self.timer = BetterTimer(self)
		self.Bind(EVT_BETTERTIMER, handler or self.OnTimer, self.timer)
		
		self.indeterminate = True
		self.keepGoing = True
		self.skip = False
		self.paused = False
		self.progress_type = 0  # 0 = processing, 1 = measurement
		self.allow_close = allow_close

		margin = 12

		self.sizer0 = wx.BoxSizer(wx.VERTICAL)
		if fancy:
			self.sizerH = wx.BoxSizer(wx.HORIZONTAL)
			self.SetSizer(self.sizerH)
			self.animbmp = AnimatedBitmap(self, -1, size=(200, 200))
			self.sizerH.Add(self.animbmp, 0, flag=wx.ALIGN_CENTER_VERTICAL)
			self.sizerH.Add(self.sizer0, 1, flag=wx.EXPAND)
			try:
				audio.init()
			except Exception, exception:
				safe_print(exception)
			self.processor_sound = audio.Sound(get_data_path("theme/engine_hum_loop.wav"),
											   True)
			self.generator_sound = audio.Sound(get_data_path("theme/pulsing_loop.wav"),
											   True)
			self.sound = self.processor_sound
			self.indicator_sound = audio.Sound(get_data_path("theme/beep_boop.wav"))
			ProgressDialog.get_bitmaps(self.progress_type)
			sizer0flag = 0
		else:
			self.SetSizer(self.sizer0)
			sizer0flag = wx.LEFT
		self.sizer1 = wx.BoxSizer(wx.VERTICAL)
		self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer0.Add(self.sizer1, flag = wx.ALIGN_LEFT | wx.TOP | wx.RIGHT |
						sizer0flag, border=margin)
		self.buttonpanel = wx.Panel(self)
		self.buttonpanel.SetSizer(wx.BoxSizer(wx.VERTICAL))
		self.buttonpanel.Sizer.Add(self.sizer2, 1, flag=wx.ALIGN_RIGHT | wx.ALL, 
								   border=margin)
		if sys.platform == "win32" and not fancy:
			self.buttonpanel_line = wx.Panel(self, size=(-1,1))
			self.buttonpanel_line.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT))
			self.sizer0.Add(self.buttonpanel_line, flag=wx.TOP | wx.EXPAND,
							border=margin)
			self.buttonpanel.SetBackgroundColour(bgcolor)
		elif fancy:
			self.buttonpanel.BackgroundColour = "#141414"
		self.sizer0.Add(self.buttonpanel, flag=wx.EXPAND)

		self.msg = wx.StaticText(self, -1, "", size=(-1, 74))
		self.sizer1.Add(self.msg, flag=wx.EXPAND | wx.BOTTOM, border=margin)
		
		self._fpprogress = 0.0
		if fancy:
			self.gauge = BetterPyGauge(self, wx.ID_ANY, range=maximum,
									   size=(-1, 4), pd=self)
			self.gauge.BackgroundColour = "#003366"
			self.gauge.SetBarGradients([("#0099CC", "#00CCFF"),
									    ("#0088BB", "#00BBEE"),
									    ("#0077AA", "#00AADD"),
									    ("#006699", "#0099CC"),
									    ("#0077AA", "#00AADD"),
									    ("#0088BB", "#00BBEE")])
			self.gauge.SetIndeterminateBarGradients([("#00CCFF", "#001144"),
													 ("#00BBEE", "#002255"),
													 ("#00AADD", "#003366"),
													 ("#0099CC", "#004477"),
													 ("#0088BB", "#005588"),
													 ("#0077AA", "#006699"),
													 ("#006699", "#0077AA"),
													 ("#005588", "#0088BB"),
													 ("#004477", "#0099CC"),
													 ("#003366", "#00AADD"),
													 ("#002255", "#00BBEE"),
													 ("#001144", "#00CCFF"),
													 ("#002255", "#00BBEE"),
													 ("#003366", "#00AADD"),
													 ("#004477", "#0099CC"),
													 ("#005588", "#0088BB"),
													 ("#006699", "#0077AA"),
													 ("#0077AA", "#006699"),
													 ("#0088BB", "#005588"),
													 ("#0099CC", "#004477"),
													 ("#00AADD", "#003366"),
													 ("#00BBEE", "#002255")])
			self.gauge.SetValue(0)
		else:
			gauge_style = wx.GA_HORIZONTAL
			if style & wx.PD_SMOOTH:
				gauge_style |= wx.GA_SMOOTH
			self.gauge = wx.Gauge(self, wx.ID_ANY, range=maximum, style=gauge_style)
		self.sizer1.Add(self.gauge, flag=wx.EXPAND | wx.BOTTOM, border=margin)
		
		if style & wx.PD_ELAPSED_TIME or style & wx.PD_REMAINING_TIME:
			self.sizer3 = wx.FlexGridSizer(0, 2, 0, margin)
			self.sizer1.Add(self.sizer3, flag=wx.ALIGN_LEFT)
		
		if style & wx.PD_ELAPSED_TIME:
			self.elapsed_time_label = wx.StaticText(self, -1,
													lang.getstr("elapsed_time"))
			self.elapsed_time_label.SetMaxFontSize(11)
			self.sizer3.Add(self.elapsed_time_label)
			self.elapsed_time = wx.StaticText(self, -1, "")
			self.elapsed_time.SetMaxFontSize(11)
			self.sizer3.Add(self.elapsed_time)
			self.elapsed_timer = BetterTimer(self)
			self.Bind(EVT_BETTERTIMER, self.elapsed_time_handler,
					  self.elapsed_timer)
		
		if style & wx.PD_REMAINING_TIME:
			self.remaining_time_label = wx.StaticText(self, -1,
													  lang.getstr("remaining_time"))
			self.remaining_time_label.SetMaxFontSize(11)
			self.sizer3.Add(self.remaining_time_label)
			self.remaining_time = wx.StaticText(self, -1, "")
			self.remaining_time.SetMaxFontSize(11)
			self.sizer3.Add(self.remaining_time)

		self.reset()

		if fancy:
			def buttoncls(parent, id, label):
				return FlatShadedButton(parent, id, None, label,
										fgcolour="#FFFFFF")
		else:
			buttoncls = wx.Button

		if style & wx.PD_CAN_ABORT:
			self.cancel = buttoncls(self.buttonpanel, wx.ID_ANY,
									lang.getstr("cancel"))
			self.sizer2.Add(self.cancel)
			self.Bind(wx.EVT_BUTTON, self.OnClose, id=self.cancel.GetId())

		self.pause_continue = buttoncls(self.buttonpanel, wx.ID_ANY,
										lang.getstr("continue"))
		self.sizer2.Prepend((margin, margin))
		self.sizer2.Prepend(self.pause_continue, flag=wx.LEFT, border=margin)
		self.Bind(wx.EVT_BUTTON, self.pause_continue_handler,
				  id=self.pause_continue.GetId())
		self.pause_continue.Show(pauseable)

		if fancy:
			bitmap = self.get_sound_on_off_btn_bitmap()
			self.sound_on_off_btn = FlatShadedButton(self.buttonpanel,
													 bitmap=bitmap,
													 fgcolour="#FFFFFF")
			self.sizer2.Prepend(self.sound_on_off_btn)
			self.sound_on_off_btn.Bind(wx.EVT_BUTTON,
									   self.play_sound_handler)

		self.buttonpanel.Layout()
		
		# Use an accelerator table for 0-9, a-z, numpad
		keycodes = range(48, 58) + range(97, 123) + numpad_keycodes
		self.id_to_keycode = {}
		for keycode in keycodes:
			self.id_to_keycode[wx.Window.NewControlId()] = keycode
		accels = []
		for id, keycode in self.id_to_keycode.iteritems():
			self.Bind(wx.EVT_MENU, keyhandler or self.key_handler, id=id)
			accels.append((wx.ACCEL_NORMAL, keycode, id))
		self.SetAcceleratorTable(wx.AcceleratorTable(accels))
		
		self.msg.SetMaxFontSize(11)
		self.msg.Label = "\n".join(["E" * 80] * 4)
		w, h = self.msg.Size
		self.msg.WindowStyle |= wx.ST_NO_AUTORESIZE
		self.msg.SetMinSize((w, h))
		self.msg.SetSize((w, h))
		self.Sizer.SetSizeHints(self)
		self.Sizer.Layout()
		self.msg.SetLabel(msg.replace("&", "&&"))

		self.pause_continue.Label = lang.getstr("pause")
		
		self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self)

		if "gtk3" in wx.PlatformInfo:
			# Fix background color not working for panels under GTK3
			self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
		
		# set position
		self.place(pos)
		
		if start_timer:
			self.start_timer()
		
		self.Show()
		if style & wx.PD_APP_MODAL:
			self.MakeModal()

	if "gtk3" in wx.PlatformInfo:
		OnEraseBackground = wx_Panel.__dict__["OnEraseBackground"]
	
	def MakeModal(self, modal=True):
		# wxPython 3.0 deprecates MakeModal, use a replacement implementation
		# based on http://wxpython.org/Phoenix/docs/html/MigrationGuide.html
		if modal and not hasattr(self, '_disabler'):
			self._disabler = BetterWindowDisabler(self)
		if not modal and hasattr(self, '_disabler'):
			del self._disabler
	
	def OnClose(self, event):
		self.keepGoing = False
		if hasattr(self, "cancel"):
			self.cancel.Disable()
		self.pause_continue.Disable()
		if not self.timer.IsRunning() and self.allow_close:
			self.Destroy()
		elif self.gauge.GetValue() == self.gauge.GetRange():
			event.Skip()
	
	def OnDestroy(self, event):
		self.stop_timer()
		del self.timer
		if hasattr(wx.Window, "UnreserveControlId"):
			for id in self.id_to_keycode.iterkeys():
				if id < 0:
					try:
						wx.Window.UnreserveControlId(id)
					except wx.wxAssertionError, exception:
						safe_print(exception)
		
	def OnMove(self, event):
		if self.IsShownOnScreen() and not self.IsIconized() and \
		   (not self.GetParent() or
		    not self.GetParent().IsShownOnScreen()):
			prev_x = getcfg("position.progress.x")
			prev_y = getcfg("position.progress.y")
			x, y = self.GetScreenPosition()
			if x != prev_x or y != prev_y:
				setcfg("position.progress.x", x)
				setcfg("position.progress.y", y)
	
	def OnTimer(self, event):
		if self.keepGoing:
			if not self.pause_continue.IsEnabled():
				self.set_progress_type(int(not self.progress_type))
			self.pause_continue.Enable()
			if self.paused:
				return
			if self._fpprogress < self.gauge.GetRange():
				self.UpdateProgress(self._fpprogress + self.gauge.GetRange() / 1000.0)
			else:
				self.stop_timer(False)
				self.UpdateProgress(self.gauge.GetRange(),
							"Finished. You may now close this window.")
				self.pause_continue.Disable()
				if hasattr(self, "cancel"):
					self.cancel.Disable()
		else:
			self.Pulse("Aborting...")
			if not hasattr(self, "delayed_stop"):
				self.delayed_stop = wx.CallLater(3000, self.stop_timer, False)
				wx.CallLater(3000, self.Pulse, 
							 "Aborted. You may now close this window.")

	def GetValue(self):
		return self._fpprogress

	def SetValue(self, value):
		self.UpdateProgress(value)

	Value = property(GetValue, SetValue)
	
	def Pulse(self, msg=None):
		if msg and msg != self.msg.Label:
			self.msg.SetLabel(msg)
			self.msg.Wrap(self.msg.ContainingSizer.Size[0])
			self.msg.Refresh()
		if getattr(self, "time2", 0):
			self.time2 = 0
			if not self.time3:
				self.time3 = time()
		if not self.indeterminate:
			self.indeterminate = True
			if hasattr(self, "remaining_time"):
				self.remaining_time.Label = u"––:––:––"
			if self.taskbar and not self.paused:
				self.taskbar.set_progress_state(taskbar.TBPF_INDETERMINATE)
		self.gauge.Pulse()
		return self.keepGoing, self.skip
	
	def Resume(self):
		self.keepGoing = True
		if hasattr(self, "sound_on_off_btn"):
			self.set_sound_on_off_btn_bitmap()
		if hasattr(self, "cancel"):
			self.cancel.Enable()
		if self.paused:
			self.pause_continue_handler()
		else:
			self.pause_continue.Enable()
	
	def UpdateProgress(self, value, msg=None):
		self.indeterminate = False
		if msg and msg != self.msg.Label:
			self.msg.SetLabel(msg)
			self.msg.Wrap(self.msg.ContainingSizer.Size[0])
			self.msg.Refresh()
		prev_value = self._fpprogress
		if value == prev_value:
			return self.keepGoing, self.skip
		self._fpprogress = value
		value = int(round(value))
		if hasattr(self, "time2"):
			t = time()
			if not self.time2:
				if value < self.gauge.GetValue():
					# Reset
					self.time2 = t
				else:
					# Continue
					self.time4 += time() - self.time3
					self.time2 = self.time + self.time4
					self.time3 = 0
		if (getcfg("measurement.play_sound") and
			self._fpprogress < prev_value and
			hasattr(self, "indicator_sound") and
			not self.indicator_sound.is_playing):
			self.indicator_sound.safe_play()
		if (isinstance(self.gauge, BetterPyGauge) and
			self._style & wx.PD_SMOOTH):
			if self._fpprogress >= prev_value:
				update_value = abs(self._fpprogress - prev_value)
				if update_value:
					# Higher ms = smoother animation, but potentially
					# increased "lag"
					ms = 450 + 50 * update_value
					self.gauge.Update(self._fpprogress, ms)
			else:
				self.gauge.Update(self._fpprogress, 50)
		else:
			self.gauge.SetValue(value)
			if self.taskbar:
				self.taskbar.set_progress_value(value)
		return self.keepGoing, self.skip
	
	UpdatePulse = Pulse

	def anim_fadein(self):
		bitmaps = ProgressDialog.get_bitmaps(self.progress_type)
		if self.progress_type == 0:
			# Processing
			range = (60, 68)
			loop = True
		elif self.progress_type == 1:
			# Measuring
			range = (0, 9)
			loop = False
		else:
			# Generating test patches
			range = (27, 36)
			loop = True
		self.animbmp.SetBitmaps(bitmaps, range=range, loop=loop)
		if self.progress_type == 1:
			self.animbmp.frame = 4
		wx.CallLater(50, lambda: self and self.IsShown() and
								  self.animbmp.Play(24))

	def sound_fadein(self):
		if getcfg("measurement.play_sound"):
			wx.CallLater(50, lambda: self and self.IsShown() and
									 self.sound.safe_fade(self.sound_get_delay(),
														  True))

	def anim_fadeout(self):
		self.animbmp.loop = False
		self.animbmp.range = self.animbmp.range[0], -1

	def sound_fadeout(self):
		if self.sound.is_playing:
			self.sound.safe_stop(self.sound_get_delay())

	def sound_get_delay(self):
		if self.sound is self.processor_sound:
			return 3000
		else:
			return 1500
	
	def elapsed_time_handler(self, event):
		self.elapsed_time.Label = strftime("%H:%M:%S",
										   gmtime(time() - self.time))
		value = self._fpprogress
		if getattr(self, "time2", 0) and value:
			t = time()
			if self.time2 < t:
				remaining = ((t - self.time2) / value *
								  (self.gauge.GetRange() - value))
				if remaining >= 0:
					self.remaining_time.Label = strftime("%H:%M:%S",
														 gmtime(remaining))

	@staticmethod
	def get_bitmaps(progress_type=0):
		if progress_type in ProgressDialog.bitmaps:
			bitmaps = ProgressDialog.bitmaps[progress_type]
		else:
			bitmaps = ProgressDialog.bitmaps[progress_type] = []
			if progress_type == 0:
				# Animation for processing
				for pth in get_data_path("theme/shutter_anim", r"\.png$") or []:
					im = wx.Image(pth)
					if im.IsOk():
						bitmaps.append(im)
				for pth in get_data_path("theme/jet_anim", r"\.png$") or []:
					im = wx.Image(pth)
					if not im.IsOk():
						continue
					# Blend red
					im = im.AdjustChannels(1, .25, 0)
					# Adjust for background
					im.AdjustMinMax(1.0 / 255 * 0x14)
					bitmaps.append(im)
				# Needs to be exactly 17 images
				if bitmaps and len(bitmaps) == 17:
					for i in xrange(7):
						bitmaps.extend(bitmaps[9:17])
					bitmaps.extend(bitmaps[9:13])
					# Fade in
					for i, im in enumerate(bitmaps[9:19]):
						bg = bitmaps[8].ConvertToBitmap()
						im = im.AdjustChannels(1, 1, 1, i / 10.0)
						bg.Blend(im.ConvertToBitmap(), 0, 0)
						bitmaps[i + 9] = bg
					for i, im in enumerate(bitmaps[:9]):
						bitmaps[i] = im.ConvertToBitmap()
					# Normal
					for i in xrange(41):
						im = bitmaps[i + 19].Copy()
						im.RotateHue(.05 * (i / 50.0))
						bitmaps[i + 19] = im.ConvertToBitmap()
					for i, im in enumerate(bitmaps[60:]):
						im = im.Copy()
						im.RotateHue(.05)
						bitmaps[i + 60] = im.ConvertToBitmap()
					# Fade out
					bitmaps.extend(reversed(bitmaps[:60]))
				else:
					bitmaps = ProgressDialog.bitmaps[progress_type] = []
			elif progress_type == 1:
				# Animation for measurements
				for i, pth in enumerate(get_data_path("theme/shutter_anim", r"\.png$") or []):
					if i < 5:
						bmp = getbitmap(os.path.splitext(pth)[0])
						bitmaps.insert(0, bmp)
				if bitmaps and len(bitmaps) == 5:
					bitmaps.extend(reversed(bitmaps[:5]))
					bitmaps.extend(bitmaps[:5])
			else:
				# Animation for generating test patches
				for pth in get_data_path("theme/patch_anim", r"\.png$") or []:
					im = wx.Image(pth)
					if im.IsOk():
						bitmaps.append(im)
				# Needs to be exactly 9 images
				if bitmaps and len(bitmaps) == 9:
					for i in xrange(3):
						bitmaps.extend(bitmaps[:9])
					# Fade in
					for i, im in enumerate(bitmaps[:27]):
						im = im.AdjustChannels(1, 1, 1, i / 27.0)
						bitmaps[i] = im.ConvertToBitmap()
					# Normal
					for i, im in enumerate(bitmaps[27:]):
						bitmaps[i + 27] = im.ConvertToBitmap()
					# Fade out
					for i in xrange(3):
						bitmaps.extend(bitmaps[27:36])
					for i, bmp in enumerate(bitmaps[36:]):
						im = bmp.ConvertToImage().AdjustChannels(1, 1, 1, 1 - i / 26.0)
						bitmaps[i + 36] = im.ConvertToBitmap()
				else:
					bitmaps = ProgressDialog.bitmaps[progress_type] = []
		return bitmaps
	
	def key_handler(self, event):
		pass

	def pause_continue_handler(self, event=None):
		if not self.pause_continue.IsShown():
			return
		self.paused = not self.paused
		if self.paused:
			self.pause_continue.Label = lang.getstr("continue")
			if self.taskbar:
				self.taskbar.set_progress_state(taskbar.TBPF_PAUSED)
		else:
			self.pause_continue.Label = lang.getstr("pause")
			if self.taskbar:
				if self.indeterminate:
					state = taskbar.TBPF_INDETERMINATE
				else:
					state = taskbar.TBPF_NORMAL
				self.taskbar.set_progress_state(state)
		self.pause_continue.Enable(not event)
		self.Layout()
		if getattr(self, "time2", 0):
			self.time2 = 0
			if not self.time3:
				self.time3 = time()

	def place(self, pos=None):
		""" Set position """
		placed = False
		if pos:
			x, y = pos
		else:
			if self.Parent and self.Parent.IsShownOnScreen():
				self.Center()
				placed = True
			else:
				x = getcfg("position.progress.x")
				y = getcfg("position.progress.y")
		if not placed:
			self.SetSaneGeometry(x, y)
	
	def play_sound_handler(self, event):
		setcfg("measurement.play_sound",
			   int(not(bool(getcfg("measurement.play_sound")))))
		if getcfg("measurement.play_sound"):
			if (self.progress_type in (0, 2) and self.keepGoing and
				self._fpprogress < self.gauge.GetRange()):
				self.sound.safe_play()
		else:
			if self.sound.is_playing:
				self.sound.safe_stop()
		self.set_sound_on_off_btn_bitmap()

	def get_sound_on_off_btn_bitmap(self):
		if getcfg("measurement.play_sound"):
			bitmap = geticon(16, "sound_volume_full")
		else:
			bitmap = geticon(16, "sound_off")
		im = bitmap.ConvertToImage()
		im.AdjustMinMax(maxvalue=1.5)
		return im.ConvertToBitmap()

	def set_sound_on_off_btn_bitmap(self):
		bitmap = self.get_sound_on_off_btn_bitmap()
		self.sound_on_off_btn._bitmap = bitmap

	def reset(self):
		self._fpprogress = 0
		self.gauge.SetValue(0)
		self.time = time()
		if hasattr(self, "elapsed_time"):
			self.elapsed_time_handler(None)
		if hasattr(self, "remaining_time"):
			self.time2 = 0
			self.time3 = self.time
			self.time4 = 0
			self.remaining_time.Label = u"––:––:––"

	def set_progress_type(self, progress_type):
		if progress_type != self.progress_type:
			if self.progress_type == 0:
				# Processing
				delay = 4000
			elif self.progress_type == 1:
				# Measuring
				delay = 500
			else:
				# Generating test patches
				delay = 2000
			if hasattr(self, "animbmp"):
				self.anim_fadeout()
				wx.CallLater(delay, lambda: self and
											self.progress_type == progress_type and
											self.anim_fadein())
			if hasattr(self, "sound"):
				self.sound_fadeout()
				if progress_type in (0, 2):
					self.set_sound(progress_type)
					wx.CallLater(delay, lambda: self and
												self.progress_type == progress_type and
												self.sound_fadein())
			self.progress_type = progress_type

	def set_sound(self, progress_type=0):
		if progress_type == 0 and hasattr(self, "processor_sound"):
			self.sound = self.processor_sound
		elif hasattr(self, "generator_sound"):
			self.sound = self.generator_sound

	def start_timer(self, ms=75):
		self.timer.Start(ms)
		if isinstance(self.gauge, BetterPyGauge) and not self.gauge.IsRunning():
			self.gauge.Start()
		if hasattr(self, "elapsed_timer"):
			self.elapsed_timer.Start(1000)
		if hasattr(self, "animbmp"):
			self.anim_fadein()
		if hasattr(self, "sound") and self.progress_type in (0, 2):
			self.set_sound(self.progress_type)
			self.sound_fadein()
		if taskbar:
			if self.Parent and self.Parent.IsShownOnScreen():
				taskbarframe = self.Parent
			else:
				taskbarframe = self
			self.taskbar = taskbar.Taskbar(taskbarframe, self.gauge.GetRange())
			self.taskbar.set_progress_state(taskbar.TBPF_INDETERMINATE)
	
	def stop_timer(self, immediate=True):
		if self.taskbar:
			self.taskbar.set_progress_state(taskbar.TBPF_NOPROGRESS)
		if not self.timer.IsRunning():
			return
		self.timer.Stop()
		if isinstance(self.gauge, BetterPyGauge) and immediate:
			self.gauge.Stop()
		if hasattr(self, "elapsed_timer"):
			self.elapsed_timer.Stop()
		if hasattr(self, "animbmp"):
			if immediate:
				self.animbmp.Stop()
			elif self.animbmp.loop or self.animbmp.range[1] != -1:
				self.anim_fadeout()
		if hasattr(self, "sound"):
			self.sound_fadeout()


class FancyProgressDialog(ProgressDialog):

	def __init__(self, *args, **kwargs):
		kwargs["fancy"] = True
		ProgressDialog.__init__(self, *args, **kwargs)


class SimpleBook(labelbook.FlatBookBase):

	def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
				 style=0, agwStyle=0, name="SimpleBook"):
		
		labelbook.FlatBookBase.__init__(self, parent, id, pos, size, style, agwStyle, name)
		
		self._pages = self.CreateImageContainer()

		# Label book specific initialization
		self._mainSizer = wx.BoxSizer(wx.HORIZONTAL)
		self.SetSizer(self._mainSizer)
		
		if "gtk3" in wx.PlatformInfo:
			# Fix background color not working for panels under GTK3
			self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

	if "gtk3" in wx.PlatformInfo:
		OnEraseBackground = wx_Panel.__dict__["OnEraseBackground"]

	def CreateImageContainer(self):
		""" Creates the image container (LabelContainer) class for L{FlatImageBook}. """

		return labelbook.ImageContainerBase(self, wx.ID_ANY, agwStyle=self.GetAGWWindowStyleFlag())


class SimpleTerminal(InvincibleFrame):
	
	""" A simple terminal-like window. """

	def __init__(self, parent=None, id=-1, title=appname, handler=None, 
				 keyhandler=None, start_timer=True, pos=wx.DefaultPosition,
				 size=wx.DefaultSize, consolestyle=wx.TE_CHARWRAP |
												   wx.TE_MULTILINE |
												   wx.TE_READONLY | wx.VSCROLL |
												   wx.NO_BORDER, show=True,
				 name="simpleterminal"):
		if pos == wx.DefaultPosition:
			pos = getcfg("position.progress.x"), getcfg("position.progress.y")
		if size == wx.DefaultSize:
			size = getcfg("size.progress.w"), getcfg("size.progress.h")
		InvincibleFrame.__init__(self, parent, id, title, pos=pos,
								 style=wx.DEFAULT_FRAME_STYLE, name=name)
		
		self.Bind(wx.EVT_CLOSE, self.OnClose, self)
		self.Bind(wx.EVT_MOVE, self.OnMove, self)
		self.timer = BetterTimer(self)
		if handler:
			self.Bind(EVT_BETTERTIMER, handler, self.timer)
		
		self.panel = wx.Panel(self, -1)
		self.sizer = wx.BoxSizer(wx.VERTICAL)
		self.panel.SetSizer(self.sizer)
		self.console = wx.TextCtrl(self.panel, -1, "", style=consolestyle)
		self.console.SetBackgroundColour("#272727")
		self.console.SetForegroundColour("#808080")
		if u"phoenix" in wx.PlatformInfo:
			kwarg = "faceName"
		else:
			kwarg = "face"
		if sys.platform == "win32":
			font = wx.Font(9, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, 
													  wx.FONTWEIGHT_NORMAL,
													  **{kwarg: "Consolas"})
		elif sys.platform == "darwin":
			font = wx.Font(11, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, 
													   wx.FONTWEIGHT_NORMAL,
													   **{kwarg: "Monaco"})
		else:
			font = wx.Font(11, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, 
													   wx.FONTWEIGHT_NORMAL)
		self.console.SetFont(font)
		self.sizer.Add(self.console, 1, flag=wx.ALL | wx.EXPAND, border=0)
		
		if sys.platform == "win32":
			# Mac: TextCtrls don't receive EVT_KEY_DOWN
			self.console.Bind(wx.EVT_KEY_DOWN, keyhandler or self.key_handler, 
							  self.console)
		else:
			# Windows: EVT_CHAR_HOOK only receives "special" keys e.g. ESC, Tab
			self.Bind(wx.EVT_CHAR_HOOK, keyhandler or self.key_handler)
		
		# set size
		text_extent = self.console.GetTextExtent(" ")
		vscroll_w = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
		w, h = (text_extent[0] * 80 + vscroll_w + 4, 
				text_extent[1] * 24)
		self.console.SetMinSize((w, h))
		w, h = max(size[0], w), max(size[1], h)
		self.console.SetSize((w, h))
		self.sizer.SetSizeHints(self)
		self.sizer.Layout()
		
		# set position	
		placed = False
		if parent:
			if parent.IsShownOnScreen():
				self.Center()
				placed = True
			else:
				x = pos[0] or parent.GetScreenPosition()[0]
				y = pos[1] or parent.GetScreenPosition()[1]
		else:
			x, y = pos
		if not placed:
			self.SetSaneGeometry(x, y, w, h)
		
		self.lastmsg = ""
		self.keepGoing = True
		
		self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self)
		
		if start_timer:
			self.start_timer()
		
		if show:
			self.Show()
			self.console.SetFocus()
	
	def EndModal(self, returncode=wx.ID_OK):
		return returncode
	
	def MakeModal(self, makemodal=False):
		pass
	
	def OnClose(self, event):
		if not self.timer.IsRunning():
			self.Destroy()
		else:
			self.keepGoing = False
	
	def OnDestroy(self, event):
		self.stop_timer()
		del self.timer
		
	def OnMove(self, event):
		if self.IsShownOnScreen() and not self.IsIconized() and \
		   (not self.GetParent() or
		    not self.GetParent().IsShownOnScreen()):
			prev_x = getcfg("position.progress.x")
			prev_y = getcfg("position.progress.y")
			x, y = self.GetScreenPosition()
			if x != prev_x or y != prev_y:
				setcfg("position.progress.x", x)
				setcfg("position.progress.y", y)
	
	def Pulse(self, msg=""):
		self.lastmsg = msg
		return self.keepGoing, False
	
	def Resume(self):
		self.keepGoing = True
	
	def UpdateProgress(self, value, msg=""):
		return self.Pulse(msg)
	
	def UpdatePulse(self, msg=""):
		return self.Pulse(msg)
	
	def add_text(self, txt):
		pos = self.console.GetLastPosition()
		if txt.startswith("\r"):
			txt = txt.lstrip("\r")
			numlines = self.console.GetNumberOfLines()
			start = pos - self.console.GetLineLength(numlines - 1)
			self.console.Replace(start, pos, txt)
		else:
			self.console.AppendText(txt)
	
	def flush(self):
		pass
	
	def isatty(self):
		return True
	
	def key_handler(self, event):
		pass
	
	def start_timer(self, ms=50):
		self.timer.Start(ms)
	
	def stop_timer(self):
		self.timer.Stop()
	
	def write(self, txt):
		wx.CallAfter(self.add_text, txt)


class TabButton(PlateButton):

	def __init__(self, *args, **kwargs):
		from config import get_default_dpi, getcfg
		self.dpiscale = max(getcfg("app.dpi") / get_default_dpi(), 1.0)
		PlateButton.__init__(self, *args, **kwargs)
		self.Unbind(wx.EVT_PAINT)
		self.Bind(wx.EVT_PAINT, self.OnPaint)

	def DoGetBestSize(self):
		"""Calculate the best size of the button
		
		:return: :class:`Size`

		"""
		width = 0
		height = 6 * self.dpiscale
		if self.Label:
			# NOTE: Should measure with a GraphicsContext to get right
			#       size, but due to random segfaults on linux special
			#       handling is done in the drawing instead...
			lsize = self.GetFullTextExtent(self.Label)
			width += lsize[0]
			height += lsize[1]
			
		if self._bmp['enable'] is not None:
			bsize = self._bmp['enable'].Size
			width += (bsize[0] + 10 * self.dpiscale)
			if height <= bsize[1]:
				height = bsize[1] + 6 * self.dpiscale
			else:
				height += 3 * self.dpiscale
		else:
			width += 10 * self.dpiscale

		if self._menu is not None or self._style & platebtn.PB_STYLE_DROPARROW:
			width += 12 * self.dpiscale

		height += 16 * self.dpiscale  # Tab hilite

		best = wx.Size(width, height)
		self.CacheBestSize(best)
		return best

	def OnFocus(self, evt):
		"""Set the visual focus state if need be"""
		if self._pressed:
			# Skip focus over to next control if it came from another control
			if isinstance(evt, wx.FocusEvent) and isinstance(evt.GetWindow(),
															 wx.Control):
				self.Navigate(int(not wx.GetKeyState(wx.WXK_SHIFT))) or evt.Skip()
		elif self._state['cur'] == platebtn.PLATE_NORMAL:
			self._SetState(platebtn.PLATE_HIGHLIGHT)

	def OnKillFocus(self, evt):
		"""Set the visual state back to normal when focus is lost
		unless the control is currently in a pressed state.

		"""
		if self._pressed:
			self._SetState(platebtn.PLATE_PRESSED)
		else:
			self._SetState(platebtn.PLATE_NORMAL)

	def OnLeftDown(self, evt):
		"""Depending on the click position will
		show the popup menu if one has been set.

		"""
		pos = evt.GetPosition()
		size = self.GetSize()
		if pos[0] >= size[0] - 16:
			if self._menu is not None:
				self.ShowMenu()
			elif self._style & platebtn.PB_STYLE_DROPARROW:
				event = PlateBtnDropArrowPressed()
				event.SetEventObject(self)
				self.EventHandler.ProcessEvent(event)
		
		if not self._pressed:
			self.SetFocus()

	def OnLeftUp(self, evt):
		"""Post a button event.

		:param `evt`: :class:`MouseEvent`

		"""
		self._SetState(platebtn.PLATE_PRESSED)
		PlateButton.OnLeftUp(self, evt)

	def OnKeyUp(self, evt):
		"""Execute a single button press action when the Return key is pressed
		and this control has the focus.
		
		:param `evt`: wx.EVT_KEY_UP

		"""
		if evt.GetKeyCode() == wx.WXK_SPACE:
			self._SetState(platebtn.PLATE_PRESSED)
			self._PostEvent()
		else:
			evt.Skip()

	def OnPaint(self, evt):
		self.__DrawButton()

	def __DrawBitmap(self, gc):
		"""Draw the bitmap if one has been set

		:param GCDC `gc`: :class:`GCDC` to draw with
		:return: x cordinate to draw text at

		"""
		if self.IsEnabled():
			bmp = self._bmp['enable']
		else:
			bmp = self._bmp['disable']

		xpos = 0
		if bmp is not None and bmp.IsOk():
			bw, bh = bmp.GetSize()
			ypos = (self.GetSize()[1] - bh) // 2
			ypos -= 8  # Tab hilite
			gc.DrawBitmap(bmp, xpos, ypos, bmp.GetMask() != None)
			return bw + xpos
		else:
			return xpos

	def __DrawHighlight(self, gc, width, height):
		"""Draw the main highlight/pressed state

		:param GCDC `gc`: :class:`GCDC` to draw with
		:param int `width`: width of highlight
		:param int `height`: height of highlight

		"""
		if ((self._state['cur'] == platebtn.PLATE_HIGHLIGHT or
			 self.HasFocus()) and not self._pressed and not get_dialogs(True)):
			if sys.platform == "darwin":
				# Use Sierra-like color scheme
				color = wx.Colour(*gamma_encode(0, 105, 217))
			else:
				color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
		elif self._pressed:
			color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
		else:
			return False

		if self._style & platebtn.PB_STYLE_SQUARE:
			rad = 0
		else:
			rad = (height - 3) / 2

		gc.SetPen(wx.TRANSPARENT_PEN)

		if self._style & platebtn.PB_STYLE_GRADIENT:
			gc.SetBrush(wx.TRANSPARENT_BRUSH)
			rgc = gc.GetGraphicsContext()
			brush = rgc.CreateLinearGradientBrush(0, 1, 0, height,
												  color, platebtn.AdjustAlpha(color, 55))
			rgc.SetBrush(brush)
		else:
			gc.SetBrush(wx.Brush(color))

		##gc.DrawRoundedRectangle(1, 1, width - 2, height - 2, rad)
		gc.DrawRectangle(0, height + 10 * self.dpiscale, width, 8 * self.dpiscale)
		return True

	def __DrawButton(self):
		"""Draw the button"""
		# TODO using a buffered paintdc on windows with the nobg style
		#      causes lots of weird drawing. So currently the use of a
		#      buffered dc is dissabled for this style.
		if platebtn.PB_STYLE_NOBG & self._style:
			dc = wx.PaintDC(self)
		else:
			dc = wx.AutoBufferedPaintDCFactory(self)
			dc.SetBackground(wx.Brush(self.Parent.BackgroundColour))
			dc.Clear()

		gc = wx.GCDC(dc)

		# Setup
		gc.SetFont(adjust_font_size_for_gcdc(self.GetFont()))
		gc.SetBackgroundMode(wx.TRANSPARENT)

		# Calc Object Positions
		width, height = self.GetSize()
		height -= 16  # Tab hilite
		# Note: Using self.GetTextExtent instead of gc.GetTextExtent seems
		# to fix sporadic segfaults with wxPython Phoenix up to 4.0.0a2
		# under Windows (fixed in 4.0.0a3), but self.GetTextExtent is NOT
		# an equivalent replacement for gc.GetTextExtent.
		tw, th = gc.GetTextExtent(self.Label)
		txt_y = max((height - th) // 2, 1)
		height += 16
		height -= 16 * self.dpiscale  # Tab hilite

		# The background needs some help to look transparent on
		# on Gtk and Windows
		if wx.Platform in ['__WXGTK__', '__WXMSW__']:
			gc.SetBrush(self.GetBackgroundBrush(gc))
			gc.SetPen(wx.TRANSPARENT_PEN)
			gc.DrawRectangle(0, 0, width, height)

		gc.SetBrush(wx.TRANSPARENT_BRUSH)

		if (self._state['cur'] == platebtn.PLATE_HIGHLIGHT or
			self._pressed or self.HasFocus()) and self.IsEnabled():
			hl = self.__DrawHighlight(gc, width, height)
		else:
			hl = False

		if hl:
			txt_c = self._color['htxt']
		else:
			if self.IsEnabled():
				txt_c = self.GetForegroundColour()
			else:
				txt_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)

		gc.SetTextForeground(txt_c)

		# Draw bitmap and text
		txt_x = self.__DrawBitmap(gc)
		t_x = max((width - tw - (txt_x + 8 * self.dpiscale)) // 2,
				  txt_x + 8 * self.dpiscale)
		gc.DrawText(self.Label, t_x, txt_y)
		##self.__DrawDropArrow(gc, width - 10, (height // 2) - 2)


class TaskBarNotification(wx.Frame):

	"""
	A popup window in a visual style similar to Win10 'toast' notifications.
	
	It will be shown next to the task bar tray, which makes it a possible
	replacement for 'balloon'-style notifications.
	The viewing/hiding animation is implemented as a fade effect.
	
	"""

	def __init__(self, icon=None, title="Notification", text="", parent=None,
				 pos=wx.DefaultPosition, timeout=-1):
		wx.Frame.__init__(self, parent, -1, style=wx.FRAME_NO_TASKBAR |
												wx.NO_BORDER |
												wx.STAY_ON_TOP,
						  name="TaskBarNotification")
		self.SetTransparent(0)
		border = wx.Panel(self)
		if sys.platform == "win32" and sys.getwindowsversion() >= (6, ):
			border.SetDoubleBuffered(True)
		border.BackgroundColour = "#484848"
		border.Sizer = wx.BoxSizer(wx.HORIZONTAL)
		panel = wx.Panel(border)
		if sys.platform == "win32" and sys.getwindowsversion() >= (6, ):
			panel.SetDoubleBuffered(True)
		border.Sizer.Add(panel, flag=wx.ALL, border=1)
		panel.Bind(wx.EVT_LEFT_DOWN, lambda event: self.fade("out"))
		panel.BackgroundColour = "#1F1F1F"
		panel.ForegroundColour = "#A5A5A5"
		panel.Sizer = wx.BoxSizer(wx.HORIZONTAL)
		if icon:
			icon = wx.StaticBitmap(panel, -1, icon)
			icon.Bind(wx.EVT_LEFT_DOWN, lambda event: self.fade("out"))
			panel.Sizer.Add(icon, flag=wx.TOP | wx.BOTTOM | wx.LEFT, border=12)
		sizer = wx.BoxSizer(wx.VERTICAL)
		panel.Sizer.Add(sizer, flag=wx.ALL, border=12)
		title = wx.StaticText(panel, -1, title)
		title.SetMaxFontSize(11)
		title.Bind(wx.EVT_LEFT_DOWN, lambda event: self.fade("out"))
		title.ForegroundColour = wx.WHITE
		font = title.Font
		font.Weight = wx.BOLD
		title.Font = font
		sizer.Add(title)
		msg = wx.StaticText(panel, -1, text)
		msg.SetMaxFontSize(11)
		msg.Bind(wx.EVT_LEFT_DOWN, lambda event: self.fade("out"))
		sizer.Add(msg)
		close = wx.BitmapButton(panel, -1,
								config.getbitmap("theme/x-2px-12x12-999"),
								style=wx.NO_BORDER)
		close.BackgroundColour = panel.BackgroundColour
		close.Bind(wx.EVT_BUTTON, lambda event: self.fade("out"))
		set_bitmap_labels(close, focus=True)
		panel.Sizer.Add(close, flag=wx.TOP | wx.RIGHT | wx.BOTTOM,
						border=12)
		border.Sizer.SetSizeHints(self)
		border.Sizer.Layout()
		opos = pos
		display = self.GetDisplay()
		if parent:
			client_area = parent.ClientRect
		else:
			client_area = display.ClientArea
		geometry = display.Geometry
		# Determine tray position so we can show our popup
		# next to it
		if client_area[0] != geometry[0]:
			# Task bar is on the left, tray is in bottom left
			pos = [geometry[0], geometry[1] + geometry[3]]
		elif (client_area[0] == geometry[0] and
			  client_area[2] < geometry[2]):
			# Task bar is on the right, tray is in bottom right
			pos = [geometry[0] + geometry[2], geometry[1] + geometry[3]]
		elif client_area[1] != geometry[1]:
			# Task bar is at the top, tray is in top right
			pos = [geometry[0] + geometry[2], geometry[1]]
		elif (client_area[1] == geometry[1] and
			  client_area[3] < geometry[3]):
			# Task bar is at the bottom, tray is in bottom right
			pos = [geometry[0] + geometry[2], geometry[1] + geometry[3]]
		else:
			# Task bar is probably set to auto-hide, so we cannot use client
			# area to determine its position. Use mouse position instead.
			mousepos = wx.GetMousePosition()
			if mousepos[0] < (geometry[0] + geometry[2]) / 2.0:
				# Assume task bar is on the left, tray is in bottom left
				pos = [geometry[0], geometry[1] + geometry[3]]
			elif (mousepos[0] > (geometry[0] + geometry[2]) / 2.0 and
				  mousepos[1] < (geometry[1] + geometry[3]) / 2.0):
				# Assume task bar is at the top, tray is in top right
				pos = [geometry[0] + geometry[2], geometry[1]]
			else:
				# Assume task tray is in bottom right
				pos = [geometry[0] + geometry[2], geometry[1] + geometry[3]]
		if pos[0] <= client_area[0]:
			pos[0] = client_area[0] + 12
		if pos[1] <= client_area[1]:
			pos[1] = client_area[1] + 12
		if pos[0] + self.Size[0] >= client_area[0] + client_area[2]:
			pos[0] = client_area[0] + client_area[2] - self.Size[0] - 12
		if pos[1] + self.Size[1] >= client_area[1] + client_area[3]:
			pos[1] = client_area[1] + client_area[3] - self.Size[1] - 12
		for i in (0, 1):
			if opos[i] != -1:
				pos[i] = opos[i]
		self.SetPosition(pos)
		if timeout == -1:
			timeout = 6250
		if timeout:
			self._fadeout_timer = wx.CallLater(timeout, lambda: self and
																self.fade("out"))
		if hasattr(panel, "SetFocusIgnoringChildren"):
			panel.SetFocusIgnoringChildren()
		self.Show()
		self.fade()

	def fade(self, direction="in", i=1):
		if (getattr(self, "_fade_timer", None) and
			self._fade_timer.IsRunning()):
			self._fade_timer.Stop()
			i = self._fade_timer_i
		if direction != "in":
			if (hasattr(self, "_fadeout_timer") and
				self._fadeout_timer.IsRunning()):
				self._fadeout_timer.Stop()
			t = 10 - i
		else:
			t = i
		if not self:
			return
		self.SetTransparent(int(t * 25.5))
		if i < 10:
			self._fade_timer_i = i
			self._fade_timer = wx.CallLater(1, self.fade, direction, i + 1)
		elif direction != "in" and self:
			self.Destroy()


class TooltipWindow(InvincibleFrame):

	""" A tooltip-style window """
	
	def __init__(self, parent=None, id=-1, title=appname, msg="", cols=1,
				 bitmap=None, pos=(-1, -1), size=(400, -1), 
				 style=(wx.DEFAULT_FRAME_STYLE | wx.FRAME_TOOL_WINDOW |
					    wx.FRAME_FLOAT_ON_PARENT) &
					   ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX), wrap=70,
				 use_header=True, show=True, scrolled=False):
		scale = getcfg("app.dpi") / get_default_dpi()
		if scale > 1 and size == (400, -1):
			size = size[0] * scale, size[1]
		InvincibleFrame.__init__(self, parent, id, title, pos, size, style)
		self.SetPosition(pos)  # yes, this is needed
		set_icons(self)

		margin = 12
		
		if scrolled:
			self.panel = wx.ScrolledWindow(self, style=wx.VSCROLL)
			self.panel.SetScrollRate(2, 2)
		else:
			self.panel = wx.Panel(self)
		self.panel.BackgroundColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
		self.sizer0 = wx.BoxSizer(wx.VERTICAL)
		self.panel.SetSizer(self.sizer0)
		self.sizer1 = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer0.Add(self.sizer1, flag = wx.LEFT | wx.ALL, 
		   border = margin)

		if bitmap:
			self.bitmap = wx.StaticBitmap(self.panel, -1, bitmap)
			self.sizer1.Add(self.bitmap)

		self.sizer2 = wx.BoxSizer(wx.VERTICAL)
		self.sizer1.Add(self.sizer2)

		if not "<" in msg and wrap:
			msg = util_str.wrap(msg, wrap)
		msg = msg.splitlines()
		for i, line in enumerate(msg):
			if not line and use_header:
				# Use as header
				header = wx.StaticText(self.panel, -1, "\n".join(msg[:i + 1]))
				self.sizer2.Add(header, flag=wx.LEFT, border=margin)
				msg = msg[i + 1:]
				break
		self.sizer3 = wx.BoxSizer(wx.HORIZONTAL)
		self.sizer2.Add(self.sizer3, flag=wx.EXPAND)
		rowspercol = int(math.ceil(len(msg) / float(cols)))
		msgs = []
		while msg:
			msgs.append(msg[:rowspercol])
			msg = msg[rowspercol:]
		for msg in msgs:
			label = "\n".join(msg)
			if u"<" in label:
				cls = BetterStaticFancyText
			else:
				cls = wx.StaticText
			col = cls(self.panel, -1, "")
			if isinstance(col, wx.Panel):
				col.BackgroundColour = self.panel.BackgroundColour
			if u"<" in label and wrap:
				col.maxlen = wrap
			col.SetMaxFontSize()
			maxlinewidth = 0
			tabcount = 0
			for line in msg:
				maxlinewidth = max(col.GetTextExtent(line)[0], maxlinewidth)
				tabcount = max(tabcount, line.count("\t"))
			# GetTextExtent doesn't always figure out correct width for
			# tab char, extra spacing needed
			tabwidth = tabcount * col.GetTextExtent("\t")[0]
			col.Label = label
			if not u"<" in label:
				col.MinSize = maxlinewidth + tabwidth + margin * 2, -1
			self.sizer3.Add(col, flag=wx.LEFT, border=margin)

		self.sizer0.SetSizeHints(self)
		self.sizer0.Layout()
		if not pos or pos == (-1, -1):
			self.Center(wx.BOTH)
		elif pos[0] == -1:
			self.Center(wx.HORIZONTAL)
		elif pos[1] == -1:
			self.Center(wx.VERTICAL)
		if show:
			self.Show()
			self.Raise()


class TwoWaySplitter(FourWaySplitter):
	
	def __init__(self, *args, **kwargs):
		FourWaySplitter.__init__(self, *args, **kwargs)
		self._minimum_pane_size = 0
		self._sashbitmap = getbitmap("theme/sash")
		self._splitsize = (800, 400)
		self._expandedsize = (400, 400)
		self._handcursor = wx.StockCursor(wx.CURSOR_HAND)
		self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
		# Redrawing the splitter in FourWaySplitter._RedrawIfHotSensitive
		# which happens if the mouse enters or leaves the splitter causes blank
		# windows managed by the splitter under wxGTK if using GTK3.
		# We are using a custom splitter graphic without 'hot' state, so we can
		# circumvent this.
		self.Unbind(wx.EVT_ENTER_WINDOW)

	def _GetSashSize(self):
		""" Used internally. """

		if self.HasFlag(wx.SP_NOSASH):
			return 0

		return self._sashbitmap.GetSize()[0]

    # Recompute layout
	def _SizeWindows(self):
		"""
		Recalculate the layout based on split positions and split fractions.

		:see: L{SetHSplit} and L{SetVSplit} for more information about split fractions.
		"""
			
		win0 = self.GetTopLeft()
		win1 = self.GetTopRight()

		width, height = self.GetSize()
		barSize = self._GetSashSize()
		border = self._GetBorderSize()
		
		if self._expanded < 0:
			totw = width - barSize - 2*border
			self._splitx = max((self._fhor*totw)/10000, self._minimum_pane_size)
			self._splity = height
			rightw = max(totw - self._splitx, 0)
			if win0:
				win0.SetPosition((0, 0))
				win0.SetSize((self._splitx, self._splity))
				win0.Show()
			if win1:
				win1.SetPosition((self._splitx + barSize, 0))
				win1.SetSize((rightw, self._splity))
				win1.Show()

		else:
			if self._expanded < len(self._windows):
				for ii, win in enumerate(self._windows):
					if ii == self._expanded:
						win.SetSize((width - barSize - 2*border, height-2*border))
						win.Show()
					else:
						win.Hide()

	# Draw the horizontal split
	def DrawSplitter(self, dc):
		"""
		Actually draws the sashes.

		:param `dc`: an instance of `wx.DC`.
		"""

		backColour = wx.Colour(*[int(v * .85) for v in self.BackgroundColour[:3]])
		dc.SetBrush(wx.Brush(backColour, wx.SOLID))
		dc.SetPen(wx.Pen(backColour))
		dc.Clear()
		
		width, height = self.GetSize()

		if self._expanded > -1:
			splitx = width - self._GetSashSize() - 2*self._GetBorderSize()
		else:
			splitx = self._splitx

		if self._expanded < 0:
			if splitx <= self._minimum_pane_size:
				self._sashbitmap = getbitmap("theme/sash-right")
			else:
				self._sashbitmap = getbitmap("theme/sash")
		else:
			self._sashbitmap = getbitmap("theme/sash-left")
		sashwidth, sashheight = self._sashbitmap.GetSize()
		
		dc.DrawRectangle(splitx, 0, sashwidth, height)
		
		dc.DrawBitmap(self._sashbitmap, splitx, height / 2 - sashheight / 2, True)
	
	def GetExpandedSize(self):
		return self._expandedsize

	# Determine split mode
	def GetMode(self, pt):
		"""
		Determines the split mode for L{FourWaySplitter}.

		:param `pt`: the point at which the mouse has been clicked, an instance of
		 `wx.Point`.

		:return: One of the following 3 split modes:

		 ================= ==============================
		 Split Mode        Description
		 ================= ==============================
		 ``wx.HORIZONTAL`` the user has clicked on the horizontal sash
		 ``wx.VERTICAL``   The user has clicked on the vertical sash
		 ``wx.BOTH``       The user has clicked at the intersection between the 2 sashes
		 ================= ==============================

		"""

		barSize = self._GetSashSize()        
		flag = wx.BOTH

		width, height = self.GetSize()
		if self._expanded > -1:
			splitx = width - self._GetSashSize() - 2*self._GetBorderSize()
		else:
			splitx = self._splitx

		if pt.x < splitx - _TOLERANCE:
			flag &= ~wx.VERTICAL

		if pt.y < self._splity - _TOLERANCE:
			flag &= ~wx.HORIZONTAL

		if pt.x >= splitx + barSize + _TOLERANCE:
			flag &= ~wx.VERTICAL
			
		if pt.y >= self._splity + barSize + _TOLERANCE:
			flag &= ~wx.HORIZONTAL
			
		return flag
	
	def GetSplitSize(self):
		return self._splitsize

	def OnLeaveWindow(self, event):
		"""
		Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{FourWaySplitter}.

		:param `event`: a `wx.MouseEvent` event to be processed.
		"""

		self.SetCursor(wx.STANDARD_CURSOR)

	def OnLeftDClick(self, event):
		if not self.IsEnabled():
			return
		
		pt = event.GetPosition()

		if self.GetMode(pt):
			
			barSize = self._GetSashSize()
			
			winborder, titlebar = get_platform_window_decoration_size()
			
			self.Freeze()
			self.SetExpanded(-1 if self._expanded > -1 else 0)
			if self._expanded < 0:
				if self.GetExpandedSize()[0] < self.GetSplitSize()[0]:
					self._fhor = int(math.ceil((self.GetExpandedSize()[0] - 
												barSize - winborder * 2) /
											   float(self.GetSplitSize()[0] - 
													 barSize - winborder * 2) *
											   10000))
				else:
					self._fhor = int(math.ceil(self._minimum_pane_size /
											   float(self.GetSplitSize()[0] - 
													 barSize - winborder * 2) *
											   10000))
			self.Thaw()

	# Button being released
	def OnLeftUp(self, event):
		"""
		Handles the ``wx.EVT_LEFT_UP`` event for L{FourWaySplitter}.

		:param `event`: a `wx.MouseEvent` event to be processed.
		"""
		
		if not self.IsEnabled():
			return

		if self.HasCapture():
			self.ReleaseMouse()

		flgs = self._flags
		
		self._flags &= ~FLAG_CHANGED
		self._flags &= ~FLAG_PRESSED
		
		if flgs & FLAG_PRESSED:
			
			if not self.GetAGWWindowStyleFlag() & wx.SP_LIVE_UPDATE:
				self.DrawTrackSplitter(self._splitx, self._splity)
				self.DrawSplitter(wx.ClientDC(self))
				self.AdjustLayout()
				
			if flgs & FLAG_CHANGED:
				event = FourWaySplitterEvent(wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED, self)
				event.SetSashIdx(self._mode)
				event.SetSashPosition(wx.Point(self._splitx, self._splity))
				self.GetEventHandler().ProcessEvent(event)                
			else:
				self.OnLeftDClick(event)

		self._mode = NOWHERE

	def OnMotion(self, event):
		"""
		Handles the ``wx.EVT_MOTION`` event for L{FourWaySplitter}.

		:param `event`: a `wx.MouseEvent` event to be processed.
		"""

		if self.HasFlag(wx.SP_NOSASH):
			return

		pt = event.GetPosition()

		# Moving split
		if self._flags & FLAG_PRESSED:
			
			width, height = self.GetSize()
			barSize = self._GetSashSize()
			border = self._GetBorderSize()
			
			totw = width - barSize - 2*border
			
			if pt.x > totw - _TOLERANCE:
				self._expanded = 0
				self._offx += 1
			elif self._expanded > -1 and pt.x > self._minimum_pane_size:
				self._fhor = int((pt.x - barSize - 2*border) / float(totw) * 10000)
				self._splitx = (self._fhor*totw)/10000
				self._offx = pt.x - self._splitx + 1
				self._expanded = -1

			oldsplitx = self._splitx
			oldsplity = self._splity
			
			if self._mode == wx.BOTH:
				self.MoveSplit(pt.x - self._offx, pt.y - self._offy)
			  
			elif self._mode == wx.VERTICAL:
				self.MoveSplit(pt.x - self._offx, self._splity)
			  
			elif self._mode == wx.HORIZONTAL:
				self.MoveSplit(self._splitx, pt.y - self._offy)

			# Send a changing event
			if not self.DoSendChangingEvent(wx.Point(self._splitx, self._splity)):
				self._splitx = oldsplitx
				self._splity = oldsplity
				return              

			if oldsplitx != self._splitx or oldsplity != self._splity:
				if not self.GetAGWWindowStyleFlag() & wx.SP_LIVE_UPDATE:
					self.DrawTrackSplitter(oldsplitx, oldsplity)
					self.DrawTrackSplitter(self._splitx, self._splity)
				else:
					self.AdjustLayout()

				self._flags |= FLAG_CHANGED

			self.Refresh()
		
		# Change cursor based on position
		ff = self.GetMode(pt)
		
		if self._expanded > -1 and self._splitx <= self._minimum_pane_size:
			self.SetCursor(self._handcursor)

		elif ff == wx.BOTH:
			self.SetCursor(self._sashCursorSIZING)

		elif ff == wx.VERTICAL:
			self.SetCursor(self._sashCursorWE)

		elif ff == wx.HORIZONTAL:
			self.SetCursor(self._sashCursorNS)

		else:
			self.SetCursor(self._handcursor)

		event.Skip()
	
	def SetExpandedSize(self, size):
		self._expandedsize = size

	def SetMinimumPaneSize(self, size=0):
		self._minimum_pane_size = size
	
	def SetSplitSize(self, size):
		self._splitsize = size


def get_gradient_panel(parent, label, x=16):
	gradientpanel = BitmapBackgroundPanelText(parent, size=(-1, 31))
	gradientpanel.alpha = .75
	gradientpanel.blend = True
	gradientpanel.bordertopcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW)
	gradientpanel.drawbordertop = True
	gradientpanel.label_x = x
	gradientpanel.textalpha = .8
	bitmap = bitmaps.get("gradient_panel")
	if not bitmap:
		bitmap = getbitmap("theme/gradient").GetSubBitmap((0, 1, 8, 15)).ConvertToImage().Mirror(False).ConvertToBitmap()
		image = bitmap.ConvertToImage()
		databuffer = image.GetDataBuffer()
		for i, byte in enumerate(databuffer):
			if byte > "\0":
				databuffer[i] = chr(int(round(ord(byte) * (255.0 / 223.0))))
		bitmap = image.ConvertToBitmap()
		bitmaps["gradient_panel"] = bitmap
	gradientpanel.SetBitmap(bitmap)
	gradientpanel.SetMaxFontSize(11)
	font = gradientpanel.Font
	font.SetWeight(wx.BOLD)
	gradientpanel.Font = font
	gradientpanel.SetLabel(label)
	return gradientpanel


def get_html_colors(allow_alpha=False):
	"""
	Get background, text, link and visited link colors based on system colors
	
	"""
	bgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
	text = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
	linkcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HOTLIGHT)
	if max(linkcolor[:3]) == 0:
		if sys.platform == "darwin":
			# Use Mavericks-like color scheme
			linkcolor = wx.Colour(44, 93, 205)
		else:
			linkcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
	vlinkcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)
	if not allow_alpha:
		for key, value in locals().items():
			if isinstance(value, wx.Colour):
				locals()[key].Set(*value[:3])
	return bgcolor, text, linkcolor, vlinkcolor


def get_widget(win, id_name_label):
	# hasattr, getattr, setattr silently convert attribute names to byte strings,
	# we have to make sure there's no UnicodeEncodeError
	if (id_name_label == safe_str(safe_unicode(id_name_label), "ascii") and
		hasattr(win, id_name_label)):
		# Attribute name
		try:
			# Trying to access some attributes of wx widgets may
			# raise an exception
			child = getattr(win, id_name_label)
		except:
			return False
		if is_scripting_allowed(win, child):
			return child
	else:
		# ID or label
		try:
			id_name_label = int(id_name_label)
		except ValueError:
			pass
		for child in win.GetAllChildren():
			if (is_scripting_allowed(win, child) and
				(child.Id == id_name_label or child.Name == id_name_label or
				 child.Label == id_name_label)):
				return child


def get_toplevel_window(id_name_label):
	try:
		id_name_label = int(id_name_label)
	except ValueError:
		pass
	for win in reversed(wx.GetTopLevelWindows()):
		if win and (win.Id == id_name_label or win.Name == id_name_label or
					win.Label == id_name_label) and win.IsShown():
			return win


def is_scripting_allowed(win, child):
	return (child and child.TopLevelParent is win and
			child.IsShownOnScreen() and
		    isinstance(child, (CustomCheckBox, aui.AuiNotebook,
							   labelbook.FlatBookBase,  wx.Control, wx.Notebook,
							   wx.grid.Grid)) and
			not isinstance(child, (SimpleBook, aui.AuiTabCtrl,
								   wx.StaticBitmap)))


_elementtable = {}

def format_ui_element(child, format="plain"):
	if child.TopLevelParent:
		if not hasattr(child.TopLevelParent, "_win2name"):
			child.TopLevelParent._win2name = {}
			for name, attr in child.TopLevelParent.__dict__.iteritems():
				if isinstance(attr, wx.Window):
					child.TopLevelParent._win2name[attr] = name
		name = child.TopLevelParent._win2name.get(child, child.Name)
	else:
		name = child.Name
	items = getattr(child, "Items", [])
	value = None
	if not items and isinstance(child, wx.ListCtrl):
		for row in xrange(child.GetItemCount()):
			item = []
			for col in xrange(child.GetColumnCount()):
				item.append(child.GetItem(row, col).GetText())
			items.append(" ".join(item))
		row = child.GetFirstSelected()
		if row != wx.NOT_FOUND:
			value = items[row]
	elif isinstance(child, (aui.AuiNotebook, labelbook.FlatBookBase,
							wx.Notebook)):
		for page_idx in xrange(child.GetPageCount()):
			items.append(child.GetPageText(page_idx))
		page_idx = child.GetSelection()
		if page_idx != wx.NOT_FOUND:
			value = child.GetPageText(page_idx)
	cols = []
	if isinstance(child, wx.grid.Grid):
		for col in xrange(child.GetNumberCols()):
			cols.append(child.GetColLabelValue(col))
	if format != "plain":
		uielement = {"class": child.__class__.__name__, "name": name,
					 "enabled": child.IsEnabled(), "id": child.Id}
		if child.Label:
			uielement["label"] = child.Label
		if isinstance(child, (CustomCheckBox, wx.CheckBox, wx.RadioButton)):
			uielement["checked"] = child.GetValue()
		elif hasattr(child, "GetValue") and not isinstance(child, wx.ListCtrl):
			uielement["value"] = child.GetValue()
		elif hasattr(child, "GetStringSelection"):
			uielement["value"] = child.GetStringSelection()
		elif value is not None:
			uielement["value"] = value
		if items:
			uielement["items"] = items
		if cols:
			uielement["cols"] = cols
			uielement["rows"] = child.GetNumberRows()
		return uielement
	return "%s %s %s%s %s%s%s%s" % (child.__class__.__name__, child.Id,
									sp.list2cmdline([name]),
									(child.Label and
									 " " + demjson.encode(child.Label)),
									"enabled" if child.IsEnabled() else "disabled",
									(isinstance(child, (CustomCheckBox, wx.CheckBox,
														wx.RadioButton)) and
									 child.GetValue() and " checked") or
									(getattr(child, "GetValue", "") and
									 not isinstance(child, (CustomCheckBox, wx.CheckBox,
													  wx.RadioButton, wx.ListCtrl)) and
									 " value " + demjson.encode(child.GetValue())) or
									(getattr(child, "GetStringSelection", "") and
									 " value " + demjson.encode(child.GetStringSelection())) or
									(value is not None and " value " +
									 demjson.encode(value) or ""),
									(items and " items " +
									 demjson.encode(items).strip("[]").replace('","', '" "') or
									 ""),
									(cols and " rows %s cols %s" %
									 (child.GetNumberRows(),
									  demjson.encode(cols).strip("[]").replace('","', '" "')) or
									 ""))


def restore_path_dialog_classes():
	wx.DirDialog = _DirDialog
	wx.FileDialog = _FileDialog


def get_appid_from_window_hierarchy(toplevelwindow):
	if isinstance(toplevelwindow, wx.Frame):
		frame = toplevelwindow
	else:
		frame = get_parent_frame(toplevelwindow)
	base_appid = appname.lower()
	return {"lut3dframe": base_appid + "-3dlut-maker",
		    "lut_viewer": base_appid + "-curve-viewer",
		    "profile_info": base_appid + "-profile-info",
		    "scriptingframe": base_appid + "-scripting-client",
		    "synthiccframe": base_appid + "-synthprofile",
		    "tcgen": base_appid + "-testchart-editor",
		    "vrml2x3dframe": base_appid + "-vrml-to-x3d-converter"}.get(frame and
																		frame.Name,
																		base_appid)


def set_icons(toplevelwindow):
	""" Set icon to that of parent frame """
	appid = get_appid_from_window_hierarchy(toplevelwindow)
	toplevelwindow.SetIcons(config.get_icon_bundle([256, 48, 32, 16], appid))


def show_result_dialog(result, parent=None, pos=None, confirm=False, wrap=70):
	""" Show dialog depending on type of result. Result should be an
	exception type. An appropriate visual representation will be chosen
	whether result is of exception type 'Info', 'Warning' or other error. """
	if (result.__class__ is Exception and result.args and
		result.args[0] == "aborted"):
		# Special case - aborted
		msg = lang.getstr("aborted")
	else:
		msg = safe_unicode(result)
	if not pos:
		pos=(-1, -1)
	if isinstance(result, Info):
		bitmap = geticon(32, "dialog-information")
	elif isinstance(result, (Warning, DownloadError)):
		bitmap = geticon(32, "dialog-warning")
	else:
		bitmap = geticon(32, "dialog-error")
	if isinstance(result, DownloadError):
		confirm = lang.getstr("go_to", result.url)
	if confirm:
		cls = ConfirmDialog
		ok = confirm
	else:
		cls = InfoDialog
		ok = lang.getstr("ok")
	nowrap = wrap is None
	dlg = cls(parent, pos=pos, msg=msg, ok=ok, bitmap=bitmap, 
			  log=not isinstance(result, (UnloggedError, UnloggedInfo,
										  UnloggedWarning)), nowrap=nowrap,
										  wrap=wrap)
	if confirm:
		dlg_ok = dlg.ShowModal() == wx.ID_OK
		dlg.Destroy()
		if dlg_ok and isinstance(result, DownloadError):
			launch_file(result.url)
		return dlg_ok


def test():
	config.initcfg()
	lang.init()
	def key_handler(self, event):
		if event.GetEventType() == wx.EVT_CHAR_HOOK.typeId:
			print "Received EVT_CHAR_HOOK", event.GetKeyCode(), repr(unichr(event.GetKeyCode()))
		elif event.GetEventType() == wx.EVT_KEY_DOWN.typeId:
			print "Received EVT_KEY_DOWN", event.GetKeyCode(), repr(unichr(event.GetKeyCode()))
		elif event.GetEventType() == wx.EVT_MENU.typeId:
			print "Received EVT_MENU", self.id_to_keycode.get(event.GetId()), repr(unichr(self.id_to_keycode.get(event.GetId())))
		event.Skip()
			
	ProgressDialog.key_handler = key_handler
	SimpleTerminal.key_handler = key_handler
	
	app = BaseApp(0)
	style = (wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_CAN_ABORT |
			 wx.PD_SMOOTH)
	p = ProgressDialog(msg="".join(("Test " * 5)), maximum=10000, style=style,
					   pauseable=True, fancy=not "+fancy" in sys.argv[1:],
					   allow_close=True)
	#t = SimpleTerminal(start_timer=False)
	app.MainLoop()

if __name__ == "__main__":
	test()
