# -*- test-case-name: mv3d.test.tools.test_guidewx -*- # Copyright (C) 2010-2012 Mortal Coil Games # See LICENSE for details. """ wxPython widgets for GUIde. @author: mike """ import os import sys from twisted.python.failure import Failure from twisted.python.log import deferr from twisted.internet.defer import Deferred, inlineCallbacks, returnValue import wx from wx import aui from wx.aui import AuiPaneInfo, AuiNotebook from wx import ITEM_NORMAL, ITEM_RADIO, ITEM_CHECK from mv3d.util.input import Keys try: from wx.lib.agw.aui.aui_constants import ITEM_LABEL, ITEM_SPACER except: ITEM_LABEL = 4 ITEM_SPACER = 5 try: from wx.aui import AuiToolBar, AUI_TB_DEFAULT_STYLE except ImportError: try: from wx.lib.agw.aui.auibar import AuiToolBar from wx.lib.agw.aui import ITEM_LABEL, ITEM_SPACER except ImportError: AuiToolBar = None try: from wx.lib.rcsizer import RowColSizer except ImportError: RowColSizer = None try: from wx.lib.agw.genericmessagedialog import GenericMessageDialog except ImportError: GenericMessageDialog = None import Image try: from wx.lib.agw.floatspin import FloatSpin, EVT_FLOATSPIN except: FloatSpin = wx.TextCtrl EVT_FLOATSPIN = wx.EVT_TEXT try: from wx.lib.scrolledpanel import ScrolledPanel except ImportError: ScrolledPanel = wx.Panel from wx.py.crust import Crust try: from wx.lib.agw.pycollapsiblepane import PyCollapsiblePane except ImportError: PyCollapsiblePane = None try: from wx.lib.buttons import GenBitmapToggleButton except ImportError: GenBitmapToggleButton = None try: from wx import propgrid except ImportError: propgrid = None import mv3d from mv3d.client.ui.irenderer import ISceneNode, IStaticMesh, IWxRenderWindow from mv3d.util.math3d import Vector, Quaternion from mv3d.util import guide from mv3d.util.guide import Parser, DataAccessor, boolify, ChangeNotifier, \ Direction, Coordinate, Alignment, Visibility, ToolbarItemType, DockLocations, \ MenuItemType, ToolbarTextLocations, BorderType, TemplateFactory,\ DialogButton, DialogIcon, PropertyDataTypes, PropertyGridField #@UnusedImport from mv3d.util.guide import Position #@UnusedImport from mv3d.util.profiler import timed wxKeyMap = { # http://www.wxpython.org/docs/api/wx.KeyEvent-class.html wx.WXK_TAB:Keys.tab, wx.WXK_UP:Keys.up, wx.WXK_DOWN:Keys.down, wx.WXK_LEFT:Keys.left, wx.WXK_RIGHT:Keys.right, wx.WXK_RETURN:Keys.return_, wx.WXK_ESCAPE:Keys.escape, wx.WXK_TAB:Keys.tab, wx.WXK_HOME:Keys.home, wx.WXK_END:Keys.end, wx.WXK_CONTROL:Keys.ctrl, wx.WXK_BACK:Keys.backspace, wx.WXK_DELETE:Keys.delete, wx.WXK_SPACE:Keys.space, wx.WXK_ADD:Keys.plus, wx.WXK_SUBTRACT:Keys.minus, wx.WXK_INSERT:Keys.insert, wx.WXK_PAGEDOWN:Keys.pageDown, wx.WXK_PAGEUP:Keys.pageUp, wx.WXK_SHIFT:Keys.shift, wx.WXK_ALT:Keys.alt, wx.WXK_SCROLL:Keys.scrollLock, wx.WXK_PRINT:Keys.printScreen, wx.WXK_PAUSE:Keys.break_, wx.WXK_F1:Keys.f1, wx.WXK_F2:Keys.f2, wx.WXK_F3:Keys.f3, wx.WXK_F4:Keys.f4, wx.WXK_F5:Keys.f5, wx.WXK_F6:Keys.f6, wx.WXK_F7:Keys.f7, wx.WXK_F8:Keys.f8, wx.WXK_F9:Keys.f9, wx.WXK_F10:Keys.f10, wx.WXK_F11:Keys.f11, wx.WXK_F12:Keys.f12, ord("~"):Keys.tilde, ord("-"):Keys.minus, ord("+"):Keys.plus, ord("["):Keys.openBracket, ord("]"):Keys.closeBracket, ord(";"):Keys.semicolon, ord("'"):Keys.apostrophe, ord(","):Keys.comma, ord("."):Keys.period, ord("/"):Keys.slash, ord("\\"):Keys.backSlash } for key in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": wxKeyMap[ord(key)] = getattr(Keys, key.lower()) for key in "0123456789": wxKeyMap[ord(key)] = getattr(Keys, "number" + key) wxReverseKeymap = dict(zip(wxKeyMap.values(), wxKeyMap.keys())) def pilToImage(pil, alpha=True): """ Convert PIL Image to wx.Image. Found at: http://bytes.com/topic/python/answers/551782-wxpython-pil """ if alpha: image = apply(wx.EmptyImage, pil.size) image.SetData(pil.convert("RGB").tostring()) image.SetAlphaData(pil.convert("RGBA").tostring()[3::4]) else: image = wx.EmptyImage(pil.size[0], pil.size[1]) new_image = pil.convert('RGB') data = new_image.tostring() image.SetData(data) return image def imagetopil(image): """Convert wx.Image to PIL Image.""" pil = Image.new('RGB', (image.GetWidth(), image.GetHeight())) pil.fromstring(image.GetData()) return pil def resizeParents(parent): while hasattr(parent, "window") and not isinstance(parent.window, wx.Frame): if not hasattr(parent, "parent"): return parent = parent.parent if isinstance(parent, DockableFrame): # parent.window.SendSizeEvent() # crashes wx in bad ways. parent.window.Refresh() return if parent is not None and isinstance(parent.window, wx.Window): parent.window.SendSizeEvent() parent.window.Refresh() def getBorderFlags(widget, flags=0): """ Determines the border flag for the specified widget """ if widget.borderType == BorderType.noBorder: flags |= wx.NO_BORDER elif widget.borderType == BorderType.default: flags |= wx.DEFAULT_CONTROL_BORDER elif widget.borderType == BorderType.normal: flags |= wx.BORDER elif widget.borderType == BorderType.simple: flags |= wx.SIMPLE_BORDER elif widget.borderType == BorderType.double: flags |= wx.DOUBLE_BORDER elif widget.borderType == BorderType.sunken: flags |= wx.SUNKEN_BORDER elif widget.borderType == BorderType.raised: flags |= wx.RAISED_BORDER return flags def getFlags(widget, flags=0): """ Helper function to convert alignment flags to wx flags """ if widget.halign is not None: if widget.halign == Alignment.center: flags |= wx.ALIGN_CENTER_HORIZONTAL elif widget.halign == Alignment.left: flags |= wx.ALIGN_LEFT elif widget.halign == Alignment.right: flags |= wx.ALIGN_RIGHT if widget.valign is not None: if widget.valign == Alignment.center: flags |= wx.ALIGN_CENTER_VERTICAL elif widget.valign == Alignment.top: flags |= wx.ALIGN_TOP elif widget.valign == Alignment.bottom: flags |= wx.ALIGN_BOTTOM return flags def getSizerArgs(widget): """ Get sizer arguments. Returns relative space, flags, and border """ flags = 0 space = 0 border = 0 if widget.border is not None: border = widget.border height = widget._height.getData() width = widget._width.getData() count = 0 if isinstance(height, str) and height.strip().endswith("%"): count += 1 if isinstance(width, str) and width.strip().endswith("%"): count += 1 if count == 1: flags |= wx.SHAPED space = 1 elif count == 2: flags |= wx.EXPAND space = 1 flags = getFlags(widget, flags) return space, flags, border def addToSizer(sizer, widget, direction): """ Adds a widget to a sizer """ if widget.visibility == Visibility.collapsed: return 0, 0 height = widget.height width = widget.width border = 0 if widget.border is not None: border = widget.border if height is None: height = Coordinate(-1) if width is None: width = Coordinate(-1) space = 0 absSpace = 0 flags = getFlags(widget) if width.relative and direction == Direction.horizontal: space = height.value if height.relative: newSizer = wx.BoxSizer(wx.VERTICAL) if widget.halign == Alignment.left: newSizer.Add(widget.window, height.value, wx.EXPAND) newSizer.Add((1, 1), 100 - height.value, wx.EXPAND) elif widget.halign == Alignment.center: newSizer.Add((1, 1), (100 - height.value) / 2, wx.EXPAND) newSizer.Add(widget.window, height.value, wx.EXPAND) newSizer.Add((1, 1), (100 - height.value) / 2, wx.EXPAND) else: newSizer.Add((1, 1), 100 - height.value, wx.EXPAND) newSizer.Add(widget.window, height.value, wx.EXPAND) sizer.Add(newSizer, space, wx.EXPAND, border) else: sizer.Add(widget.window, space, flags, border) elif height.relative and direction == Direction.vertical: space = height.value if width.relative: newSizer = wx.BoxSizer(wx.HORIZONTAL) if widget.valign == Alignment.top: newSizer.Add(widget.window, width.value, wx.EXPAND) newSizer.Add((1, 1), 100 - width.value, wx.EXPAND) elif widget.valign == Alignment.center: newSizer.Add((1, 1), (100 - width.value) / 2, wx.EXPAND) newSizer.Add(widget.window, width.value, wx.EXPAND) newSizer.Add((1, 1), (100 - width.value) / 2, wx.EXPAND) else: if width.value < 100: newSizer.Add((1, 1), 100 - width.value, wx.EXPAND) newSizer.Add(widget.window, width.value, wx.EXPAND) sizer.Add(newSizer, space, wx.EXPAND, border) else: sizer.Add(widget.window, space, flags, border) elif width.relative: # direction is vertical and height not relative newSizer = wx.BoxSizer(wx.HORIZONTAL) if widget.halign == Alignment.left: newSizer.Add(widget.window, width.value, flags | wx.EXPAND) newSizer.Add((1, 1), 100 - width.value, wx.EXPAND) elif widget.halign == Alignment.center: newSizer.Add((1, 1), (100 - width.value) / 2, wx.EXPAND) newSizer.Add(widget.window, width.value, flags | wx.EXPAND) newSizer.Add((1, 1), (100 - width.value) / 2, wx.EXPAND) else: newSizer.Add((1, 1), 100 - width.value, wx.EXPAND) newSizer.Add(widget.window, width.value, flags | wx.EXPAND) sizer.Add(newSizer, 0, wx.EXPAND | wx.ALL, border) absSpace += height.value elif height.relative: # direction is horizontal and width not relative newSizer = wx.BoxSizer(wx.VERTICAL) if widget.valign == Alignment.top: newSizer.Add(widget.window, height.value, flags | wx.EXPAND) newSizer.Add((1, 1), 100 - height.value, wx.EXPAND) elif widget.valign == Alignment.center: newSizer.Add((1, 1), (100 - height.value) / 2, wx.EXPAND) newSizer.Add(widget.window, height.value, flags | wx.EXPAND) newSizer.Add((1, 1), (100 - height.value) / 2, wx.EXPAND) else: newSizer.Add((1, 1), 100 - height.value, wx.EXPAND) newSizer.Add(widget.window, height.value, flags | wx.EXPAND) sizer.Add(newSizer, 0, wx.EXPAND, border) absSpace += width.value else: sizer.Add(widget.window, 0, flags, border) if direction == Direction.horizontal: if widget.width is not None: absSpace += width.value else: if widget.height is not None: absSpace += height.value return space, absSpace def addWidgetsToSizer(parent, widgets, direction, sizer=None): """ Add a group of widgets to a sizer """ if sizer is None: if direction == Direction.horizontal: sizer = wx.BoxSizer(wx.HORIZONTAL) else: sizer = wx.BoxSizer(wx.VERTICAL) totalSpace = 0 totalAbsSpace = 0 for child in widgets: space, absSpace = addToSizer(sizer, child, direction) totalSpace += space totalAbsSpace += absSpace if totalSpace > 0 and totalSpace < 100: # there's some extra room if totalAbsSpace == 0: sizer.Add((1, 1), 100 - totalSpace) else: dims = parent.window.GetClientSize() if direction == Direction.horizontal: dim = dims[0] else: dim = dims[1] if dim: totalSpace += totalAbsSpace / float(dim) * 100 if totalSpace < 100: sizer.Add((1, 1), 100 - totalSpace) return sizer class GuideWxWidget(object): """ Base class for guidewx Widgets """ keyDict = { wx.WXK_UP:"up", wx.WXK_DOWN:"down", wx.WXK_LEFT:"left", wx.WXK_RIGHT:"right", wx.WXK_RETURN:"return", wx.WXK_ESCAPE:"escape", wx.WXK_TAB:"tab", wx.WXK_HOME:"home", wx.WXK_END:"end", wx.WXK_CONTROL:"control", wx.WXK_ALT:"alt", } _updatingSize = False _wasEverCollapsed = False minSize = 100, 20 def __init__(self): self.window.SetAutoLayout(True) if self.name: self.window.SetName(self.name) elif self.text: self.window.SetName(str(self.text)) self.listen("visibility", self._visibilityUpdated) if self.visibility == Visibility.hidden: self.window.Hide() if self.tooltip is not None: self.window.SetToolTipString(str(self.tooltip)) if self.contextMenu is not None: self.contextMenu = self.contextMenu.produce(self.dataContext, self)[0] self.listen("enabled", self._enabledUpdated) if type(self.enabled) == bool: self.window.Enable(self.enabled) self.listen("width", self._sizeUpdated) self.listen("height", self._sizeUpdated) self.listen("backgroundColor", self._backgroundColorUpdated) self.listen("tooltip", self._tooltipUpdated) self.window.Bind(wx.EVT_MOUSE_EVENTS, self._mouseEvent) self.window.Bind(wx.EVT_KEY_UP, self._keyUp) self.window.Bind(wx.EVT_KEY_DOWN, self._keyDown) self._backgroundColorUpdated(None, None) self.window.Bind(wx.EVT_SIZE, self._onSizeChanged) self._lastSize = self.window.GetClientSizeTuple() self._sizeUpdated(None, None) def _onSizeChanged(self, event): """ The size of the widget changed """ if self.window.GetClientSizeTuple() == self._lastSize: return self._sizeChanged(None, None) self._lastSize = self.window.GetClientSizeTuple() event.Skip() def _keyUp(self, event): """ Called when the user changes the text """ if isinstance(event, wx.KeyEvent): keycode = self.keyDict.get(event.GetKeyCode()) if keycode is None: if event.GetKeyCode() < 255: keycode = chr(event.GetKeyCode()).lower() else: keycode = event.GetKeyCode() self.onKeyUp(keycode) event.Skip() def _keyDown(self, event): """ Called when the user changes the text """ if isinstance(event, wx.KeyEvent): keycode = self.keyDict.get(event.GetKeyCode()) if keycode is None: if event.GetKeyCode() < 255: keycode = chr(event.GetKeyCode()).lower() else: keycode = event.GetKeyCode() self.onKeyDown(keycode) event.Skip() def _sizeUpdated(self, _obj, _property): """ Called when the width or height is changed """ return def _sizeChanged(self, obj, prop=None): """ Called when the size of the widget changes. """ if self._updatingSize: return if self.window.GetClientSizeTuple() == self._lastSize: return guide.Widget._sizeChanged(self, obj, prop) def _visibilityUpdated(self, _obj, _prop): """ Called when the visibility changes. """ if self.visibility == Visibility.hidden: if self._wasEverCollapsed: self.window.Show() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) self.window.Hide() elif self.visibility == Visibility.collapsed: self._wasEverCollapsed = True self.window.Hide() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) else: self.window.Show() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) def _enabledUpdated(self, _obj, _property): """ Called when enabled state is changed outside of the UI """ self.window.Enable(bool(self.enabled)) def _backgroundColorUpdated(self, _obj, _prop): """ Called when the background color changes """ if self.backgroundColor is not None: try: if sys.platform == "darwin" and hasattr(self.window, "SetOwnBackgroundColour"): self.window.SetOwnBackgroundColour(self.backgroundColor[:3]) else: self.window.SetBackgroundColour(self.backgroundColor) self.window.Refresh() except wx.PyDeadObjectError: pass except AttributeError: pass def _tooltipUpdated(self, _obj, _prop): """ Called when the tooltip changes """ if self.tooltip is not None: self.window.SetToolTipString(str(self.tooltip)) else: self.window.SetToolTip(None) def _mouseEvent(self, event): """ Forward the event """ pos = event.GetPositionTuple() if event.LeftUp(): self.onLeftUp(pos) elif event.LeftDown(): self.onLeftDown(pos) elif event.RightDown(): self.onRightDown(pos) elif event.RightUp(): self.onRightUp(pos) if self.contextMenu is not None: self.window.PopupMenu(self.contextMenu.window) return else: self.onMouseMoved(pos) event.Skip() def onChildrenUpdated(self, parser): """ Default behavior is to call onGetChildren """ self.onGetChildren(parser) parent = self.parent while parent != parser: parent.window.Layout() parent = parent.parent def disable(self): """ Disable the widget """ self.window.Disable() def enable(self): """ Enable the widget """ self.window.Enable() def _getSize(self): """ Returns the size as a tuple """ size = [wx.DefaultSize.x, wx.DefaultSize.y] if self.width is not None: size[0] = int(self.width.value) if self.height is not None: size[1] = int(self.height.value) return size def _setSize(self, size): """ Sets the size from a tuple """ return self.width = size[0] self.height = size[1] size = property(_getSize, _setSize) def destroy(self): """ Destroy the widget """ if self.window is None: return try: self.window.Destroy() self.window = None except wx.PyDeadObjectError: pass class Frame(GuideWxWidget, guide.Frame): """ A panel that is a guide widget """ def __init__(self, **attribs): guide.Frame.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, id= -1, pos=wx.DefaultPosition, size=self.size, style=wx.TAB_TRAVERSAL | wx.NO_BORDER) GuideWxWidget.__init__(self) def onGetChildren(self, _parser): """ Called once our children have been parsed, add them all to a box sizer. """ guide.Frame.onGetChildren(self, _parser) box = addWidgetsToSizer(self, self.children, Direction.vertical) self.window.SetSizer(box) self.window.Layout() def destroy(self): """ Destroy the widget """ guide.Frame.destroy(self) GuideWxWidget.destroy(self) class Button(GuideWxWidget, guide.Button): """ A button that is a guide widget """ def __init__(self, **attribs): guide.Button.__init__(self, **attribs) if not self.isToggle: if self.image is None: self.window = wx.Button(self.parent.window, id= -1, label=str(self.text), pos=wx.DefaultPosition, size=self.size, style=0) self.listen("text", self._textUpdated) else: style = wx.BU_AUTODRAW if self.borderType: style = getBorderFlags(self, style) self.window = wx.BitmapButton(self.parent.window, id= -1, pos=wx.DefaultPosition, size=self.size, style=style, bitmap=self.image) self.window.Bind(wx.EVT_BUTTON, self.click) else: if self.image is None or GenBitmapToggleButton is None: self.window = wx.ToggleButton(self.parent.window, id= -1, label=self.text, pos=wx.DefaultPosition, size=self.size, style=0) self.window.Bind(wx.EVT_TOGGLEBUTTON, self.click) else: self.window = GenBitmapToggleButton(self.parent.window, -1, self.image) # self.window.SetBitmapLabel(self.image) # wx: stick to a frikkin api please. self.window.SetValue = self.window.SetToggle self.window.Bind(wx.EVT_BUTTON, self.click) self.window.SetValue(bool(self.isDown)) self.listen("isDown", self._stateUpdated) GuideWxWidget.__init__(self) def _textUpdated(self, _obj, _prop): """ The button's label was updated. """ self.window.SetLabel(self.text) def _stateUpdated(self, _obj, _property): """ Called when the state of the check is updated outside the ui """ self.window.SetValue(bool(self.isDown)) def click(self, _event): """ Pass through which ignores the event arg and send it to the guide button """ if self.isToggle: self.isDown = self.window.GetValue() guide.Button.click(self) def destroy(self): """ Destroy the widget """ guide.Button.destroy(self) GuideWxWidget.destroy(self) class WidgetWithText(GuideWxWidget): """ A Wx Widget with text """ def __init__(self): GuideWxWidget.__init__(self) self.listen("text", self._textUpdated) self.window.Bind(wx.EVT_KEY_UP, self._textEntered) self.updatesDisabled = False def _textEntered(self, event): """ Called when the user changes the text """ if self.updatesDisabled: return self.updatesDisabled = True try: if self.editable: text = self.window.GetValue() if text != self.text: self.text = text if isinstance(event, wx.KeyEvent): if event.GetKeyCode() != wx.WXK_RETURN: event.Skip() finally: self.updatesDisabled = False def _convertKey(self, wxKey): """ Convert a wxKey event to a string """ return self.keyDict.get(wxKey, wxKey) def _textUpdated(self, _obj, _property): """ Called when the text is changed outside of the UI """ if self.updatesDisabled: return self.updatesDisabled = True try: if self.text != self.window.GetValue(): text = self.text if text is None: text = "" self.window.SetValue(str(text)) except wx.PyDeadObjectError: pass finally: self.updatesDisabled = False class Spinner(WidgetWithText, guide.Spinner): """ A spinner control """ _settingValue = False def __init__(self, **attribs): guide.Spinner.__init__(self, **attribs) widget = wx.SpinCtrl if self.decimalPlaces: widget = FloatSpin self.window = widget(self.parent.window, id= -1, size=self.size) if self.minimum is not None and self.maximum is not None: self.window.SetRange(self.minimum, self.maximum) try: value = float(self.value) self.window.SetValue(value) except ValueError: pass except TypeError: pass if self.decimalPlaces and FloatSpin != wx.TextCtrl: self.window.SetDigits(self.decimalPlaces) self.window.SetIncrement(self.increment) WidgetWithText.__init__(self) self.window.Bind(EVT_FLOATSPIN, self._spinned) self.window.Bind(wx.EVT_SPIN, self._spinned) self.listen("value", self._valueUpdated) def _spinned(self, _event): """ The spinner was spun! """ if self._settingValue: return self._settingValue = True self.value = self.window.GetValue() self._settingValue = False def _valueUpdated(self, _obj, _prop): """ Our value was updated outside of the control """ if self._settingValue: return self._settingValue = True try: self.window.SetValue(float(self.value)) except ValueError: pass except TypeError: pass self._settingValue = False def _textUpdated(self, _obj, _property): """ Called when the text is changed outside of the UI """ try: if self.text != self.window.GetValue() and not self.updatesDisabled: text = self.text if text is None: text = "0" if self.decimalPlaces: try: self.window.SetValue(float(text)) except ValueError: pass else: self.window.SetValue(int(text)) except wx.PyDeadObjectError: pass def destroy(self): """ Destroy the widget """ guide.Spinner.destroy(self) WidgetWithText.destroy(self) class TextBox(WidgetWithText, guide.TextBox): """ A text box that is a guide widget """ def __init__(self, **attribs): guide.TextBox.__init__(self, **attribs) style = wx.TE_PROCESS_ENTER if self.multiline: style |= wx.TE_MULTILINE if self.mask: style |= wx.TE_PASSWORD style = getBorderFlags(self, style) text = self.text if text is None: text = "" self.window = wx.TextCtrl(self.parent.window, id= -1, value=str(text), pos=wx.DefaultPosition, size=self.size, style=style) WidgetWithText.__init__(self) if self.isLog: self.window.SetInsertionPointEnd() self.window.Bind(wx.EVT_TEXT_ENTER, self._enterHit) self.window.SetEditable(self.editable) self.listen("editable", self._editableUpdated) self.listen("cursorPosition", self._cursorPositionUpdated) def _cursorPositionUpdated(self, _obj, _prop): """ The cursor position was updated """ self.window.SetInsertionPoint(self.cursorPosition) def _enterHit(self, event): """ Pass through to our enter handler """ self._textEntered(event) self.onEnter() def _editableUpdated(self, _obj, _property): """ Called when editable state is changed outside of the UI """ self.window.SetEditable(self.editable) def _textUpdated(self, obj, prop): """ Called when the text is changed outside of the UI """ WidgetWithText._textUpdated(self, obj, prop) self.cursorPosition = self.window.GetInsertionPoint() if self.isLog: self.window.SetInsertionPointEnd() def destroy(self): """ Destroy the widget """ guide.TextBox.destroy(self) WidgetWithText.destroy(self) class WidgetWithLabel(GuideWxWidget): """ A Wx Widget with a label """ def __init__(self): GuideWxWidget.__init__(self) self.listen("text", self._textUpdated) def _textUpdated(self, _obj, _property): """ Called when the text is changed outside of the UI """ self.window.SetLabel(str(self.text)) class Label(WidgetWithLabel, guide.Label): """ A label that is a guide widget """ def __init__(self, **attribs): guide.Label.__init__(self, **attribs) style = getBorderFlags(self) text = self.text if text is None: text = "" self.window = wx.StaticText(self.parent.window, -1, str(text), pos=wx.DefaultPosition, size=self.size, style=style) WidgetWithLabel.__init__(self) def destroy(self): """ Destroy the widget """ guide.Label.destroy(self) WidgetWithLabel.destroy(self) class ProgressBar(WidgetWithLabel, guide.ProgressBar): """ A progress bar. """ def __init__(self, **attribs): guide.ProgressBar.__init__(self, **attribs) self.window = wx.Gauge(self.parent.window, -1, pos=wx.DefaultPosition, size=self.size, style=getBorderFlags(self)) WidgetWithLabel.__init__(self) self.window.SetRange(100) if self.progress is not None: self.window.SetValue(round(self.progress * 100)) self.listen("progress", self._progressUpdated) def _progressUpdated(self, _obj, _prop): """ Called when the progress is updated. """ if self.progress is not None: self.window.SetValue(round(self.progress * 100)) class CheckBox(WidgetWithLabel, guide.CheckBox): """ A text box that is a guide widget """ def __init__(self, **attribs): guide.CheckBox.__init__(self, **attribs) self.window = wx.CheckBox(self.parent.window, id= -1, label=self.text, pos=wx.DefaultPosition, size=self.size, style=getBorderFlags(self)) WidgetWithLabel.__init__(self) self.window.SetValue(self.checked) self.window.Bind(wx.EVT_CHECKBOX, self.checkChanged) self.listen("checked", self.checkUpdated) def checkChanged(self, _event): """ Called when the user changes the state of the check """ self.checked = self.window.GetValue() def checkUpdated(self, _obj, _property): """ Called when the state of the check is updated outside the ui """ self.window.SetValue(self.checked) def destroy(self): """ Destroy the widget """ guide.CheckBox.destroy(self) WidgetWithLabel.destroy(self) class Slider(GuideWxWidget, guide.Slider): """ A text box that is a guide widget """ def __init__(self, **attribs): guide.Slider.__init__(self, **attribs) self.window = wx.Slider(self.parent.window, id= -1, pos=wx.DefaultPosition, size=self.size, style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS) GuideWxWidget.__init__(self) self.window.SetTickFreq(5, 1) self.window.Min = self.start self.window.Max = self.end self.window.SetValue(self.value) self.window.Bind(wx.EVT_SLIDER, self.positionChanged) self.listen("value", self.positionUpdated) self.listen("start", self.scaleUpdated) self.listen("end", self.scaleUpdated) def positionChanged(self, _event): """ Called when the user changes the state of the check """ self.value = self.window.GetValue() def positionUpdated(self, _obj, _property): """ Called when the state of the slider is updated outside the ui """ self.window.SetValue(self.value) def scaleUpdated(self, _obj, _property): """ Called when the start or end is changed """ self.window.Min = self.start self.window.Max = self.end def destroy(self): """ Destroy the widget """ guide.Slider.destroy(self) GuideWxWidget.destroy(self) class ComboBox(WidgetWithText, guide.ComboBox): """ A text box that is a guide widget """ ignoreUpdates = False def __init__(self, **attribs): guide.ComboBox.__init__(self, **attribs) self.window = wx.ComboBox(self.parent.window, id= -1, value=str(self.text), pos=wx.DefaultPosition, size=self.size, style=getBorderFlags(self)) WidgetWithText.__init__(self) self.window.Bind(wx.EVT_COMBOBOX, self._textEntered) self.listen("items", self.itemsUpdated) self.listen("selection", self._selectionChanged) def _textEntered(self, event): """ Called when the user changes the text or selection """ if self.ignoreUpdates: return WidgetWithText._textEntered(self, event) for item in self.items or []: if self.text == str(item) and self.selection != item: self.ignoreUpdates = True try: self.selection = item finally: self.ignoreUpdates = False self.onSelect(item) return def _selectionChanged(self, _obj, _prop): """ The selection was changed outside of the ui """ if self.ignoreUpdates: return self.ignoreUpdates = True try: self.text = str(self.selection) finally: self.ignoreUpdates = False self.onSelect(self.selection) def onGetChildren(self, _parser): """ Called once our children have been parsed, append them to the list of selections. """ guide.ComboBox.onGetChildren(self, _parser) self.itemsUpdated() def itemsUpdated(self, _obj=None, _prop=None): """ Called when the items list is updated. """ if self.window.GetCount(): self.window.Clear() for item in self.items or []: self.window.Append(str(item), item) if self.selection is not None: self.text = str(self.selection) if self.text is not None: self.window.SetValue(self.text) def destroy(self): """ Destroy the widget """ guide.ComboBox.destroy(self) WidgetWithText.destroy(self) class ComboBoxItem(guide.ComboBoxItem): """ A Combo box item """ def __init__(self, **attribs): guide.ComboBoxItem.__init__(self, **attribs) self.window = self.parent.window class ListBox(GuideWxWidget, guide.ListBox): """ A text box that is a guide widget """ updatingSelection = False storedItems = None def __init__(self, **attribs): guide.ListBox.__init__(self, **attribs) style = 0 if self.selectMultiple: style = wx.LB_EXTENDED style = getBorderFlags(self, style) if self.columns is not None: self.window = wx.ListCtrl(self.parent.window, id= -1, pos=wx.DefaultPosition, size=self.size, style=getBorderFlags( self, wx.LC_REPORT)) self.selectMultiple = True self.columns = self.columns.produce(self.dataContext, self) for idx, column in enumerate(self.columns): self.window.InsertColumn(idx, column.text) self.window.Bind(wx.EVT_LEFT_DCLICK, self.doubleClick) self.window.Bind(wx.EVT_LIST_ITEM_SELECTED, self.selectionChanged) self.window.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.selectionChanged) else: self.window = wx.ListBox(self.parent.window, id= -1, pos=wx.DefaultPosition, size=self.size, style=style) self.window.Bind(wx.EVT_LISTBOX, self.selectionChanged) self.window.Bind(wx.EVT_LISTBOX_DCLICK, self.doubleClick) GuideWxWidget.__init__(self) self.listen("selection", self.selectionUpdated) self.listen("items", self.itemsUpdated) def onGetChildren(self, _parser): """ Called once our children have been parsed, append them as items in the listbox """ guide.ListBox.onGetChildren(self, _parser) if self.items is None: return self.itemsUpdated(None, None) def selectionChanged(self, _event): """ Called when the user changes the selected item """ if self.updatingSelection: return self.updatingSelection = True try: if self.selectMultiple: if self.columns is None: selections = self.window.GetSelections() else: selections = [] sel = self.window.GetFirstSelected() while sel > -1: selections.append(sel) sel = self.window.GetNextSelected(sel) self.selection = [self.items[sel] for sel in selections] else: self.selection = self.items[self.window.GetSelection()] self.onSelect(self.selection) finally: self.updatingSelection = False def doubleClick(self, _event): """ Called when the user doubleclicks an item """ # if self.selectMultiple: # selections = self.window.GetSelections() # self.selection = [self.items[sel] for sel in selections] # else: # self.selection = self.items[self.window.GetSelection()] self.onDoubleClick(self.selection) def selectionUpdated(self, _obj, _property): """ Called when the selection is changed outside of this ui """ if self.updatingSelection: return self.updatingSelection = True try: if self.selection is None: if self.columns is None: self.window.DeselectAll() self.updatingSelection = False return if self.selectMultiple: if self.columns is None: self.window.DeselectAll() else: selection = self.window.GetFirstSelected() while selection != -1: self.window.Select(selection, False) selection = self.window.GetFirstSelected() for selection in self.selection: try: if self.columns is not None: self.window.Select(self.items.index(selection), True) else: self.window.Select(self.items.index(selection)) except ValueError: continue else: try: self.window.Select(self.items.index(self.selection)) except ValueError: pass finally: self.updatingSelection = False @timed def itemsUpdated(self, _obj=None, _prop=None): """ Called when the items list is updated. """ if self.updatingSelection: return self.updatingSelection = True if self.storedItems is None: self.storedItems = [] found = [] for newIdx, item in enumerate(self.items or []): if self.items == self.storedItems: break try: oldIdx = self.storedItems[newIdx:].index(item) + newIdx if oldIdx != newIdx: for testIdx in range(newIdx, oldIdx): if testIdx >= len(self.storedItems): break if not self.storedItems[testIdx] in self.items[newIdx:]: self._removeRow(testIdx) self.storedItems.pop(testIdx) oldIdx = testIdx if oldIdx != newIdx: self._removeRow(oldIdx) self._addItem(item, newIdx) self.storedItems.pop(oldIdx) self.storedItems.insert(newIdx, item) found.append(item) except ValueError: self._addItem(item, newIdx) self.storedItems.insert(newIdx, item) found.append(item) else: oldIdx = 0 while oldIdx < len(self.storedItems): item = self.storedItems[oldIdx] if not item in found: self._removeRow(oldIdx) self.storedItems.pop(oldIdx) else: oldIdx += 1 if self.columns is not None: for index, column in enumerate(self.columns): if column.width is None: self.window.SetColumnWidth(index, wx.LIST_AUTOSIZE) else: self.window.SetColumnWidth(index, column.width.value) self.selectionUpdated(None, None) self.updatingSelection = False def _addItem(self, item, position=sys.maxint): """ Add an item at the specified position. """ if self.columns is not None: rowID = None for index, column in enumerate(self.columns): if column.field is not None: if rowID is None: rowID = self.window.InsertStringItem(position, str(self._getColumnData(item, column))) else: self.window.SetStringItem(rowID, index, str(self._getColumnData(item, column))) else: if rowID is None: rowID = self.window.InsertStringItem(position, str(item[index])) else: self.window.SetStringItem(rowID, index, str(item[index])) else: self.window.Append(str(item), item) def _removeRow(self, index): """ Remove the row at the given index. """ if self.columns is not None: # for colIndex in range(len(self.columns)): # item = self.window.GetItem(index, colIndex) self.window.DeleteItem(index) else: self.window.Delete(index) def before(self): if self.columns is not None: self.window.DeleteAllItems() else: self.window.Clear() if self.items is None: self.updatingSelection = False return def _getColumnData(self, item, column): """ Returns the data in a column """ colAttr = column.field.getData() try: for attr in colAttr.split("."): item = getattr(item, attr) except AttributeError: return None return item def destroy(self): """ Destroy the widget """ guide.ListBox.destroy(self) GuideWxWidget.destroy(self) class ListBoxItem(guide.ListBoxItem): """ A list box item """ def __init__(self, **attribs): guide.ListBoxItem.__init__(self, **attribs) self.window = self.parent.window class ListBoxColumn(guide.ListBoxColumn): """ A column of a list box """ def __init__(self, **attribs): guide.ListBoxColumn.__init__(self, **attribs) self.window = self.parent.window class PythonConsole(GuideWxWidget, guide.PythonConsole): """ Frame with an interactive python console """ def __init__(self, **attribs): guide.PythonConsole.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) self.crustFrame = None window = self.findParent(Window) window.listen("onShow", self._createCrustFrame) if window.visibility == Visibility.visible: self._createCrustFrame(None, None) def _createCrustFrame(self, _obj, _prop): """ Create the PyCrust frame (deferred until the parent window is ready) """ if self.crustFrame is None: sizer = wx.BoxSizer(wx.HORIZONTAL) self.crustFrame = Crust(parent=self.window, locals=self.locals, size=self.size) sizer.Add(self.crustFrame, 100, wx.ALL|wx.EXPAND) self.window.SetSizer(sizer) class StackFrame(GuideWxWidget, guide.StackFrame): """ A panel with a box sizer """ def __init__(self, **attribs): guide.StackFrame.__init__(self, **attribs) style = getBorderFlags(self) self.window = wx.Panel(self.parent.window, size=self.size, style=style) GuideWxWidget.__init__(self) def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.StackFrame.onGetChildren(self, _parser) box = addWidgetsToSizer(self, self.children, self.direction) self.window.SetSizer(box) self.window.Layout() def destroy(self): """ Destroy the widget """ guide.StackFrame.destroy(self) GuideWxWidget.destroy(self) class WrapFrame(GuideWxWidget, guide.WrapFrame): """ A panel with a box sizer """ def __init__(self, **attribs): guide.WrapFrame.__init__(self, **attribs) style = getBorderFlags(self) self.window = wx.Panel(self.parent.window, size=self.size, style=style) GuideWxWidget.__init__(self) def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.WrapFrame.onGetChildren(self, _parser) box = addWidgetsToSizer(self, self.children, self.direction, sizer=wx.WrapSizer()) self.window.SetSizer(box) self.window.Layout() def destroy(self): """ Destroy the widget """ guide.WrapFrame.destroy(self) GuideWxWidget.destroy(self) class Position(GuideWxWidget, guide.Position): """ A widget that holds a position """ _updating = False def __init__(self, **attribs): guide.Position.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) GuideWxWidget.__init__(self) width = self.width or 100 vector = self.vector or (0, 0, 0) self.xSpin = Spinner(parent=self, height=self.height, width=width / 3, value=vector[0], decimalPlaces=2) self.ySpin = Spinner(parent=self, height=self.height, width=width / 3, value=vector[1], decimalPlaces=2) self.zSpin = Spinner(parent=self, height=self.height, width=width / 3, value=vector[2], decimalPlaces=2) self.children = [self.xSpin, self.ySpin, self.zSpin] self.xSpin.listen("value", self._valueChanged) self.ySpin.listen("value", self._valueChanged) self.zSpin.listen("value", self._valueChanged) self.listen("vector", self._valueUpdated) def _valueChanged(self, _obj, _prop): """ The value has been changed by the UI """ if self._updating: return self._updating = True self.vector = Vector(self.xSpin.value, self.ySpin.value, self.zSpin.value) self._updating = False def _valueUpdated(self, _obj, _prop): """ The value has been changed outside of the UI """ if self._updating: return self._updating = True if self.vector is not None: self.xSpin.value = self.vector[0] self.ySpin.value = self.vector[1] self.zSpin.value = self.vector[2] self._updating = False def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.Position.onGetChildren(self, _parser) box = wx.BoxSizer(wx.HORIZONTAL) for child in self.children: box.Add(child.window) self.window.SetSizer(box) self.window.Layout() def destroy(self): """ Destroy the widget """ guide.Position.destroy(self) GuideWxWidget.destroy(self) class Orientation(GuideWxWidget, guide.Orientation): """ A widget that holds an orientation """ _updating = False def __init__(self, **attribs): guide.Orientation.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) GuideWxWidget.__init__(self) width = self.width or 100 if self.euler is None and self.quaternion is not None: self.euler = Quaternion(self.quaternion).toEuler() elif self.quaternion is None and self.euler is not None: self.quaternion = Quaternion.fromEuler(self.euler) euler = self.euler or (0, 0, 0) self.xSpin = Spinner(parent=self, height=self.height, width=width / 3, value=euler[0], decimalPlaces=2) self.ySpin = Spinner(parent=self, height=self.height, width=width / 3, value=euler[1], decimalPlaces=2) self.zSpin = Spinner(parent=self, height=self.height, width=width / 3, value=euler[2], decimalPlaces=2) self.children = [self.xSpin, self.ySpin, self.zSpin] self.xSpin.listen("value", self._valueChanged) self.ySpin.listen("value", self._valueChanged) self.zSpin.listen("value", self._valueChanged) self.listen("euler", self._eulerUpdated) self.listen("quaternion", self._quaternionUpdated) def _valueChanged(self, _obj, _prop): """ The value has been changed by the UI """ if self._updating: return self._updating = True self.euler = Vector(self.xSpin.value, self.ySpin.value, self.zSpin.value) self.quaternion = Quaternion.fromEuler(self.euler) self._updating = False def _eulerUpdated(self, _obj, _prop): """ The euler value has been changed outside of the UI """ if self._updating: return self._updating = True if self.euler is not None: self.xSpin.value = self.euler[0] self.ySpin.value = self.euler[1] self.zSpin.value = self.euler[2] self.quaternion = Quaternion.fromEuler(self.euler) self._updating = False def _quaternionUpdated(self, _obj, _prop): """ The quaternion value has been changed outside of the UI """ if self._updating: return self._updating = True if self.quaternion is not None: self.euler = Quaternion(self.quaternion).toEuler() self.xSpin.value = self.euler[0] self.ySpin.value = self.euler[1] self.zSpin.value = self.euler[2] self._updating = False def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.Orientation.onGetChildren(self, _parser) box = wx.BoxSizer(wx.HORIZONTAL) for child in self.children: box.Add(child.window) self.window.SetSizer(box) self.window.Layout() def destroy(self): """ Destroy the widget """ guide.Orientation.destroy(self) GuideWxWidget.destroy(self) class SplitFrame(GuideWxWidget, guide.SplitFrame): """ A panel with a splitter between elements """ updating = False def __init__(self, **attribs): guide.SplitFrame.__init__(self, **attribs) self.window = wx.SplitterWindow(self.parent.window, size=self.size, style=wx.SP_LIVE_UPDATE | wx.SP_3DSASH | wx.SP_NO_XP_THEME) GuideWxWidget.__init__(self) self.window.Bind(wx.EVT_SIZE, self._sashUpdated) self.window.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self._sashChanged) self.listen("sashOffset", self._sashUpdated) def _sashChanged(self, _event): """ The user changed the sash position """ if self.updating: return self.updating = True self.sashOffset = self.window.GetSashPosition() self.updating = False def _sashUpdated(self, _obj, _prop=None): """ The sash position was set outside of the UI """ if self.updating: return self.updating = True self.window.SetSashPosition(int(self.sashOffset)) self.updating = False def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.SplitFrame.onGetChildren(self, _parser) if len(self.children) != 2: raise ValueError("SplitFrame requires exactly 2 children.") self.window.SetMinimumPaneSize(20) if self.direction == Direction.horizontal: self.window.SplitHorizontally(self.children[0].window, self.children[1].window, self.sashOffset) else: self.window.SplitVertically(self.children[0].window, self.children[1].window, self.sashOffset) self.window.SetSashSize(5) # hack from Zenex to make the sash actually show up. self.children[0].window.SetWindowStyle(wx.BORDER_THEME) self.children[1].window.SetWindowStyle(wx.BORDER_THEME) self._sashUpdated(None, None) def destroy(self): """ Destroy the widget """ guide.SplitFrame.destroy(self) GuideWxWidget.destroy(self) class ScrollFrame(GuideWxWidget, guide.ScrollFrame): """ A scrolled frame. """ def __init__(self, **attribs): guide.ScrollFrame.__init__(self, **attribs) self.window = ScrolledPanel(self.parent.window, size=self.size) GuideWxWidget.__init__(self) def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.ScrollFrame.onGetChildren(self, _parser) box = addWidgetsToSizer(self, self.children, Direction.vertical) self.window.SetSizer(box) self.window.Layout() self.window.SetAutoLayout(1) if ScrolledPanel != wx.Panel: self.window.SetupScrolling() def destroy(self): """ Destroy the widget """ guide.ScrollFrame.destroy(self) GuideWxWidget.destroy(self) class GroupFrame(GuideWxWidget, guide.GroupFrame): """ A panel with a group box and a label """ boxSizer = None def __init__(self, **attribs): guide.GroupFrame.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) self.box = wx.StaticBox(self.window, -1, str(self.text)) GuideWxWidget.__init__(self) def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical boxsizer """ guide.GroupFrame.onGetChildren(self, _parser) self.boxSizer = wx.StaticBoxSizer(self.box, wx.VERTICAL) sizer = addWidgetsToSizer(self, self.children, self.direction) self.boxSizer.Add(sizer, 1, wx.EXPAND | wx.ALL) self.sizer = wx.BoxSizer() self.sizer.Add(self.boxSizer, 1, wx.EXPAND | wx.ALL) self.window.SetSizer(self.sizer) self.window.Layout() def destroy(self): """ Destroy the widget """ guide.GroupFrame.destroy(self) GuideWxWidget.destroy(self) class ListFrame(GuideWxWidget, guide.ListFrame): """ A panel with a box sizer """ box = None lastSelection = None def __init__(self, **attribs): guide.ListFrame.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) GuideWxWidget.__init__(self) self.progressBar = ProgressBar(parent=self.parent) self.progressBar.initialize(self.getParser()) self.progressBar.hide() self.listen("selection", self._selectionUpdated) self.listen("updateProgress", self._progressUpdated) def _progressUpdated(self, _obj, _prop): """ Our update progress has changed. """ self.progressBar.progress = self.updateProgress def _deselectChild(self, item): """ Deselect a Child """ for checkItem, bgColor in self.lastSelection: if checkItem == item: item.backgroundColor = bgColor self.lastSelection.remove((checkItem, bgColor)) def _deselectAll(self): """ Deselect all selected children """ if self.lastSelection is None: return for item, bgColor in self.lastSelection: item.backgroundColor = bgColor self.lastSelection = None def _selectChild(self, item): """ Select a Child. """ if self.lastSelection is None: self.lastSelection = [] self.lastSelection.append((item, item.backgroundColor or (255, 255, 255))) item.backgroundColor = self.selectionBackgroundColor def _selectionUpdated(self, _obj, _prop): """ The selection was changed outside of the UI. """ if self.updating: return if not isinstance(self.selection, (list, tuple)): selection = [self.selection] else: selection = self.selection self._deselectAll() for item in selection: for child in self.children: if child.dataContext == item: self._selectChild(child) def _selectionChanged(self, child, _prop): """ Select the child. """ if self.updating: return while child.parent != self: child = child.parent self.updating = True shift = self.getParser().getKeyState("shift") ctrl = self.getParser().getKeyState("control") if not self.selectMultiple: self._deselectAll() self.selection = [] elif not shift and not ctrl: self._deselectAll() self.selection = [] elif ctrl: if child.dataContext in self.selection: self._deselectChild(child) self.selection.remove(child.dataContext) self.updating = False return self._selectChild(child) self.selection = self.selection + [child.dataContext] self.updating = False @inlineCallbacks def onGetChildren(self, parser, useCache=True): """ Called once our children have been parsed, add them to a vertical boxsizer """ if self.visibility == Visibility.visible: self.window.Hide() if self.items and len(self.items) > 4: self.progressBar.show() if self.box is not None: self.box.Clear() yield guide.ListFrame.onGetChildren(self, parser, useCache) self.box = addWidgetsToSizer(self, self.children, self.direction) self.window.SetSizer(self.box) self.window.Layout() self.box.Layout() self.window.Refresh() if self.isSelectable: def setListener(children): for child in children: child.listen("onLeftDown", self._selectionChanged) setListener(child.children) setListener(self.children) if self.alternatingBackgroundColor is not None: for index, child in enumerate(self.children): if index % 2: child.backgroundColor = self.alternatingBackgroundColor if self.contextMenu is not None: for child in self.children: if child.contextMenu is None: child.contextMenu = self.contextMenu if self.visibility == Visibility.visible: self.window.Show() resizeParents(self) self.progressBar.hide() def destroy(self): """ Destroy the widget """ guide.ListFrame.destroy(self) GuideWxWidget.destroy(self) class GridFrame(GuideWxWidget, guide.GridFrame): """ A panel with a box sizer """ def __init__(self, **attribs): guide.GridFrame.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size, style=getBorderFlags(self)) GuideWxWidget.__init__(self) def onGetChildren(self, _parser): """ Called once our children have been parsed, create the GridSizer and add them to it. """ guide.GridFrame.onGetChildren(self, _parser) if RowColSizer is not None: self.grid = RowColSizer()#self.columnCount, self.rowCount) else: self.grid = wx.GridBagSizer() collapsedRows = 0 for rid, row in enumerate(self.children): if row.visibility == Visibility.collapsed: collapsedRows += 1 continue if row.height is not None and row.height.relative and RowColSizer is not None: self.grid.AddGrowableRow(rid - collapsedRows) collapsedCells = 0 for cid, cell in enumerate(row.children): if cell.visibility == Visibility.collapsed: collapsedCells += 1 continue # pane = wx.Panel(self.window, style=wx.RAISED_BORDER) # for child in cell.children: # child.window.Reparent(pane) box = addWidgetsToSizer(self, cell.children, Direction.vertical) # pane.SetSizer(box) if RowColSizer is not None: self.grid.Add(box, row=rid - collapsedRows, col=cid - collapsedCells, rowspan=row.rowspan, colspan=cell.colspan, flag=getFlags( cell, wx.EXPAND | wx.ALL)) else: self.grid.Add(box, (rid - collapsedRows, cid - collapsedCells), (row.rowspan, cell.colspan)) if cell.width is not None and cell.width.relative and RowColSizer is not None: self.grid.AddGrowableCol(cid - collapsedCells) if cell.height is not None and cell.height.relative and RowColSizer is not None: self.grid.AddGrowableRow(rid - collapsedRows) if collapsedCells == len(row.children): collapsedRows += 1 self.window.SetSizer(self.grid) self.window.Layout() def onChildrenUpdated(self, parser): """ Just call onGetChildren """ self.onGetChildren(parser) self.callNextTick(self._doParentUpdate) def _doParentUpdate(self): """ Layout all the parents """ parser = self.getParser() parent = self.parent while parent != parser: parent.window.Layout() parent = parent.parent def destroy(self): """ Destroy the widget """ guide.GridFrame.destroy(self) GuideWxWidget.destroy(self) class GridRow(guide.GridRow): """ A grid row """ def __init__(self, **attribs): guide.GridRow.__init__(self, **attribs) self.window = self.parent.window self.listen("visibility", self._visibilityUpdated) def onChildrenUpdated(self, parser): """ This call needs to go all the way up to the grid frame. """ self.parent.onChildrenUpdated(parser) def _visibilityUpdated(self, _obj, _prop): """ Called when the visibility changes. """ if self.visibility == Visibility.hidden: for cell in self.children: for child in cell.children: if child.visibility == Visibility.visible: child.window.Show() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) for cell in self.children: for child in cell.children: child.window.Hide() elif self.visibility == Visibility.collapsed: for cell in self.children: for child in cell.children: child.window.Hide() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) else: for cell in self.children: for child in cell.children: if child.visibility == Visibility.visible: child.window.Show() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) class GridCell(guide.GridCell): """ A grid cell """ def __init__(self, **attribs): guide.GridCell.__init__(self, **attribs) self.window = self.parent.window self.listen("visibility", self._visibilityUpdated) def onChildrenUpdated(self, parser): """ This call needs to go all the way up to the grid frame. """ self.parent.onChildrenUpdated(parser) def _visibilityUpdated(self, _obj, _prop): """ Called when the visibility changes. """ if self.visibility == Visibility.hidden: for child in self.children: if child.visibility == Visibility.visible: child.window.Show() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) for child in self.children: child.window.Hide() elif self.visibility == Visibility.collapsed: for child in self.children: child.window.Hide() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) else: for child in self.children: if child.visibility == Visibility.visible: child.window.Show() if not isinstance(self.parent, Parser): self.parent.onChildrenUpdated(self.getParser()) if propgrid is not None: class CustomProperty(propgrid.PyProperty): """ Holding class for custom property definitions. """ class PropertyGrid(GuideWxWidget, guide.PropertyGrid): """ Displays a property grid that can be used to edit the properties of a Python object. Fields can be described manually within the xml or they can be automatically determined if the object is Persistable. """ if propgrid is not None: dataTypeMap = { PropertyDataTypes.string:propgrid.StringProperty, PropertyDataTypes.integer:propgrid.IntProperty, PropertyDataTypes.float:propgrid.FloatProperty, PropertyDataTypes.boolean:propgrid.BoolProperty, PropertyDataTypes.multiLineString:propgrid.LongStringProperty, PropertyDataTypes.directory:propgrid.DirProperty, PropertyDataTypes.file:propgrid.FileProperty, PropertyDataTypes.stringArray:propgrid.ArrayStringProperty, PropertyDataTypes.enum:propgrid.EnumProperty, PropertyDataTypes.date:propgrid.DateProperty, PropertyDataTypes.color:propgrid.ColourProperty, PropertyDataTypes.imageFile:propgrid.ImageFileProperty, PropertyDataTypes.multipleChoice:propgrid.MultiChoiceProperty, } else: dataTypeMap = { PropertyDataTypes.string:TextBox, PropertyDataTypes.integer:Spinner, PropertyDataTypes.float:Spinner, PropertyDataTypes.boolean:CheckBox, PropertyDataTypes.multiLineString:TextBox, PropertyDataTypes.directory:TextBox, PropertyDataTypes.file:TextBox, PropertyDataTypes.stringArray:TextBox, PropertyDataTypes.enum:ComboBox, PropertyDataTypes.date:TextBox, PropertyDataTypes.color:TextBox, PropertyDataTypes.imageFile:TextBox, PropertyDataTypes.multipleChoice:TextBox, } _updating = False def __init__(self, **attribs): guide.PropertyGrid.__init__(self, **attribs) if propgrid is not None: style = propgrid.PG_AUTO_SORT | propgrid.PG_SPLITTER_AUTO_CENTER self.window = propgrid.PropertyGridManager(self.parent.window, size=self.size, style=style) self.window.Bind(propgrid.EVT_PG_CHANGED, self._onItemChanged) else: self.window = wx.Panel(self.parent.window, size=self.size) GuideWxWidget.__init__(self) self.listen("value", self._buildFields) self.propListeners = [] def _buildFields(self, _obj=None, _prop=None): """ If the underlying object changes, or we are just setting up, then we need to build out the fields. """ if propgrid is not None: fields = self._getFields() self.window.Clear() sections = {} self._currentFields = {} while self.propListeners: self.propListeners.pop().remove() for name, field in fields.items(): try: section = sections[field.section] except KeyError: sections[field.section] = [] section = sections[field.section] dataType = self.dataTypeMap[field.dataType] if field.dataType == PropertyDataTypes.enum: names, values = field.getEnumNamesAndValues() value = getattr(self.value, name, names[0]) data = dataType(name, labels=names, values=values, value=names.index(value)) else: data = dataType(name, value=getattr(self.value, name, None)) section.append((field.priority, field.field, data, field)) self.propListeners.append(self.value.listen(name, self._onItemUpdated)) self._currentFields[field.field] = field sections = sorted(sections.iteritems()) for sectionName, sectionItems in sections: sectionItems.sort(reverse=True) self.window.Append(propgrid.PropertyCategory(str(sectionName))) for _priority, _name, dataType, field in sectionItems: self.window.Append(dataType) else: guide.PropertyGrid._buildFields(self) box = addWidgetsToSizer(self, [self._scrollFrame], Direction.vertical) self.window.SetSizer(box) def _onItemChanged(self, event): """ Called when a property is changed in the UI. """ if self._updating: return self._updating = True prop = event.GetProperty() if prop: if self._currentFields[str(prop.GetName())].valueAsString: value = prop.GetValueAsString() else: value = prop.GetValue() setattr(self.value, prop.GetName(), value) self._updating = False def _onItemUpdated(self, obj, prop): """ Called when a property is changed outside the UI. """ if self._updating: return self._updating = True if propgrid is not None: propItem = self.window.GetPropertyByLabel(prop) if propItem: propItem.SetValue(getattr(self.value, prop)) self._updating = False def destroy(self): """ Destroy the widget """ while self.propListeners: self.propListeners.pop().remove() guide.PropertyGrid.destroy(self) GuideWxWidget.destroy(self) class Tree(GuideWxWidget, guide.Tree): """ A tree selector. """ updating = False hasImages = False def __init__(self, **attribs): guide.Tree.__init__(self, **attribs) self.window = wx.TreeCtrl(self.parent.window, size=self.size, style=wx.TR_DEFAULT_STYLE) GuideWxWidget.__init__(self) if self.template is not None: self.template = self.template.produce(self.dataContext, self)[0] self.window.Bind(wx.EVT_TREE_SEL_CHANGED, self._selectionChanged) self.window.Bind(wx.EVT_LEFT_DCLICK, self._leftDoubleClicked) self.window.Bind(wx.EVT_TREE_ITEM_EXPANDED, self._expandedItem) self.window.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self._collapsedItem) self.listen("selection", self._selectionUpdated) self.listen("root", self._rootUpdated) def _findItem(self, data): """ Find the item id associated with the specified data """ item = self.window.GetRootItem() noResult = object() def search(root): if data == self.window.GetItemPyData(root): return root child, cookie = self.window.GetFirstChild(root) while child.IsOk(): result = search(child) if result is not noResult: return result child, cookie = self.window.GetNextChild(root, cookie) return noResult res = search(item) if res is noResult: return return res def _selectionUpdated(self, _obj, _prop): """ The selection was set outside of the GUI """ if self.updating: return self.updating = True if self.selection is None: self.window.UnselectAll() self.updating = False return item = self._findItem(self.selection) if item is None: self.window.UnselectAll() self.updating = False return self.window.SelectItem(item, True) self.window.ScrollTo(item) self.updating = False def _rootUpdated(self, _obj, _prop): """ The root of the tree was updated. """ self.onChildrenUpdated(self.getParser()) def _selectionChanged(self, event): """ The selection has changed """ if self.updating: return self.updating = True sel = self.window.GetSelection() if sel is None: self.selection = None self.updating = False event.Skip() return if not sel.IsOk(): self.selection = None self.updating = False event.Skip() return self.selection = self.window.GetPyData(sel) self.onSelect() self.updating = False event.Skip() def _leftDoubleClicked(self, event): """ The left mouse was double clicked """ self.onDoubleClick() event.Skip() def _expandedItem(self, event): """ An item was expanded """ wxitem = event.GetItem() item = self.window.GetPyData(wxitem) self.onExpand(item) event.Skip() def _collapsedItem(self, event): """ An item was collapsed """ wxitem = event.GetItem() item = self.window.GetPyData(wxitem) self.onCollapse(item) event.Skip() def _childrenUpdated(self, _obj, _prop): """ One of the children was updated. Rebuild the tree """ self.onGetChildren(self.parser) def onGetChildren(self, parser): """ Called once our children have been parsed, make a list of items """ self.parser = parser guide.Tree.onGetChildren(self, parser) self.window.DeleteAllItems() imageList = wx.ImageList(16, 16) if self.template is None: def addItem(child, parent=None): if child is None: return child.loadImage() imageIndex = None if child.image is not None and sys.platform != "darwin": imageIndex = imageList.Add(child.image) if parent is None: childItem = self.window.AddRoot(str(child)) else: childItem = self.window.AppendItem(parent, str(child)) if imageIndex is not None: self.hasImages = True self.window.SetItemImage(childItem, imageIndex, wx.TreeItemIcon_Normal) try: for grandChild in child: addItem(grandChild, childItem) except TypeError: pass addItem(self.root) else: def addItem(child, template, parent=None): if child is None: return template.dataContext = child template.loadImage() imageIndex = None if template.image is not None and sys.platform != "darwin": imageIndex = imageList.Add(template.image) if parent is None: childItem = self.window.AddRoot(str(child)) else: childItem = self.window.AppendItem(parent, str(child)) if imageIndex is not None and not "linux" in sys.platform: self.hasImages = True self.window.SetItemImage(childItem, imageIndex, wx.TreeItemIcon_Normal) self.window.SetPyData(childItem, child) if template.field is None: return if isinstance(child, ChangeNotifier): child.addPropertyListener(template.field, self._childrenUpdated) items = getattr(child, template.field, None) if items is None: return if not len(template.items): for item in items: grandChild = self.window.AppendItem(childItem, str(item)) self.window.SetPyData(grandChild, item) return for item in items: addItem(item, template.items[0], childItem) addItem(self.root, self.template) if self.hasImages: self.window.SetImageList(imageList) self._selectionUpdated(None, None) class TreeItem(guide.TreeItem): """ An item in a tree """ def loadImage(self): """ The image file was updated """ if not self.imageFile: return if os.path.exists(self.imageFile): imageFile = self.imageFile else: imageFile = os.path.abspath(os.path.join(os.path.dirname( mv3d.__file__), "..", self.imageFile)) if not os.path.exists(imageFile): return if "panda" in sys.executable: # hack for running in a p3d fil = open(imageFile, "rb") data = fil.read() fil.close() tempName = "temp." + imageFile.split(".")[-1] fil = open(tempName, "wb") fil.write(data) fil.close() self.image = wx.Image(tempName).ConvertToBitmap() return try: self.image = wx.Image(imageFile).ConvertToBitmap() except wx.PyAssertionError: pass class RenderFrame(GuideWxWidget, guide.RenderFrame): """ A RenderFrame """ renderer = DataAccessor("_renderer") ownScene = DataAccessor("_ownScene", boolify, True) renderWindow = None initializedOgre = False ogreRoot = None renderTask = None gridNode = None gridObject = None def __init__(self, **attribs): guide.RenderFrame.__init__(self, **attribs) self.window = self.renderer.getRendererClass(IWxRenderWindow)( self.renderer, parent=self.parent.window, id=-1, size=self.size) GuideWxWidget.__init__(self) self.window.callWhenInitialized(self._createWindow) self.window.Bind(wx.EVT_KEY_DOWN, self._cameraEvent) self.window.Bind(wx.EVT_KEY_UP, self._cameraEvent) self.window.Bind(wx.EVT_MOUSE_EVENTS, self._cameraEvent) def _createWindow(self, _obj=None, _prop=None): """ Initialize the render window. """ self.sceneNode = self.renderer.getRendererClass(ISceneNode)( self.renderer) self.sceneNode.initialize("SceneNode%s" % self) if self.ownScene: self.sceneNode.setPosition(RenderFrame.nodeOffset) self.nodeOffset = RenderFrame.nodeOffset RenderFrame.nodeOffset = (0, 0, RenderFrame.nodeOffset[1] - 10000) self.camera.setup(self, self.renderer, self.window.renderWindow) if self.showGrid: self.gridNode = self.renderer.getRendererClass(ISceneNode)( self.renderer, self.sceneNode) self.gridNode.initialize("Grid%d" % id(self)) self.gridObject = self.renderer.getRendererClass(IStaticMesh)( self.renderer) self.gridObject.create(os.path.join( os.path.dirname(mv3d.__file__), "..", "media", "models", "grid.%s" % self.renderer.getMeshFileExtension()), "GridObj%d" % id(self)) self.gridNode.addObject(self.gridObject) self.window.setBackgroundColor(self.backgroundColor) def setupCamera(self, newCamera): """ Change what camera we use """ self.camera = newCamera def _cameraEvent(self, wxEvent): """ Handle events by passing them to our camera """ self.camera._wxInputEvent(wxEvent, wx, wxKeyMap) def AcceptsFocus(self): """ Overrides wx.PyWindow.AcceptsFocus() This is required to allow the renderframe to recieve wx key events. """ return True def AcceptsFocusFromKeyboard(self): """ Overrides wx.PyWindow.AcceptsFocusFromKeyboard() This is required to allow the renderframe to recieve wx key events. """ return True def _keyDown(self, event): """ Called when a key is hit. """ self.camera._wxInputEvent(event, wx, wxKeyMap) def takeScreenShot(self, filename): """ Takes a screenshot of the current view. """ self.camera.saveScreenshot(filename) def getViewportRay(self, position, returnOffsetAndDirection=False): """ Returns (origin, normalized direction vector) for the given 2d position on the screen. """ size = self.window.GetClientSize() ray = self.camera.getCameraToViewportRay((position[0] / float(size[0]), position[1] / float(size[1])), returnOffsetAndDirection) return ray def getViewpointLocation(self, position): """ the x and y component are the 2d space, and the z is how far away from the screen """ size = self.Size ray = self.camera.getCameraToViewportRay(position[0] / float(size[0]), position[1] / float(size[1])) return Vector(ray.getPoint(position[2])) def mousePick(self, position): """ Casts a ray at position and returns the object underneath or none """ if self.lockSelection: return if self.space is not None: return self.mousePickPhysics(position) return self.mousePickRenderer(position) def mousePickPhysics(self, position): """ Use the physics engine to pick something. """ offset, direction = self.getViewportRay(position, returnOffsetAndDirection=True) result = self.space.castRay(offset, offset + (direction * 2000)) oldsel = self.selection self.selection = result.mingeom if oldsel != self.selection: self.onMousePick(self.selection) return self.selection def mousePickRenderer(self, position): """ Do mouse picking with the renderer. This is less accurate than using a physics engine, but is better than the Ogre picker. """ size = self.window.Size items = [] toRemove = [] for item in self.items: try: items.append(item.name) except RuntimeError: toRemove.append(item) except AttributeError: pass self.items = [item for item in self.items if not item in toRemove] selection = self.renderer.pickObject( (position[0] / float(size[0]), position[1] / float(size[1])), self.sceneNode.node, camera=self.camera, pickObjects=items) if selection != self.selection: self.selection = selection self.onMousePick(self.selection) return self.selection class MenuBar(guide.MenuBar): """ A menu bar """ def __init__(self, **attribs): guide.MenuBar.__init__(self, **attribs) self.window = wx.MenuBar() self.parent.window.SetMenuBar(self.window) def onGetChildren(self, parser): """ Called once our children have been parsed, create the GridSizer and add them to it. """ guide.MenuBar.onGetChildren(self, parser) for child in self.children: self.window.Append(child.window, child.text) class Menu(guide.Menu): """ A menu """ def __init__(self, **attribs): guide.Menu.__init__(self, **attribs) self.window = wx.Menu() def onGetChildren(self, parser): """ Called once our children have been parsed, create the GridSizer and add them to it. """ guide.Menu.onGetChildren(self, parser) for menuItem in self.window.MenuItems: self.window.RemoveItem(menuItem) for child in self.children: if isinstance(menuItem, wx.MenuItem) and ( child.window.GetId() == menuItem.GetId()): child.window = menuItem for child in self.children: if isinstance(child, Menu): self.window.AppendMenu(-1, child.text, child.window) else: self.window.AppendItem(child.window) if not child.enabled: child.window.Enable(False) if isinstance(child, MenuItem) and child.itemType == MenuItemType.check: child._selectedUpdated(None, None) class MenuItem(guide.MenuItem): """ A menu item """ ignoreEvents = False def __init__(self, **attribs): guide.MenuItem.__init__(self, **attribs) style = wx.ITEM_NORMAL self.menuID = wx.NewId() if self.itemType == MenuItemType.check: style = wx.ITEM_CHECK elif self.itemType == MenuItemType.radio: style = wx.ITEM_RADIO elif self.itemType == MenuItemType.separator: style = wx.ITEM_SEPARATOR self.menuID = wx.ID_SEPARATOR elif self.itemType == MenuItemType.normal: pass else: raise ValueError("Invalid menu item type %s!" % self.itemType) if self.text is not None: text = self.text.replace("\\t", "\t") else: text = "" self.window = wx.MenuItem(self.parent.window, self.menuID, str(text), kind=style) if self.subMenu is not None: self.subMenu = self.subMenu.produce(self.dataContext, self)[0] self.window.SetSubMenu(self.subMenu.window) if self.itemType != MenuItemType.separator: self.listen("subMenu", self._subMenuUpdated) parent = self.parent while isinstance(parent, (Menu, MenuBar, MenuItem, Toolbar)): parent = parent.parent parent.window.Bind(wx.EVT_MENU, self._menuClicked, id=self.menuID) if self.itemType in [MenuItemType.check, MenuItemType.radio]: if self.itemType != MenuItemType.radio or self.selected: parent = self.parent while not isinstance(parent, Window): parent = parent.parent parent.listen("onShow", self._setSelection) self.listen("selected", self._selectedUpdated) if self.image is not None: self.window.SetBitmap(self.image) def _subMenuUpdated(self, _obj, _prop): """ The subMenu was set """ self.window.SetSubMenu(self.subMenu.window) def _setSelection(self, window, _prop): """ Have to do this when shown """ window.stopListening("onShow", self._setSelection) self._selectedUpdated(None, None) def _menuClicked(self, _event): """ The user clicked us! """ self.ignoreEvents = True self.selected = self.window.IsChecked() self.ignoreEvents = False self.onClick() def _selectedUpdated(self, _obj, _prop): """ The selection status was changed """ if self.window.IsChecked() != self.selected and not self.ignoreEvents: self.window.Toggle() class Toolbar(GuideWxWidget, guide.Toolbar): """ A toolbar widget. """ def __init__(self, **attribs): guide.Toolbar.__init__(self, **attribs) style = AUI_TB_DEFAULT_STYLE style |= (aui.AUI_TB_TEXT * self.showText) style |= (aui.AUI_TB_OVERFLOW * self.showOverflow) if self.direction == Direction.vertical: style |= aui.AUI_TB_VERTICAL #| aui.AUI_TB_VERT_TEXT if self.showText: if self.textLocation == ToolbarTextLocations.left: style |= aui.AUI_TBTOOL_TEXT_LEFT elif self.textLocation == ToolbarTextLocations.right: style |= aui.AUI_TBTOOL_TEXT_RIGHT elif self.textLocation == ToolbarTextLocations.top: style |= aui.AUI_TBTOOL_TEXT_TOP elif self.textLocation == ToolbarTextLocations.bottom: style |= aui.AUI_TBTOOL_TEXT_BOTTOM if AuiToolBar is not None: self.window = AuiToolBar(self.parent.window, id= -1, style=style) else: self.window = wx.Panel(self.parent.window) self.pane = AuiPaneInfo().ToolbarPane() self.pane.Name(self.name or str(id(self))) self.pane.Caption(self.text) self.pane.PaneBorder(self.hasBorder) self.pane.Movable(self.movable) if not self.movable: self.pane.Fixed() self.pane.DockFixed() self.pane.Gripper(self.hasGripper) self.pane.Dockable(self.dockable) self.pane.Floatable(self.floatable) GuideWxWidget.__init__(self) def _updateLocation(self): """ Updates the dock location and position. """ self.pane.ToolbarPane() self.pane.Name(self.name or str(id(self))) self.pane.Caption(self.text) self.pane.PaneBorder(self.hasBorder) self.pane.Movable(self.movable) self.pane.Gripper(self.hasGripper) self.pane.Dockable(self.dockable) self.pane.Floatable(self.floatable) if self.location == DockLocations.top: self.pane.Top() elif self.location == DockLocations.bottom: self.pane.Bottom() elif self.location == DockLocations.left: self.pane.Left() elif self.location == DockLocations.right: self.pane.Right() elif self.location == DockLocations.center: self.pane.Center() elif self.location == DockLocations.float: self.pane.Float() else: self.pane.Left() if not self.location == DockLocations.float: self.pane.Dock() self.pane.Position(self.position) self.pane.Layer(self.layer) size = [-1, -1] if self.width is not None: size[0] = self.width.asAbsolute(self.parent.window.GetSize()[0]) if self.height is not None: size[1] = self.height.asAbsolute(self.parent.window.GetSize()[1]) if size != [-1, -1]: self.pane.BestSize(tuple(size)) if AuiToolBar is not None: self.window.Realize() self.parent.auiManager.Update() def onParentResized(self): """ Our parent size was updated. """ guide.Toolbar.onParentResized(self) self._updateLocation() def _visibilityUpdated(self, _obj, _prop): """ Called when the visibility changes. """ if (self.visibility == Visibility.hidden or self.visibility == Visibility.collapsed): self.pane.Show(False) else: self.pane.Show(True) self.parent.auiManager.Update() def onGetChildren(self, _parser): """ Called once our children have been parsed, append them to the list of selections. """ guide.Toolbar.onGetChildren(self, _parser) if AuiToolBar is None: return # sorry, nothing else can be done :( self.window.Clear() radioGroup = [] for toolID, child in enumerate(self.children): if isinstance(child, ToolbarItem): if child.type == ToolbarItemType.button: kind = ITEM_NORMAL elif child.type == ToolbarItemType.label: kind = ITEM_LABEL elif child.type == ToolbarItemType.spacer: kind = ITEM_SPACER elif child.type == ToolbarItemType.separator: kind = ITEM_SPACER elif child.type == ToolbarItemType.radio: kind = ITEM_RADIO elif child.type == ToolbarItemType.check: kind = ITEM_CHECK else: raise ValueError("Unknown type %s" % self.type) if child.type == ToolbarItemType.spacer: self.window.AddSpacer(child.border or 2) elif child.type == ToolbarItemType.separator: self.window.AddSeparator() elif child.type == ToolbarItemType.label: self.window.AddLabel(toolID, str(child.text)) child.window = self.window.FindTool(toolID) else: self.window.AddTool(toolID, child.text, child.image, str(child.tooltip or ""), kind) child.window = self.window.FindTool(toolID) self.window.Bind(wx.EVT_TOOL, child.click, id=toolID) child._onCheckUpdated(None, None) if child.dropDownMenu is not None: self.window.SetToolDropDown(child.window.GetId(), True) self.window.Bind(aui.EVT_AUITOOLBAR_TOOL_DROPDOWN, child._onDropDown, id=child.window.GetId()) if child.type == ToolbarItemType.radio: if not radioGroup: radioGroup.append([child]) else: radioGroup[0].append(child) child._radioGroup = radioGroup[0] else: if radioGroup: radioGroup.pop() self.window.EnableTool(toolID, bool(child.enabled)) else: self.window.AddControl(child.window, str(child.text)) self.window.Realize() self.parent.auiManager.AddPane(self.window, self.pane) self.pane = self.parent.auiManager.GetPane(self.name or str(id(self))) self._updateLocation() def destroy(self): """ Destroy the widget """ guide.Toolbar.destroy(self) GuideWxWidget.destroy(self) class ToolbarItem(guide.ToolbarItem): """ An item in a toolbar. """ window = None _radioGroup = None _ignoreUpdates = False def __init__(self, **attribs): guide.ToolbarItem.__init__(self, **attribs) self.listen("text", self._textUpdated) self.listen("checked", self._onCheckUpdated) self.listen("tooltip", self._tooltipUpdated) def _onDropDown(self, event): """ Display the dropdown menu! """ self.parent.window.SetToolSticky(event.GetId(), True) menu = self.dropDownMenu.produce(self.dataContext, self.parent)[0] rect = self.parent.window.GetToolRect(event.GetId()) pt = self.parent.window.ClientToScreen(rect.GetBottomLeft()) pt = self.parent.parent.window.ScreenToClient(pt) self.parent.parent.window.PopupMenu(menu.window, pt) self.parent.window.SetToolSticky(event.GetId(), False) def _onCheckUpdated(self, _obj, _prop): """ The check status was updated outside of the UI. """ if self._ignoreUpdates: return if AuiToolBar is None: return self._ignoreUpdates = True state = self.window.GetState() if self.checked and not state & aui.AUI_BUTTON_STATE_CHECKED: self.parent.window.ToggleTool(self.window.GetId(), aui.AUI_BUTTON_STATE_CHECKED) self.window.SetState(aui.AUI_BUTTON_STATE_CHECKED) self.parent.window.Realize() elif not self.checked and state & aui.AUI_BUTTON_STATE_CHECKED: self.parent.window.ToggleTool(self.window.GetId(), aui.AUI_BUTTON_STATE_NORMAL) self.window.SetState(aui.AUI_BUTTON_STATE_NORMAL) self.parent.window.Realize() self._ignoreUpdates = False def _textUpdated(self, _obj, _prop): """ The text was updated. """ if AuiToolBar is None: return self.window.SetLabel(str(self.text)) self.parent.window.Realize() self.parent.parent.auiManager.Update() def _tooltipUpdated(self, _obj, _prop): """ Called when the tooltip changes """ if AuiToolBar is None: return if self.tooltip is not None: self.window.SetShortHelp(str(self.tooltip)) else: self.window.SetShortHelp("") def enable(self): """ Enable the toolbar item """ guide.ToolbarItem.enable(self) if AuiToolBar is None: return self.parent.window.EnableTool(self.window.GetId(), True) self.parent.window.Realize() def disable(self): """ Disable the toolbar item """ guide.ToolbarItem.disable(self) if AuiToolBar is None: return self.parent.window.EnableTool(self.window.GetId(), False) self.parent.window.Realize() def click(self, _event=None): """ The item was clicked. """ if self._ignoreUpdates: return if self.type == ToolbarItemType.radio: self.checked = bool( self.window.GetState() & aui.AUI_BUTTON_STATE_CHECKED) for other in self._radioGroup: if other is self: continue other.checked = not self.checked self.checked = self.checked # mdh: hack to fix wxPython strangeness elif self.type == ToolbarItemType.check: self.checked = bool( self.window.GetState() & aui.AUI_BUTTON_STATE_CHECKED) self._onClick(self) def destroy(self): """ Destroy the widget """ self.parent.children.remove(self) self.parent.onGetChildren(self.getParser()) guide.ToolbarItem.destroy(self) class Image(guide.Image, GuideWxWidget): """ Displays a static image """ def __init__(self, **attribs): guide.Image.__init__(self, **attribs) if self.imageFile is not None: self._imageFileUpdated(None, None) self.window = wx.StaticBitmap(self.parent.window, -1, bitmap=self._convertImage(self.image), size=self.size) GuideWxWidget.__init__(self) self.listen("imageFile", self._imageFileUpdated) self.listen("image", self._imageUpdated) def _convertImage(self, image): """ Converts to the correct type of image. """ if hasattr(image, "paste"): image = pilToImage(image).ConvertToBitmap() elif image is None: return wx.NullBitmap return image def _imageFileUpdated(self, _obj, _prop): """ The image file was updated """ if os.path.exists(self.imageFile): imageFile = self.imageFile else: imageFile = os.path.abspath(os.path.join(os.path.dirname( mv3d.__file__), "..", self.imageFile)) if not os.path.exists(imageFile): return if "panda" in sys.executable: # hack for running in a p3d fil = open(imageFile, "rb") data = fil.read() fil.close() tempName = "temp." + imageFile.split(".")[-1] fil = open(tempName, "wb") fil.write(data) fil.close() self.image = wx.Image(tempName).ConvertToBitmap() return try: self.image = wx.Image(imageFile).ConvertToBitmap() except wx.PyAssertionError: pass def _imageUpdated(self, _obj, _prop): """ The image object was updated. """ if self.window is None: return self.window.SetBitmap(self._convertImage(self.image)) def destroy(self): """ Destroy the widget """ guide.Image.destroy(self) GuideWxWidget.destroy(self) class DockableFrame(WidgetWithLabel, guide.DockableFrame): """ Uses AUI to create a frame that can be docked inside its parent window """ def __init__(self, **attribs): guide.DockableFrame.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) self.window.Freeze() self.pane = AuiPaneInfo() self._updatePane() WidgetWithLabel.__init__(self) self.parent.window.Bind(aui.EVT_AUI_PANE_CLOSE, self._handleClose) self.auiManager = aui.AuiManager() self.auiManager.SetManagedWindow(self.window) def _handleClose(self, event): """ Pass it on """ if event.GetPane().window == self.window: self.onClose() else: event.Skip() def _updatePane(self): """ Update our pane """ self.pane.CloseButton(self.hasCloseButton) self.pane.MaximizeButton(self.hasMaximizeButton) self.pane.MinimizeButton(self.hasMinimizeButton) self.pane.Name(self.name or str(id(self))) self.pane.Caption(self.text) self.pane.PaneBorder(self.hasBorder) self.pane.Movable(self.movable) self.pane.Gripper(self.hasGripper) self.pane.PinButton(self.hasPinButton) self.pane.Dockable(self.dockable) self.pane.Floatable(self.floatable) self.pane.CaptionVisible(self.hasTitleBar) self.pane.DockFixed(False) def onGetChildren(self, _parser): """ Called once our children have been parsed, add them to a vertical sizer """ size = (20, 20) if self.width is None: self.width = Coordinate(-1) if self.height is None: self.height = Coordinate(-1) if Coordinate(size[0]) > self.width - 20: self.width = size[0] + 20 if Coordinate(size[1]) > self.height - 40: self.height = size[1] + 40 self.parent.auiManager.AddPane(self.window, self.pane) self.pane = self.parent.auiManager.GetPane(self.name or str(id(self))) self.pane.MinSize((self.width.asAbsolute(self.parent.window.GetSize()[0]), self.height.asAbsolute(self.parent.window.GetSize()[1]))) self._updateLocation() # self.auiManager.Update() # here be ghosts! box = addWidgetsToSizer(self, self.children, Direction.vertical) self.window.SetSizer(box) self.window.Layout() box.Layout() self.pane.FloatingSize(self.window.GetSize()) if self.toolbar: if not isinstance(self.toolbar, list): self.toolbar = self.toolbar.produce(self.dataContext, self) for toolbar in self.toolbar: toolbar.onParentResized() self._visibilityUpdated(None, None) self.window.Thaw() def _updateLocation(self): """ Updates the dock location and position. """ if self.location == DockLocations.top: self.pane.Top() elif self.location == DockLocations.bottom: self.pane.Bottom() elif self.location == DockLocations.left: self.pane.Left() elif self.location == DockLocations.right: self.pane.Right() elif self.location == DockLocations.center: self.pane.Center() elif self.location == DockLocations.float: self.pane.Float() elif self.location == DockLocations.centerPane: self.pane.CenterPane() else: self.pane.Left() if not self.location == DockLocations.float: self.pane.Dock() self.pane.Position(self.position) self.parent.auiManager.Update() def _visibilityUpdated(self, _obj, _prop): """ Called when the visibility changes. """ if self.visibility == Visibility.visible: self.pane.Show(True) else: self.pane.Show(False) self.parent.auiManager.Update() def destroy(self): """ Destroy the widget """ guide.DockableFrame.destroy(self) WidgetWithLabel.destroy(self) def _sizeChanged(self, obj, prop=None): """ Called when the window's size changes. """ if isinstance(self.toolbar, TemplateFactory): return WidgetWithLabel._sizeChanged(self, obj, prop=prop) for toolbar in self.toolbar: toolbar.onParentResized() return WidgetWithLabel._sizeChanged(self, obj, prop=prop) class TabFrame(GuideWxWidget, guide.TabFrame): """ A frame that holds one or more tabs. """ updating = False def __init__(self, **attribs): guide.TabFrame.__init__(self, **attribs) self.window = AuiNotebook(self.parent.window, size=self.size) self.window.SetMinSize(self.size) GuideWxWidget.__init__(self) self.window.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._pageClosed) self.window.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self._pageChanged) self.listen("selection", self._pageUpdated) def _pageClosed(self, event): """ Called when the user closes a page. """ window = self.window.GetPage(event.GetSelection()) for child in self.children: if child.window == window: child.onClose() self.children.remove(child) guide.Tab.destroy(child) break def _pageChanged(self, event): """ The user changed the page """ if self.updating: return self.updating = True self.selection = self.children[self.window.GetSelection()] self.updating = False def _pageUpdated(self, _obj, _prop): """ A page was changed outside of the UI. """ if self.updating: return self.updating = True if self.selection is not None: self.window.SetSelection(self.window.GetPageIndex( self.selection.window)) self.updating = False def onGetChildren(self, parser): """ Called once our children have been parsed, add them to a vertical sizer """ guide.TabFrame.onGetChildren(self, parser) self.updating = True while self.window.PageCount: self.window.RemovePage(0) for child in self.children: self.window.AddPage(child.window, str(child.text)) if self.selection is not None: self.window.SetSelection(self.window.GetPageIndex( self.selection.window)) self.updating = False def removeTab(self, tab): """ Removes a tab """ self.children.remove(tab) self.window.RemovePage(self.window.GetPageIndex(tab.window)) tab.destroy() def destroy(self): """ Destroy the widget """ guide.TabFrame.destroy(self) GuideWxWidget.destroy(self) class Tab(WidgetWithLabel, guide.Tab): """ A tab in a TabFrame """ def __init__(self, **attribs): guide.Tab.__init__(self, **attribs) self.window = wx.Panel(self.parent.window, size=self.size) GuideWxWidget.__init__(self) def onGetChildren(self, parser): """ Called once our children have been parsed, add them to a vertical sizer """ guide.Tab.onGetChildren(self, parser) box = addWidgetsToSizer(self, self.children, Direction.vertical) self.window.SetSizer(box) self.window.Layout() box.Layout() # size = box.GetMinSize() # if self.width is None: # self.width = -1 # if self.height is None: # self.height = -1 # if size[0] > self.width.value - 20: # self.width = size[0] + 20 # if size[1] > self.height.value - 40: # self.height = size[1] + 40 def destroy(self): """ Destroy the widget """ guide.Tab.destroy(self) GuideWxWidget.destroy(self) class ExpandFrame(WidgetWithLabel, guide.ExpandFrame): """ A frame that can expand or contract """ def __init__(self, **attribs): guide.ExpandFrame.__init__(self, **attribs) self.window = PyCollapsiblePane(self.parent.window, label=str(self.text), size=self.size, agwStyle=wx.CP_NO_TLW_RESIZE | wx.CP_USE_STATICBOX) #@UndefinedVariable self.window.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self._expandChanged) self.listen("expanded", self._onExpandUpdated) WidgetWithLabel.__init__(self) def _expandChanged(self, _event): """ The expander changed states """ self.parent.window.Layout() def _onExpandUpdated(self, _obj, _prop): """ The expand parameter was changed """ self.window.Collapse(not self.expanded) def onGetChildren(self, parser): """ Called once our children have been parsed, add them to a vertical sizer """ guide.ExpandFrame.onGetChildren(self, parser) for child in self.children: child.window.Reparent(self.window.GetPane()) box = addWidgetsToSizer(self, self.children, Direction.vertical) self.window.GetPane().SetSizer(box) self.window.GetPane().Layout() box.Layout() size = box.GetMinSize() if self.width is None: self.width = -1 if self.height is None: self.height = -1 if Coordinate(size[0]) > self.width - 20: self.width = size[0] + 20 if Coordinate(size[1]) > self.height - 40: self.height = size[1] + 40 self.window.Collapse(not self.expanded) # handle the case of this not existing # it's only available in newer wx distribs. if PyCollapsiblePane is None: ExpandFrame = StackFrame class FileSelector(guide.FileSelector): """ Opens a file selector window as a modal dialog """ def show(self): """ Show the dialog """ # TODO: don't use ShowModal, use events instead. self.deferred = Deferred() guide.FileSelector.show(self) if self.operation == "directory": dlg = wx.DirDialog(self.parent.window, message=self.text, defaultPath=self.directory) else: style = wx.OPEN if self.operation == "save": style = wx.SAVE if self.selectMultiple: style |= wx.FD_MULTIPLE dlg = wx.FileDialog( self.parent.window, message=self.text, defaultDir=self.directory, defaultFile=self.fileName, wildcard=self.pattern, style=style ) if dlg.ShowModal() == wx.ID_OK: self.fileName = dlg.GetPath() if self.selectMultiple: self.selection = dlg.GetPaths() else: self.selection = dlg.GetPath() self.onSelect() self.deferred.callback(self.selection) else: self.onCancel() self.deferred.errback(RuntimeError("User hit Cancel")) return self.deferred class MessageDialog(guide.MessageDialog): """ Opens a message dialog. """ _deferred = None @inlineCallbacks def show(self): """ Show the dialog """ assert self._deferred is None self._deferred = Deferred() style = 0 if self.buttons & DialogButton.ok: style |= wx.OK if self.buttons & DialogButton.cancel: style |= wx.CANCEL if self.buttons & DialogButton.yes: style |= wx.YES if self.buttons & DialogButton.no: style |= wx.NO if self.buttons & DialogButton.help: style |= wx.HELP if self.iconType == DialogIcon.info: style |= wx.ICON_INFORMATION elif self.iconType == DialogIcon.warning: style |= wx.ICON_WARNING elif self.iconType == DialogIcon.exclaimation: style |= wx.ICON_EXCLAMATION elif self.iconType == DialogIcon.error: style |= wx.ICON_ERROR elif self.iconType == DialogIcon.question: style |= wx.ICON_QUESTION if GenericMessageDialog is not None: dialog = GenericMessageDialog(self.parent.window, self.text, self.title, style) if self.image is not None: dialog.SetIcon(self.image) else: dialog = wx.MessageDialog(self.parent.window, self.text, self.title, style) dialog.Bind(wx.EVT_BUTTON, self._okClicked, id=wx.ID_OK) dialog.Bind(wx.EVT_BUTTON, self._cancelClicked, id=wx.ID_CANCEL) dialog.Bind(wx.EVT_BUTTON, self._yesClicked, id=wx.ID_YES) dialog.Bind(wx.EVT_BUTTON, self._noClicked, id=wx.ID_NO) dialog.Bind(wx.EVT_BUTTON, self._helpClicked, id=wx.ID_HELP) dialog.Bind(wx.EVT_CLOSE, self._cancelClicked) dialog.Show() result = yield self._deferred dialog.Destroy() returnValue(result) def _okClicked(self, event): """ The ok button was clicked. """ self.onDismiss(DialogButton.ok) self.onOk() self._deferred.callback(DialogButton.ok) def _cancelClicked(self, event): """ The cancel button was clicked. """ self.onDismiss(DialogButton.cancel) self.onCancel() self._deferred.callback(DialogButton.cancel) def _yesClicked(self, event): """ The yes button was clicked. """ self.onDismiss(DialogButton.yes) self.onYes() self._deferred.callback(DialogButton.yes) def _noClicked(self, event): """ The no button was clicked. """ self.onDismiss(DialogButton.no) self.onNo() self._deferred.callback(DialogButton.no) def _helpClicked(self, event): """ The help button was clicked. """ self.onDismiss(DialogButton.help) self.onHelp() self._deferred.callback(DialogButton.help) if not "darwin" in sys.platform: class _PopupThatHandlesDismissEvent(wx.PopupTransientWindow): """ Subclass so that we can handle onDismiss """ def __init__(self, owner, *args, **kwargs): wx.PopupTransientWindow.__init__(self, *args, **kwargs) self.owner = owner def OnDismiss(self): """ We were dismissed """ self.owner._dismissed() else: class _PopupThatHandlesDismissEvent(wx.Frame): """ Not supported on OS X, so we fake it with a full blown Window. """ def __init__(self, owner, parent, *args, **kwargs): wx.Frame.__init__(self, parent) self.owner = owner self.Bind(wx.EVT_CLOSE, self.onClose) def onClose(self, _event): """ Called when the user closes the window. """ self.owner._dismissed() def Popup(self): """ Just show the window. """ self.Show() def Dismiss(self): """ Hide the window """ self.Hide() def Position(self, pos, size): """ This should probably set the position and size of the window, but that wasn't working on OS X. """ class PopupWindow(guide.PopupWindow, GuideWxWidget): """ A popup window is a very simple frame. Keep this in mind: http://trac.wxwidgets.org/ticket/2043 You can not use text boxes inside popup windows. """ def __init__(self, **attribs): guide.PopupWindow.__init__(self, **attribs) if self.isTransient: self.window = _PopupThatHandlesDismissEvent(self, self.parent.window, getBorderFlags(self)) else: self.window = wx.PopupWindow(self.parent.window, getBorderFlags(self)) GuideWxWidget.__init__(self) def _visibilityUpdated(self, _obj, _prop): """ Called when the visibility changes. """ pos = self.parent.window.ClientToScreen((0, 0)) size = self.parent.window.GetSize() self.window.Position(pos, (self.left.asAbsolute(size[0]), self.top.asAbsolute(size[1]))) if self.isTransient: if self.visibility == Visibility.hidden: self.window.Dismiss() self.onDismiss() else: self.window.Popup() else: GuideWxWidget._visibilityUpdated(self, _obj, _prop) if self.visibility == Visibility.hidden: self.onDismiss() def _dismissed(self, _event=None): """ The window was dismissed """ self.onDismiss() def onGetChildren(self, _parser): """ Called once our children have been parsed, add them all to a box sizer. """ guide.PopupWindow.onGetChildren(self, _parser) box = addWidgetsToSizer(self, self.children, Direction.vertical) self.window.SetSizer(box) self.window.Layout() size = box.GetMinSize() if self.width is None: self.width = -1 if self.height is None: self.height = -1 if size[0] > self.width.value - 20: self.width = size[0] + 20 if size[1] > self.height.value - 60: self.height = size[1] + 60 parentSize = self.parent.window.GetSizeTuple() self.window.SetSize((self.width.asAbsolute(parentSize[0]), self.height.asAbsolute(parentSize[1]))) def destroy(self): """ Destroy the widget """ guide.PopupWindow.destroy(self) GuideWxWidget.destroy(self) class Window(WidgetWithLabel, guide.Window): """ Top level guide frame """ hasBeenShown = False def __init__(self, **attribs): guide.Window.__init__(self, **attribs) self.window = wx.Frame(None, title=str(self.text), size=self.size) self.auiManager = aui.AuiManager() self.auiManager.SetManagedWindow(self.window) if self.menubar is not None: self.menubar = self.menubar.produce(self.dataContext, self) if self.toolbar: self.toolbar = self.toolbar.produce(self.dataContext, self) WidgetWithLabel.__init__(self) self.window.Bind(wx.EVT_CLOSE, self.onClose) def onGetChildren(self, parser): """ Called once our children have been parsed, add them to a vertical sizer """ guide.Window.onGetChildren(self, parser) box = addWidgetsToSizer(self, [child for child in self.children if not isinstance(child, (DockableFrame))], Direction.vertical) self.window.SetSizer(box) self.window.Layout() box.Layout() size = box.GetMinSize() if self.width is None: self.width = -1 if self.height is None: self.height = -1 if size[0] > self.width.value - 20: self.width = size[0] + 20 if size[1] > self.height.value - 60: self.height = size[1] + 60 if (not isinstance(self.parent.width, Coordinate) and not isinstance(self.parent.height, Coordinate)): self.window.SetSize((int(self.width.asAbsolute(self.parent.width)), int(self.height.asAbsolute(self.parent.height)))) self.auiManager.Update() def show(self): """ Show the window. """ guide.Window.show(self) self.hasBeenShown = True def destroy(self): """ Destroy the widget """ guide.Widget.destroy(self) WidgetWithLabel.destroy(self) def _sizeChanged(self, obj, prop=None): """ Called when the window's size changes. """ for toolbar in self.toolbar: toolbar.onParentResized() return WidgetWithLabel._sizeChanged(self, obj, prop=prop) def savePerspective(self, cfg, name): """ Save the perspective to the configparser under the named section. """ persp = self.auiManager.SavePerspective() cfg.set(name, self.name or "MainWindow", persp) rect = ",".join([str(coord) for coord in self.window.GetRect()]) cfg.set(name, "rect", rect) cfg.set(name, "fullscreen", str(self.window.IsFullScreen()).lower()) cfg.set(name, "maximized", str(self.window.IsMaximized()).lower()) def loadPerspective(self, cfg, name): """ Load the perspective from the configparser with the given section name. """ rect = cfg.get(name, "rect") rect = tuple([int(coord) for coord in rect.split(",")]) ignoreRect = False if cfg.has_option(name, "fullscreen") and cfg.getboolean(name, "fullscreen"): self.window.ShowFullScreen(True) ignoreRect = True else: self.window.ShowFullScreen(False) if cfg.has_option(name, "maximized") and cfg.getboolean(name, "maximized"): self.window.Maximize(True) ignoreRect = True else: self.window.Maximize(False) if not ignoreRect: self.window.SetRect(rect) persp = cfg.get(name, self.name or "MainWindow") self.auiManager.LoadPerspective(persp) class CustomControlFrame(guide.CustomControlFrame, GuideWxWidget): """ Holder for a custom control. Only inherit from this in guide implementations, not to define a custom control. """ def __init__(self, controlClass, **attribs): guide.CustomControlFrame.__init__(self, controlClass, **attribs) self.window = wx.Panel(self.parent.window, id= -1, pos=wx.DefaultPosition, size=self.size, style=wx.TAB_TRAVERSAL | wx.NO_BORDER) GuideWxWidget.__init__(self) def destroy(self): """ Destroy the widget """ guide.CustomControlFrame.destroy(self) GuideWxWidget.destroy(self) class ErrorWindow(object): """ Brings up an error window. """ def __init__(self, parser, error, reason): errorType = ".".join([error.type.__module__, error.type.__name__]) if reason is None: reason = errorType errorValue = "%s: %s" % (errorType, str(error.value)) text = "%s\n\n%s" % (errorValue, error.getTraceback()) self.window = MessageDialog(parent=parser, text=text, title=reason, iconType=DialogIcon.error) self.window.show() parser.setClipboard(text) class WxParser(Parser): """ WX Version of the parser """ window = None width = property(lambda self: wx.Display().GetGeometry()[2]) height = property(lambda self: wx.Display().GetGeometry()[3]) revKeyDict = { "up":wx.WXK_UP, "down":wx.WXK_DOWN, "left":wx.WXK_LEFT, "right":wx.WXK_RIGHT, "return":wx.WXK_RETURN, "escape":wx.WXK_ESCAPE, "tab":wx.WXK_TAB, "home":wx.WXK_HOME, "end":wx.WXK_END, "control":wx.WXK_CONTROL, "alt":wx.WXK_ALT, "shift":wx.WXK_SHIFT, } def errorHandler(self, exc=None, why=None): """ Handle an exception. """ if exc is None: exc = Failure() deferr(exc, why) ErrorWindow(self, exc, why) def getWidgetClass(self, name): """ Return a widget from our globals """ return globals()[name] def loadImage(self, method, data): """ Loads an image """ method = method.getData() data = data.getData() if method == "file": fname = data if not os.path.exists(fname): fname = os.path.join(os.path.abspath(os.path.dirname( mv3d.__file__)), "..", fname) if "panda" in sys.executable: # hack for running in a p3d fil = open(fname, "rb") data = fil.read() fil.close() tempName = "temp." + fname.split(".")[-1] fil = open(tempName, "wb") fil.write(data) fil.close() image = wx.Bitmap(tempName) return image image = wx.Bitmap(fname) return image raise ValueError("Unsupported image loader: %s" % method) def setClipboard(self, data): """ Add some data to the clipboard. This uses an internal clipboard, other parsers will need to override. """ if not wx.TheClipboard.IsOpened(): wx.TheClipboard.Open() tdata = wx.TextDataObject() tdata.SetText(str(data)) wx.Clipboard.Get().SetData(tdata) wx.TheClipboard.Close() def getClipboard(self): """ Returns whatever is in the clipboard. This uses an internal clipboard, other parsers will need to override. """ if not wx.TheClipboard.IsOpened(): wx.TheClipboard.Open() tdata = wx.TextDataObject() wx.Clipboard.Get().GetData(tdata) wx.TheClipboard.Close() return tdata.GetText() def clearClipboard(self): """ Remove the data in the clipboard """ if not wx.TheClipboard.IsOpened(): wx.TheClipboard.Open() wx.TheClipboard.Clear() wx.TheClipboard.Close() def getKeyState(self, key): """ Returns true if the key is pressed currently """ keycode = self.revKeyDict.get(key) if keycode is None: if len(key) == 1: keycode = ord(key.upper()) else: return False raise KeyError(key) return wx.GetKeyState(keycode) def getMousePosition(self): """ Returns the position of the mouse. """ return tuple(wx.GetMousePosition())