# Copyright\r
\r
"""Defines :class:`GUI` providing a wxWidgets interface to Hooke.\r
+\r
"""\r
\r
WX_GOOD=['2.8']\r
import wx.html\r
import wx.aui as aui\r
import wx.lib.evtmgr as evtmgr\r
-\r
-\r
# wxPropertyGrid is included in wxPython >= 2.9.1, see\r
# http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
# until then, we'll avoid it because of the *nix build problems.\r
#import wx.propgrid as wxpg\r
\r
-from matplotlib.ticker import FuncFormatter\r
-\r
from ...command import CommandExit, Exit, Success, Failure, Command, Argument\r
from ...config import Setting\r
from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
from . import menu as menu\r
from . import navbar as navbar\r
from . import panel as panel\r
-from .panel.propertyeditor2 import prop_from_argument, prop_from_setting\r
+from .panel.propertyeditor import prop_from_argument, prop_from_setting\r
from . import prettyformat as prettyformat\r
from . import statusbar as statusbar\r
\r
# Min size for the frame itself isn't completely done. See\r
# the end of FrameManager::Update() for the test code. For\r
# now, just hard code a frame minimum size.\r
- self.SetMinSize(wx.Size(500, 500))\r
+ #self.SetMinSize(wx.Size(500, 500))\r
\r
self._setup_panels()\r
self._setup_toolbars()\r
\r
# Create the menubar after the panes so that the default\r
# perspective is created with all panes open\r
+ panels = [p for p in self._c.values() if isinstance(p, panel.Panel)]\r
self._c['menu bar'] = menu.HookeMenuBar(\r
parent=self,\r
+ panels=panels,\r
callbacks={\r
'close': self._on_close,\r
'about': self._on_about,\r
self._setup_perspectives()\r
self._bind_events()\r
\r
- name = self.gui.config['active perspective']\r
+ self.execute_command(\r
+ command=self._command_by_name('load playlist'),\r
+ args={'input':'test/data/test'},\r
+ )\r
return # TODO: cleanup\r
- self.playlists = self._c['playlists'].Playlists\r
+ self.playlists = self._c['playlist'].Playlists\r
self._displayed_plot = None\r
#load default list, if possible\r
- self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist'))\r
+ self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlists'))\r
\r
\r
# GUI maintenance\r
\r
def _setup_panels(self):\r
client_size = self.GetClientSize()\r
- for label,p,style in [\r
+ for p,style in [\r
# ('folders', wx.GenericDirCtrl(\r
# parent=self,\r
# dir=self.gui.config['folders-workdir'],\r
# style=wx.DIRCTRL_SHOW_FILTERS,\r
# filter=self.gui.config['folders-filters'],\r
# defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'), #HACK: config should convert\r
- ('playlists', panel.PANELS['playlist'](\r
+ (panel.PANELS['playlist'](\r
callbacks={\r
'delete_playlist':self._on_user_delete_playlist,\r
'_delete_playlist':self._on_delete_playlist,\r
'_on_set_selected_playlist':self._on_set_selected_playlist,\r
'_on_set_selected_curve':self._on_set_selected_curve,\r
},\r
- config=self.gui.config,\r
parent=self,\r
style=wx.WANTS_CHARS|wx.NO_BORDER,\r
# WANTS_CHARS so the panel doesn't eat the Return key.\r
# size=wx.Size(430, 200),\r
# style=aui.AUI_NB_DEFAULT_STYLE\r
# | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),\r
- ('commands', panel.PANELS['commands'](\r
+ (panel.PANELS['commands'](\r
commands=self.commands,\r
selected=self.gui.config['selected command'],\r
callbacks={\r
# WANTS_CHARS so the panel doesn't eat the Return key.\r
# size=(160, 200),\r
), 'right'),\r
- ('property', panel.PANELS['propertyeditor2'](\r
+ (panel.PANELS['propertyeditor'](\r
callbacks={},\r
parent=self,\r
style=wx.WANTS_CHARS,\r
# pos=wx.Point(0, 0),\r
# size=wx.Size(150, 90),\r
# style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),\r
- ('output', panel.PANELS['output'](\r
+ (panel.PANELS['plot'](\r
+ callbacks={\r
+ },\r
+ parent=self,\r
+ style=wx.WANTS_CHARS|wx.NO_BORDER,\r
+ # WANTS_CHARS so the panel doesn't eat the Return key.\r
+# size=(160, 200),\r
+ ), 'center'),\r
+ (panel.PANELS['output'](\r
parent=self,\r
pos=wx.Point(0, 0),\r
size=wx.Size(150, 90),\r
'bottom'),\r
# ('results', panel.results.Results(self), 'bottom'),\r
]:\r
- self._add_panel(label, p, style)\r
+ self._add_panel(p, style)\r
#self._c['assistant'].SetEditable(False)\r
\r
- def _add_panel(self, label, panel, style):\r
- self._c[label] = panel\r
- cap_label = label.capitalize()\r
- info = aui.AuiPaneInfo().Name(cap_label).Caption(cap_label)\r
+ def _add_panel(self, panel, style):\r
+ self._c[panel.name] = panel\r
+ m_name = panel.managed_name\r
+ info = aui.AuiPaneInfo().Name(m_name).Caption(m_name)\r
info.PaneBorder(False).CloseButton(True).MaximizeButton(False)\r
if style == 'top':\r
info.Top()\r
\r
\r
\r
+ # Panel utility functions\r
+\r
+ def _file_name(self, name):\r
+ """Cleanup names according to configured preferences.\r
+ """\r
+ if self.gui.config['hide extensions'] == 'True': # HACK: config should decode\r
+ name,ext = os.path.splitext(name)\r
+ return name\r
+\r
+\r
+\r
# Command handling\r
\r
def _command_by_name(self, name):\r
command=None, args=None):\r
if args == None:\r
args = {}\r
- if ('property' in self._c\r
+ if ('property editor' in self._c\r
and self.gui.config['selected command'] == command):\r
arg_names = [arg.name for arg in command.arguments]\r
- for name,value in self._c['property'].get_values().items():\r
+ for name,value in self._c['property editor'].get_values().items():\r
if name in arg_names:\r
args[name] = value\r
print 'executing', command.name, args\r
"""\r
if not isinstance(results[-1], Success):\r
self._postprocess_text(command, results=results)\r
+ return\r
assert len(results) == 2, results\r
playlist = results[0]\r
- self._c['playlists']._c['tree'].add_playlist(playlist)\r
+ self._c['playlist']._c['tree'].add_playlist(playlist)\r
\r
def _postprocess_get_playlist(self, command, args={}, results=[]):\r
if not isinstance(results[-1], Success):\r
self._postprocess_text(command, results=results)\r
+ return\r
assert len(results) == 2, results\r
playlist = results[0]\r
- self._c['playlists']._c['tree'].update_playlist(playlist)\r
+ self._c['playlist']._c['tree'].update_playlist(playlist)\r
\r
def _postprocess_get_curve(self, command, args={}, results=[]):\r
"""Update `self` to show the curve.\r
"""\r
if not isinstance(results[-1], Success):\r
self._postprocess_text(command, results=results)\r
+ return\r
assert len(results) == 2, results\r
curve = results[0]\r
if args.get('curve', None) == None:\r
playlist = results[0]\r
else:\r
raise NotImplementedError()\r
- self._c['playlists']._c['tree'].set_selected_curve(\r
- playlist, curve)\r
+ if 'playlist' in self._c:\r
+ self._c['playlist']._c['tree'].set_selected_curve(\r
+ playlist, curve)\r
+ if 'plot' in self._c:\r
+ self._c['plot'].set_curve(curve, config=self.gui.config)\r
\r
def _postprocess_next_curve(self, command, args={}, results=[]):\r
"""No-op. Only call 'next curve' via `self._next_curve()`.\r
def _GetActiveFileIndex(self):\r
lib.playlist.Playlist = self.GetActivePlaylist()\r
#get the selected item from the tree\r
- selected_item = self._c['playlists']._c['tree'].GetSelection()\r
+ selected_item = self._c['playlist']._c['tree'].GetSelection()\r
#test if a playlist or a curve was double-clicked\r
- if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
+ if self._c['playlist']._c['tree'].ItemHasChildren(selected_item):\r
return -1\r
else:\r
count = 0\r
- selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item)\r
+ selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)\r
while selected_item.IsOk():\r
count += 1\r
- selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item)\r
+ selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)\r
return count\r
\r
def _GetPlaylistTab(self, name):\r
self.UpdateNote()\r
self.UpdatePlot()\r
\r
- def UpdatePlot(self, plot=None):\r
-\r
- def add_to_plot(curve, set_scale=True):\r
- if curve.visible and curve.x and curve.y:\r
- #get the index of the subplot to use as destination\r
- destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1\r
- #set all parameters for the plot\r
- axes_list[destination].set_title(curve.title)\r
- if set_scale:\r
- axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x)\r
- axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y)\r
- #set the formatting details for the scale\r
- formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
- formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)\r
- axes_list[destination].xaxis.set_major_formatter(formatter_x)\r
- axes_list[destination].yaxis.set_major_formatter(formatter_y)\r
- if curve.style == 'plot':\r
- axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1)\r
- if curve.style == 'scatter':\r
- axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2)\r
- #add the legend if necessary\r
- if curve.legend:\r
- axes_list[destination].legend()\r
-\r
- if plot is None:\r
- active_file = self.GetActiveFile()\r
- if not active_file.driver:\r
- #the first time we identify a file, the following need to be set\r
- active_file.identify(self.drivers)\r
- for curve in active_file.plot.curves:\r
- curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals')\r
- curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals')\r
- curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend')\r
- curve.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix')\r
- curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix')\r
- if active_file.driver is None:\r
- self.AppendToOutput('Invalid file: ' + active_file.filename)\r
- return\r
- self.displayed_plot = copy.deepcopy(active_file.plot)\r
- #add raw curves to plot\r
- self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves)\r
- #apply all active plotmanipulators\r
- self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file)\r
- #add corrected curves to plot\r
- self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves)\r
- else:\r
- active_file = None\r
- self.displayed_plot = copy.deepcopy(plot)\r
-\r
- figure = self.GetActiveFigure()\r
- figure.clear()\r
-\r
- #use '0' instead of e.g. '0.00' for scales\r
- use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')\r
- #optionally remove the extension from the title of the plot\r
- hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
- if hide_curve_extension:\r
- title = lh.remove_extension(self.displayed_plot.title)\r
- else:\r
- title = self.displayed_plot.title\r
- figure.suptitle(title, fontsize=14)\r
- #create the list of all axes necessary (rows and columns)\r
- axes_list =[]\r
- number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])\r
- number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])\r
- for index in range(number_of_rows * number_of_columns):\r
- axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))\r
-\r
- #add all curves to the corresponding plots\r
- for curve in self.displayed_plot.curves:\r
- add_to_plot(curve)\r
-\r
- #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'\r
- figure.subplots_adjust(hspace=0.3)\r
-\r
- #display results\r
- self.panelResults.ClearResults()\r
- if self.displayed_plot.results.has_key(self.results_str):\r
- for curve in self.displayed_plot.results[self.results_str].results:\r
- add_to_plot(curve, set_scale=False)\r
- self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])\r
- else:\r
- self.panelResults.ClearResults()\r
- #refresh the plot\r
- figure.canvas.draw()\r
-\r
def _on_curve_select(self, playlist, curve):\r
#create the plot tab and add playlist to the dictionary\r
plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))\r
#self.select_plugin(plugin=command.plugin)\r
if 'assistant' in self._c:\r
self._c['assitant'].ChangeValue(command.help)\r
- self._c['property'].clear()\r
+ self._c['property editor'].clear()\r
for argument in command.arguments:\r
if argument.name == 'help':\r
continue\r
argument, curves=curves, playlists=playlists)\r
if p == None:\r
continue # property intentionally not handled (yet)\r
- self._c['property'].append_property(p)\r
+ self._c['property editor'].append_property(p)\r
\r
self.gui.config['selected command'] = command # TODO: push to engine\r
\r
if not self._perspectives.has_key(selected_perspective):\r
self.gui.config['active perspective'] = 'Default' # TODO: push to engine's Hooke\r
\r
- self._restore_perspective(selected_perspective)\r
+ self._restore_perspective(selected_perspective, force=True)\r
self._update_perspective_menu()\r
\r
def _update_perspective_menu(self):\r
# ) that makes the radio item indicator in the menu disappear.\r
# The code should be fine once this issue is fixed.\r
\r
- def _restore_perspective(self, name):\r
- if name != self.gui.config['active perspective']:\r
+ def _restore_perspective(self, name, force=False):\r
+ if name != self.gui.config['active perspective'] or force == True:\r
print 'restoring perspective:', name\r
self.gui.config['active perspective'] = name # TODO: push to engine's Hooke\r
self._c['manager'].LoadPerspective(self._perspectives[name])\r
self._c['manager'].Update()\r
for pane in self._c['manager'].GetAllPanes():\r
- if pane.name in self._c['menu bar']._c['view']._c.keys():\r
- pane.Check(pane.window.IsShown())\r
+ view = self._c['menu bar']._c['view']\r
+ if pane.name in view._c.keys():\r
+ view._c[pane.name].Check(pane.window.IsShown())\r
\r
def _on_save_perspective(self, *args):\r
perspective = self._c['manager'].SavePerspective()\r
Setting(section=self.setting_section, option='hide extensions',\r
value=False,\r
help='Hide file extensions when displaying names.'),\r
+ Setting(section=self.setting_section, option='plot legend',\r
+ value=True,\r
+ help='Enable/disable the plot legend.'),\r
+ Setting(section=self.setting_section, option='plot x format',\r
+ value='None',\r
+ help='Display format for plot x values.'),\r
+ Setting(section=self.setting_section, option='plot y format',\r
+ value='None',\r
+ help='Display format for plot y values.'),\r
+ Setting(section=self.setting_section, option='plot zero',\r
+ value=0,\r
+ help='Select "0" vs. e.g. "0.00" for plot axes?'),\r
Setting(section=self.setting_section, option='folders-workdir',\r
value='.',\r
help='This should probably go...'),\r
import wx
from ...util.callback import callback, in_callback
-from . import panel as panel
class Menu (wx.Menu):
class ViewMenu (Menu):
- def __init__(self, callbacks=None, **kwargs):
+ def __init__(self, panels, callbacks=None, **kwargs):
super(ViewMenu, self).__init__(**kwargs)
if callbacks == None:
callbacks = {}
self._callbacks = callbacks
self._c = {}
- for i,panelname in enumerate(sorted(panel.PANELS.keys())):
- text = '%s\tF%d' % (panelname.capitalize(), i+5)
+ for i,panelname in enumerate(sorted([p.managed_name for p in panels])):
+ text = '%s\tF%d' % (panelname, i+5)
self._c[panelname] = self.AppendCheckItem(id=wx.ID_ANY, text=text)
for item in self._c.values():
item.Check()
class HookeMenuBar (MenuBar):
- def __init__(self, callbacks=None, **kwargs):
+ def __init__(self, panels, callbacks=None, **kwargs):
super(HookeMenuBar, self).__init__(**kwargs)
if callbacks == None:
callbacks = {}
for key in ['file', 'view', 'perspective', 'help']:
cap_key = key.capitalize()
_class = globals()['%sMenu' % cap_key]
- self._c[key] = _class(parent=self, callbacks=callbacks)
+ kwargs = {}
+ if key == 'view':
+ kwargs['panels'] = panels
+ self._c[key] = _class(parent=self, callbacks=callbacks, **kwargs)
self.Append(self._c[key], cap_key)
# Copyright\r
\r
+"""The `panel` module provides optional submodules that add GUI panels.\r
+"""\r
+\r
from ....util.pluggable import IsSubclass, construct_odict\r
\r
\r
# 'notebook',\r
'output',\r
'playlist',\r
-# 'plot',\r
-# 'propertyeditor',\r
- 'propertyeditor2',\r
+ 'plot',\r
+ 'propertyeditor',\r
# 'results',\r
# 'selection',\r
# 'welcome',\r
def __init__(self, name=None, callbacks=None, **kwargs):\r
super(Panel, self).__init__(**kwargs)\r
self.name = name\r
+ self.managed_name = name.capitalize()\r
+ self._hooke_frame = kwargs.get('parent', None)\r
if callbacks == None:\r
callbacks = {}\r
self._callbacks = callbacks\r
class Tree (wx.TreeCtrl):\r
""":class:`wx.TreeCtrl` subclass handling playlist and curve selection.\r
"""\r
- def __init__(self, config, callbacks, *args, **kwargs):\r
+ def __init__(self, *args, **kwargs):\r
+ self._panel = kwargs['parent']\r
+ self._callbacks = self._panel._callbacks # TODO: CallbackClass.set_callback{,s}()\r
super(Tree, self).__init__(*args, **kwargs)\r
imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
imglist.Add(wx.ArtProvider.GetBitmap(\r
self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)\r
self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
\r
- self.config = config\r
- self._callbacks = callbacks\r
self._setup_playlists()\r
\r
def _setup_playlists(self):\r
self._id_for_name = {} # {name: id}\r
self._name_for_id = {} # {id: name}\r
\r
- def _name(self, name):\r
- """Cleanup names according to configured preferences.\r
- """\r
- if self.config['hide extensions'] == 'True': # HACK: config should decode\r
- name,ext = os.path.splitext(name)\r
- return name\r
-\r
def _is_curve(self, name): # name from ._id_for_name / ._name_for_id\r
"""Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`.\r
"""\r
self._playlists[playlist.name] = playlist\r
p_id = self.AppendItem(\r
parent=self._c['root'],\r
- text=self._name(playlist.name),\r
+ text=self._panel._hooke_frame._file_name(playlist.name),\r
image=self.image['playlist'])\r
self._id_for_name[playlist.name] = p_id\r
self._name_for_id[p_id] = playlist.name\r
p.append(curve)\r
c_id = self.AppendItem(\r
parent=self._id_for_name[playlist_name],\r
- text=self._name(curve.name),\r
+ text=self._panel._hooke_frame._file_name(curve.name),\r
image=self.image['curve'])\r
self._id_for_name[(p.name, curve.name)] = c_id\r
self._name_for_id[c_id] = (p.name, curve.name)\r
class Playlist (Panel, wx.Panel):\r
""":class:`wx.Panel` subclass wrapper for :class:`Tree`.\r
"""\r
- def __init__(self, config, callbacks, *args, **kwargs):\r
+ def __init__(self, callbacks=None, **kwargs):\r
# Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
- super(Playlist, self).__init__(*args, **kwargs)\r
- self.name = 'playlist panel'\r
-\r
+ super(Playlist, self).__init__(\r
+ name='playlist', callbacks=callbacks, **kwargs)\r
self._c = {\r
'tree': Tree(\r
- config=config,\r
- callbacks=callbacks,\r
parent=self,\r
size=wx.Size(160, 250),\r
style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT),\r
# Copyright\r
\r
"""Plot panel for Hooke.\r
+\r
+Notes\r
+-----\r
+Originally based on `this example`_.\r
+\r
+.. _this example:\r
+ http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_wx2.html\r
"""\r
\r
+import matplotlib\r
+matplotlib.use('WXAgg') # use wxpython with antigrain (agg) rendering\r
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas\r
-from matplotlib.backends.backend_wx import NavigationToolbar2Wx\r
+from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar\r
from matplotlib.figure import Figure\r
-\r
import wx\r
\r
-# There are many comments in here from the demo app.\r
-# They should come in handy to expand the functionality in the future.\r
-\r
-class HookeCustomToolbar(NavigationToolbar2Wx):\r
-\r
- def __init__(self, plotCanvas):\r
- NavigationToolbar2Wx.__init__(self, plotCanvas)\r
- # add new toolbar buttons\r
- #glyph_file = 'resources' + os.sep + 'pipette.png'\r
- #glyph = wx.Image(glyph_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()\r
-\r
- #self.AddCheckTool(ON_CUSTOM_PICK, glyph, shortHelp='Select a data point', longHelp='Select a data point')\r
- #wx.EVT_TOOL(self, ON_CUSTOM_PICK, self.OnSelectPoint)\r
-\r
- # remove the unwanted button\r
-# POSITION_OF_CONFIGURE_SUBPLOTS_BTN = 6\r
-# self.DeleteToolByPos(POSITION_OF_CONFIGURE_SUBPLOTS_BTN)\r
-\r
- #def OnSelectPoint(self, event):\r
- #self.Parent.Parent.Parent.pick_active = True\r
-\r
-\r
-#class LineBuilder:\r
- #def __init__(self, line):\r
- #self.line = line\r
- #self.xs = list(line.get_xdata())\r
- #self.ys = list(line.get_ydata())\r
- #self.cid = line.figure.canvas.mpl_connect('button_press_event', self)\r
-\r
- #def __call__(self, event):\r
- #print 'click', event\r
- #if event.inaxes != self.line.axes:\r
- #return\r
- #self.xs.append(event.xdata)\r
- #self.ys.append(event.ydata)\r
- #self.line.set_data(self.xs, self.ys)\r
- #self.line.figure.canvas.draw()\r
-\r
+from ....util.callback import callback, in_callback\r
+from . import Panel\r
\r
-class PlotPanel(wx.Panel):\r
-\r
- def __init__(self, parent, ID):\r
- wx.Panel.__init__(self, parent, ID, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200))\r
-\r
- self.figure = Figure()\r
- self.canvas = FigureCanvas(self, -1, self.figure)\r
- self.SetColor(wx.NamedColor('WHITE'))\r
-\r
- self.sizer = wx.BoxSizer(wx.VERTICAL)\r
- self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)\r
- self.SetSizer(self.sizer)\r
- self.Fit()\r
\r
+class PlotPanel (Panel, wx.Panel):\r
+ """UI for graphical curve display.\r
+ """\r
+ def __init__(self, callbacks=None, **kwargs):\r
self.display_coordinates = False\r
+ self.style = 'line'\r
+ self._curve = None\r
+ super(PlotPanel, self).__init__(\r
+ name='plot', callbacks=callbacks, **kwargs)\r
+ self._c = {}\r
+ self._c['figure'] = Figure()\r
+ self._c['canvas'] = FigureCanvas(\r
+ parent=self, id=wx.ID_ANY, figure=self._c['figure'])\r
+ self._c['toolbar'] = NavToolbar(self._c['canvas'])\r
+\r
+ self._set_color(wx.NamedColor('WHITE'))\r
+ sizer = wx.BoxSizer(wx.VERTICAL)\r
+ sizer.Add(self._c['canvas'], 1, wx.LEFT | wx.TOP | wx.GROW)\r
+ self._setup_toolbar(toolbar=self._c['toolbar'], sizer=sizer)\r
+ self.SetSizer(sizer)\r
+ self.Fit()\r
\r
- self.figure.canvas.mpl_connect('button_press_event', self.OnClick)\r
- self.figure.canvas.mpl_connect('axes_enter_event', self.OnEnterAxes)\r
- self.figure.canvas.mpl_connect('axes_leave_event', self.OnLeaveAxes)\r
- self.figure.canvas.mpl_connect('motion_notify_event', self.OnMouseMove)\r
- self.add_toolbar() # comment this out for no toolbar\r
-\r
- def add_toolbar(self):\r
- self.toolbar = HookeCustomToolbar(self.canvas)\r
- self.toolbar.Realize()\r
+ self.Bind(wx.EVT_SIZE, self._on_size) \r
+ self._c['figure'].canvas.mpl_connect(\r
+ 'button_press_event', self._on_click)\r
+ self._c['figure'].canvas.mpl_connect(\r
+ 'axes_enter_event', self._on_enter_axes)\r
+ self._c['figure'].canvas.mpl_connect(\r
+ 'axes_leave_event', self._on_leave_axes)\r
+ self._c['figure'].canvas.mpl_connect(\r
+ 'motion_notify_event', self._on_mouse_move)\r
+\r
+ def _setup_toolbar(self, toolbar, sizer):\r
+ self._c['toolbar'].Realize() # call after putting items in the toolbar\r
if wx.Platform == '__WXMAC__':\r
# Mac platform (OSX 10.3, MacPython) does not seem to cope with\r
# having a toolbar in a sizer. This work-around gets the buttons\r
# back, but at the expense of having the toolbar at the top\r
- self.SetToolBar(self.toolbar)\r
- else:\r
+ self.SetToolBar(toolbar)\r
+ elif wx.Platform == '__WXMSW__':\r
# On Windows platform, default window size is incorrect, so set\r
# toolbar width to figure width.\r
- tw, th = self.toolbar.GetSizeTuple()\r
- fw, fh = self.canvas.GetSizeTuple()\r
+ tw, th = toolbar.GetSizeTuple()\r
+ fw, fh = self._c['canvas'].GetSizeTuple()\r
# By adding toolbar in sizer, we are able to put it at the bottom\r
# of the frame - so appearance is closer to GTK version.\r
# As noted above, doesn't work for Mac.\r
- self.toolbar.SetSize(wx.Size(fw, th))\r
- self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)\r
- # update the axes menu on the toolbar\r
- self.toolbar.update()\r
-\r
- def get_figure(self):\r
- return self.figure\r
-\r
- def SetColor(self, rgbtuple):\r
- '''\r
- Set figure and canvas colours to be the same\r
- '''\r
- if not rgbtuple:\r
+ toolbar.SetSize(wx.Size(fw, th))\r
+ sizer.Add(toolbar, 0 , wx.LEFT | wx.EXPAND)\r
+ else:\r
+ sizer.Add(toolbar, 0 , wx.LEFT | wx.EXPAND)\r
+ self._c['toolbar'].update() # update the axes menu on the toolbar\r
+\r
+ def _set_color(self, rgbtuple=None):\r
+ """Set both figure and canvas colors to `rgbtuple`.\r
+ """\r
+ if rgbtuple == None:\r
rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()\r
- col = [c / 255.0 for c in rgbtuple]\r
- self.figure.set_facecolor(col)\r
- self.figure.set_edgecolor(col)\r
- self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))\r
+ col = [c/255.0 for c in rgbtuple]\r
+ self._c['figure'].set_facecolor(col)\r
+ self._c['figure'].set_edgecolor(col)\r
+ self._c['canvas'].SetBackgroundColour(wx.Colour(*rgbtuple))\r
\r
- def SetStatusText(self, text, field=1):\r
- self.Parent.Parent.statusbar.SetStatusText(text, field)\r
+ #def SetStatusText(self, text, field=1):\r
+ # self.Parent.Parent.statusbar.SetStatusText(text, field)\r
\r
- def OnClick(self, event):\r
+ def _on_size(self, event):\r
+ event.Skip()\r
+ wx.CallAfter(self._resize_canvas)\r
+\r
+ def _on_click(self, event):\r
#self.SetStatusText(str(event.xdata))\r
#print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)\r
pass\r
\r
- def OnEnterAxes(self, event):\r
+ def _on_enter_axes(self, event):\r
self.display_coordinates = True\r
\r
- def OnLeaveAxes(self, event):\r
+ def _on_leave_axes(self, event):\r
self.display_coordinates = False\r
- self.SetStatusText('')\r
+ #self.SetStatusText('')\r
\r
- def OnMouseMove(self, event):\r
+ def _on_mouse_move(self, event):\r
if event.guiEvent.m_shiftDown:\r
- self.toolbar.set_cursor(2)\r
- #print 'hand: ' + str(wx.CURSOR_HAND)\r
- #print 'cross: ' + str(wx.CURSOR_CROSS)\r
- #print 'ibeam: ' + str(wx.CURSOR_IBEAM)\r
- #print 'wait: ' + str(wx.CURSOR_WAIT)\r
- #print 'hourglass: ' + str(wx.HOURGLASS_CURSOR)\r
+ self._c['toolbar'].set_cursor(wx.CURSOR_RIGHT_ARROW)\r
else:\r
- self.toolbar.set_cursor(1)\r
-\r
- #axes = self.figure.axes[0]\r
- #line, = axes.plot([event.x - 20 , event.x + 20], [event.y - 20, event.y + 20])\r
-\r
- #line.figure.canvas.draw()\r
+ self._c['toolbar'].set_cursor(wx.CURSOR_ARROW)\r
if self.display_coordinates:\r
- coordinateString = ''.join(['x: ', str(event.xdata), ' y: ', str(event.ydata)])\r
+ coordinateString = ''.join(\r
+ ['x: ', str(event.xdata), ' y: ', str(event.ydata)])\r
#TODO: pretty format\r
- self.SetStatusText(coordinateString)\r
+ #self.SetStatusText(coordinateString)\r
+\r
+ def _resize_canvas(self):\r
+ print 'resizing'\r
+ w,h = self.GetClientSize()\r
+ tw,th = self._c['toolbar'].GetSizeTuple()\r
+ dpi = float(self._c['figure'].get_dpi())\r
+ self._c['figure'].set_figwidth(w/dpi)\r
+ self._c['figure'].set_figheight((h-th)/dpi)\r
+ self._c['canvas'].draw()\r
+ self.Refresh()\r
\r
def OnPaint(self, event):\r
- self.canvas.draw()\r
+ print 'painting'\r
+ super(PlotPanel, self).OnPaint(event)\r
+ self._c['canvas'].draw()\r
+\r
+ def set_curve(self, curve, config={}):\r
+ self._curve = curve\r
+ self.update(config=config)\r
+\r
+ def update(self, config={}):\r
+ print 'updating'\r
+ x_format = config['plot x format']\r
+ y_format = config['plot y format']\r
+ zero = config['plot zero']\r
+\r
+ self._c['figure'].clear()\r
+ self._c['figure'].suptitle(\r
+ self._hooke_frame._file_name(self._curve.name),\r
+ fontsize=12)\r
+ axes = self._c['figure'].add_subplot(1, 1, 1)\r
+\r
+# if x_format != 'None':\r
+# f = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
+# axes.xaxis.set_major_formatter(f)\r
+# if y_format != 'None':\r
+# f = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)\r
+# axes.yaxis.set_major_formatter(f)\r
+\r
+ x_name = 'z piezo (m)'\r
+ y_name = 'deflection (m)'\r
+ #axes.set_xlabel(x_name)\r
+ #axes.set_ylabel(y_name)\r
+\r
+ self._c['figure'].hold(True)\r
+ for i,data in enumerate(self._curve.data):\r
+ axes.plot(data[:,data.info['columns'].index(x_name)],\r
+ data[:,data.info['columns'].index(y_name)],\r
+ '.',\r
+ label=data.info['name'])\r
+ if config['plot legend'] == 'True': # HACK: config should convert\r
+ axes.legend(loc='best')\r
+ self._c['canvas'].draw()\r
--- /dev/null
+# Copyright\r
+\r
+"""Property editor panel for Hooke.\r
+"""\r
+\r
+import sys\r
+import os.path\r
+\r
+import wx\r
+import wx.propgrid as wxpg\r
+\r
+# There are many comments and code fragments in here from the demo app.\r
+# They should come in handy to expand the functionality in the future.\r
+\r
+class Display (object):\r
+ property_descriptor = []\r
+ def __init__(self):\r
+ pass\r
+\r
+class ValueObject (object):\r
+ def __init__(self):\r
+ pass\r
+\r
+\r
+class IntProperty2 (wxpg.PyProperty):\r
+ """This is a simple re-implementation of wxIntProperty.\r
+ """\r
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):\r
+ wxpg.PyProperty.__init__(self, label, name)\r
+ self.SetValue(value)\r
+\r
+ def GetClassName(self):\r
+ return "IntProperty2"\r
+\r
+ def GetEditor(self):\r
+ return "TextCtrl"\r
+\r
+ def GetValueAsString(self, flags):\r
+ return str(self.GetValue())\r
+\r
+ def PyStringToValue(self, s, flags):\r
+ try:\r
+ v = int(s)\r
+ if self.GetValue() != v:\r
+ return v\r
+ except TypeError:\r
+ if flags & wxpg.PG_REPORT_ERROR:\r
+ wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")\r
+ return False\r
+\r
+ def PyIntToValue(self, v, flags):\r
+ if (self.GetValue() != v):\r
+ return v\r
+\r
+\r
+class PyFilesProperty(wxpg.PyArrayStringProperty):\r
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
+ wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
+ self.SetValue(value)\r
+\r
+ def OnSetValue(self, v):\r
+ self.value = v\r
+ self.display = ', '.join(self.value)\r
+\r
+ def GetValueAsString(self, argFlags):\r
+ return self.display\r
+\r
+ def PyStringToValue(self, s, flags):\r
+ return [a.strip() for a in s.split(',')]\r
+\r
+ def OnEvent(self, propgrid, ctrl, event):\r
+ if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
+ # Show dialog to select a string, call DoSetValue and\r
+ # return True, if value changed.\r
+ return True\r
+\r
+ return False\r
+\r
+\r
+class PyObjectPropertyValue:\r
+ """\\r
+ Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
+ list of string given as argument to constructor.\r
+ """\r
+ def __init__(self, s=None):\r
+ try:\r
+ self.ls = [a.strip() for a in s.split('-')]\r
+ except:\r
+ self.ls = []\r
+\r
+ def __repr__(self):\r
+ return ' - '.join(self.ls)\r
+\r
+\r
+class PyObjectProperty(wxpg.PyProperty):\r
+ """\\r
+ Another simple example. This time our value is a PyObject (NOTE: we can't\r
+ return an arbitrary python object in DoGetValue. It cannot be a simple\r
+ type such as int, bool, double, or string, nor an array or wxObject based.\r
+ Dictionary, None, or any user-specified Python object is allowed).\r
+ """\r
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
+ wxpg.PyProperty.__init__(self, label, name)\r
+ self.SetValue(value)\r
+\r
+ def GetClassName(self):\r
+ return self.__class__.__name__\r
+\r
+ def GetEditor(self):\r
+ return "TextCtrl"\r
+\r
+ def GetValueAsString(self, flags):\r
+ return repr(self.GetValue())\r
+\r
+ def PyStringToValue(self, s, flags):\r
+ return PyObjectPropertyValue(s)\r
+\r
+\r
+class ShapeProperty(wxpg.PyEnumProperty):\r
+ """\\r
+ Demonstrates use of OnCustomPaint method.\r
+ """\r
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
+ wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
+\r
+ def OnMeasureImage(self, index):\r
+ return wxpg.DEFAULT_IMAGE_SIZE\r
+\r
+ def OnCustomPaint(self, dc, rect, paint_data):\r
+ """\\r
+ paint_data.m_choiceItem is -1 if we are painting the control,\r
+ in which case we need to get the drawn item using DoGetValue.\r
+ """\r
+ item = paint_data.m_choiceItem\r
+ if item == -1:\r
+ item = self.DoGetValue()\r
+\r
+ dc.SetPen(wx.Pen(wx.BLACK))\r
+ dc.SetBrush(wx.Brush(wx.BLACK))\r
+\r
+ if item == 0:\r
+ dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
+ elif item == 1:\r
+ half_width = rect.width / 2\r
+ dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
+ elif item == 2:\r
+ dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
+\r
+\r
+class LargeImagePickerCtrl(wx.Window):\r
+ """\\r
+ Control created and used by LargeImageEditor.\r
+ """\r
+ def __init__(self):\r
+ pre = wx.PreWindow()\r
+ self.PostCreate(pre)\r
+\r
+ def Create(self, parent, id_, pos, size, style = 0):\r
+ wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
+ img_spc = size[1]\r
+ self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
+ self.SetBackgroundColour(wx.WHITE)\r
+ self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
+ self.property = None\r
+ self.bmp = None\r
+ self.Bind(wx.EVT_PAINT, self.OnPaint)\r
+\r
+ def OnPaint(self, event):\r
+ dc = wx.BufferedPaintDC(self)\r
+\r
+ whiteBrush = wx.Brush(wx.WHITE)\r
+ dc.SetBackground(whiteBrush)\r
+ dc.Clear()\r
+\r
+ bmp = self.bmp\r
+ if bmp:\r
+ dc.DrawBitmap(bmp, 2, 2)\r
+ else:\r
+ dc.SetPen(wx.Pen(wx.BLACK))\r
+ dc.SetBrush(whiteBrush)\r
+ dc.DrawRectangle(2, 2, 64, 64)\r
+\r
+ def RefreshThumbnail(self):\r
+ """\\r
+ We use here very simple image scaling code.\r
+ """\r
+ if not self.property:\r
+ self.bmp = None\r
+ return\r
+\r
+ path = self.property.DoGetValue()\r
+\r
+ if not os.path.isfile(path):\r
+ self.bmp = None\r
+ return\r
+\r
+ image = wx.Image(path)\r
+ image.Rescale(64, 64)\r
+ self.bmp = wx.BitmapFromImage(image)\r
+\r
+ def SetProperty(self, property):\r
+ self.property = property\r
+ self.tc.SetValue(property.GetDisplayedString())\r
+ self.RefreshThumbnail()\r
+\r
+ def SetValue(self, s):\r
+ self.RefreshThumbnail()\r
+ self.tc.SetValue(s)\r
+\r
+ def GetLastPosition(self):\r
+ return self.tc.GetLastPosition()\r
+\r
+\r
+class LargeImageEditor(wxpg.PyEditor):\r
+ """\\r
+ Double-height text-editor with image in front.\r
+ """\r
+ def __init__(self):\r
+ wxpg.PyEditor.__init__(self)\r
+\r
+ def CreateControls(self, propgrid, property, pos, sz):\r
+ try:\r
+ h = 64 + 6\r
+ x = propgrid.GetSplitterPosition()\r
+ x2 = propgrid.GetClientSize().x\r
+ bw = propgrid.GetRowHeight()\r
+ lipc = LargeImagePickerCtrl()\r
+ if sys.platform == 'win32':\r
+ lipc.Hide()\r
+ lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
+ lipc.SetProperty(property)\r
+ # Hmmm.. how to have two-stage creation without subclassing?\r
+ #btn = wx.PreButton()\r
+ #pre = wx.PreWindow()\r
+ #self.PostCreate(pre)\r
+ #if sys.platform == 'win32':\r
+ # btn.Hide()\r
+ #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
+ btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
+ return (lipc, btn)\r
+ except:\r
+ import traceback\r
+ print traceback.print_exc()\r
+\r
+ def UpdateControl(self, property, ctrl):\r
+ ctrl.SetValue(property.GetDisplayedString())\r
+\r
+ def DrawValue(self, dc, property, rect):\r
+ if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
+ dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
+\r
+ def OnEvent(self, propgrid, ctrl, event):\r
+ if not ctrl:\r
+ return False\r
+\r
+ evtType = event.GetEventType()\r
+\r
+ if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
+ if propgrid.IsEditorsValueModified():\r
+ return True\r
+\r
+ elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
+ if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
+ ctrl.GetLastPosition() > 0:\r
+\r
+ # We must check this since an 'empty' text event\r
+ # may be triggered when creating the property.\r
+ PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
+ if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
+ event.Skip();\r
+ event.SetId(propgrid.GetId());\r
+\r
+ propgrid.EditorsValueWasModified();\r
+\r
+ return False\r
+\r
+\r
+ def CopyValueFromControl(self, property, ctrl):\r
+ tc = ctrl.tc\r
+ res = property.SetValueFromString(tc.GetValue(),0)\r
+ # Changing unspecified always causes event (returning\r
+ # true here should be enough to trigger it).\r
+ if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
+ res = True\r
+\r
+ return res\r
+\r
+ def SetValueToUnspecified(self, ctrl):\r
+ ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
+\r
+ def SetControlStringValue(self, ctrl, txt):\r
+ ctrl.SetValue(txt)\r
+\r
+ def OnFocus(self, property, ctrl):\r
+ ctrl.tc.SetSelection(-1,-1)\r
+ ctrl.tc.SetFocus()\r
+\r
+\r
+class PropertyEditor(wx.Panel):\r
+\r
+ def __init__(self, parent):\r
+ # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
+ wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))\r
+\r
+ sizer = wx.BoxSizer(wx.VERTICAL)\r
+\r
+ self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)\r
+\r
+ # Show help as tooltips\r
+ self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)\r
+\r
+ #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)\r
+ #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)\r
+ #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)\r
+\r
+ # Needed by custom image editor\r
+ wx.InitAllImageHandlers()\r
+\r
+ #\r
+ # Let's create a simple custom editor\r
+ #\r
+ # NOTE: Editor must be registered *before* adding a property that uses it.\r
+ self.pg.RegisterEditor(LargeImageEditor)\r
+\r
+ '''\r
+ #\r
+ # Add properties\r
+ #\r
+\r
+ pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )\r
+ pg.Append( wxpg.StringProperty("String",value="Some Text") )\r
+ pg.Append( wxpg.IntProperty("Int",value=100) )\r
+ pg.Append( wxpg.FloatProperty("Float",value=100.0) )\r
+ pg.Append( wxpg.BoolProperty("Bool",value=True) )\r
+ pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )\r
+ pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)\r
+\r
+ pg.Append( wxpg.PropertyCategory("2 - More Properties") )\r
+ pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )\r
+ pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )\r
+ pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )\r
+ pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )\r
+\r
+ pg.Append( wxpg.EnumProperty("Enum","Enum",\r
+ ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],\r
+ [10,11,12],0) )\r
+ pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )\r
+\r
+ pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )\r
+ pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )\r
+ pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )\r
+ pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )\r
+ pg.Append( wxpg.SystemColourProperty("SystemColour") )\r
+ pg.Append( wxpg.ImageFileProperty("ImageFile") )\r
+ pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )\r
+\r
+ pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )\r
+ pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )\r
+ pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )\r
+ pg.Append( wxpg.FontDataProperty("FontData") )\r
+ pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )\r
+ pg.SetPropertyEditor("IntWithSpin","SpinCtrl")\r
+ pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )\r
+ pg.SetPropertyHelpString( "String", "String Property help string!" )\r
+ pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )\r
+\r
+ pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )\r
+ pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )\r
+ pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )\r
+\r
+ pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )\r
+ pg.Append( IntProperty2("IntProperty2", value=1024) )\r
+\r
+ pg.Append( ShapeProperty("ShapeProperty", value=0) )\r
+ pg.Append( PyObjectProperty("PyObjectProperty") )\r
+\r
+ pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )\r
+ pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")\r
+\r
+\r
+ pg.SetPropertyClientData( "Point", 1234 )\r
+ if pg.GetPropertyClientData( "Point" ) != 1234:\r
+ raise ValueError("Set/GetPropertyClientData() failed")\r
+\r
+ # Test setting unicode string\r
+ pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")\r
+\r
+ #\r
+ # Test some code that *should* fail (but not crash)\r
+ #try:\r
+ #a_ = pg.GetPropertyValue( "NotARealProperty" )\r
+ #pg.EnableProperty( "NotAtAllRealProperty", False )\r
+ #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )\r
+ #except:\r
+ #pass\r
+ #raise\r
+\r
+ '''\r
+ sizer.Add(self.pg, 1, wx.EXPAND)\r
+ self.SetSizer(sizer)\r
+ sizer.SetSizeHints(self)\r
+\r
+ self.SelectedTreeItem = None\r
+\r
+ def GetPropertyValues(self):\r
+ return self.pg.GetPropertyValues()\r
+\r
+ def Initialize(self, properties):\r
+ pg = self.pg\r
+ pg.Clear()\r
+\r
+ if properties:\r
+ for element in properties:\r
+ if element[1]['type'] == 'arraystring':\r
+ elements = element[1]['elements']\r
+ if 'value' in element[1]:\r
+ property_value = element[1]['value']\r
+ else:\r
+ property_value = element[1]['default']\r
+ #retrieve individual strings\r
+ property_value = split(property_value, ' ')\r
+ #remove " delimiters\r
+ values = [value.strip('"') for value in property_value]\r
+ pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
+\r
+ if element[1]['type'] == 'boolean':\r
+ if 'value' in element[1]:\r
+ property_value = element[1].as_bool('value')\r
+ else:\r
+ property_value = element[1].as_bool('default')\r
+ property_control = wxpg.BoolProperty(element[0], value=property_value)\r
+ pg.Append(property_control)\r
+ pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
+\r
+ #if element[0] == 'category':\r
+ #pg.Append(wxpg.PropertyCategory(element[1]))\r
+\r
+ if element[1]['type'] == 'color':\r
+ if 'value' in element[1]:\r
+ property_value = element[1]['value']\r
+ else:\r
+ property_value = element[1]['default']\r
+ property_value = eval(property_value)\r
+ pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
+\r
+ if element[1]['type'] == 'enum':\r
+ elements = element[1]['elements']\r
+ if 'value' in element[1]:\r
+ property_value = element[1]['value']\r
+ else:\r
+ property_value = element[1]['default']\r
+ pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
+\r
+ if element[1]['type'] == 'filename':\r
+ if 'value' in element[1]:\r
+ property_value = element[1]['value']\r
+ else:\r
+ property_value = element[1]['default']\r
+ pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
+\r
+ if element[1]['type'] == 'float':\r
+ if 'value' in element[1]:\r
+ property_value = element[1].as_float('value')\r
+ else:\r
+ property_value = element[1].as_float('default')\r
+ property_control = wxpg.FloatProperty(element[0], value=property_value)\r
+ pg.Append(property_control)\r
+\r
+ if element[1]['type'] == 'folder':\r
+ if 'value' in element[1]:\r
+ property_value = element[1]['value']\r
+ else:\r
+ property_value = element[1]['default']\r
+ pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
+\r
+ if element[1]['type'] == 'integer':\r
+ if 'value' in element[1]:\r
+ property_value = element[1].as_int('value')\r
+ else:\r
+ property_value = element[1].as_int('default')\r
+ property_control = wxpg.IntProperty(element[0], value=property_value)\r
+ if 'maximum' in element[1]:\r
+ property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
+ if 'minimum' in element[1]:\r
+ property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
+ property_control.SetAttribute('Wrap', True)\r
+ pg.Append(property_control)\r
+ pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
+\r
+ if element[1]['type'] == 'string':\r
+ if 'value' in element[1]:\r
+ property_value = element[1]['value']\r
+ else:\r
+ property_value = element[1]['default']\r
+ pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
+\r
+ pg.Refresh()\r
+\r
+ def OnReserved(self, event):\r
+ pass\r
# Copyright\r
\r
"""Property editor panel for Hooke.\r
-"""\r
\r
-import sys\r
-import os.path\r
+wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_. Until\r
+then, we'll avoid it because of the *nix build problems.\r
\r
-import wx\r
-import wx.propgrid as wxpg\r
+This module hacks together a workaround to be used until 2.9.1 is\r
+widely installed (or at least released ;).\r
\r
-# There are many comments and code fragments in here from the demo app.\r
-# They should come in handy to expand the functionality in the future.\r
+.. _included:\r
+ http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
+"""\r
\r
-class Display (object):\r
- property_descriptor = []\r
- def __init__(self):\r
- pass\r
+import wx.grid\r
\r
-class ValueObject (object):\r
- def __init__(self):\r
- pass\r
+from . import Panel\r
\r
\r
-class IntProperty2 (wxpg.PyProperty):\r
- """This is a simple re-implementation of wxIntProperty.\r
+def prop_from_argument(argument, curves=None, playlists=None):\r
+ """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.\r
"""\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):\r
- wxpg.PyProperty.__init__(self, label, name)\r
- self.SetValue(value)\r
+ type = argument.type\r
+ if type in ['driver']: # intentionally not handled (yet)\r
+ return None\r
+ if argument.count != 1:\r
+ raise NotImplementedError(argument)\r
+ kwargs = {\r
+ 'label':argument.name,\r
+ 'default':argument.default,\r
+ 'help':argument.help(),\r
+ }\r
+ # type consolidation\r
+ if type == 'file':\r
+ type = 'path'\r
+ # type handling\r
+ if type in ['string', 'bool', 'int', 'float', 'path']:\r
+ _class = globals()['%sProperty' % type.capitalize()]\r
+ return _class(**kwargs)\r
+ elif type in ['curve', 'playlist']:\r
+ if type == 'curve':\r
+ choices = curves # extracted from the current playlist\r
+ else:\r
+ choices = playlists\r
+ return ChoiceProperty(choices=choices, **kwargs)\r
+ raise NotImplementedError(argument.type)\r
\r
- def GetClassName(self):\r
- return "IntProperty2"\r
+def prop_from_setting(setting):\r
+ """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.\r
+ """ \r
+ raise NotImplementedError()\r
\r
- def GetEditor(self):\r
- return "TextCtrl"\r
\r
- def GetValueAsString(self, flags):\r
- return str(self.GetValue())\r
+class Property (object):\r
+ def __init__(self, type, label, default, help=None):\r
+ self.type = type\r
+ self.label = label\r
+ self.default = default\r
+ self.help = help\r
\r
- def PyStringToValue(self, s, flags):\r
- try:\r
- v = int(s)\r
- if self.GetValue() != v:\r
- return v\r
- except TypeError:\r
- if flags & wxpg.PG_REPORT_ERROR:\r
- wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")\r
- return False\r
+ def get_editor(self):\r
+ """Return a suitable grid editor.\r
+ """\r
+ raise NotImplementedError()\r
\r
- def PyIntToValue(self, v, flags):\r
- if (self.GetValue() != v):\r
- return v\r
+ def get_renderer(self):\r
+ """Return a suitable grid renderer.\r
\r
+ Returns `None` if no special renderer is required.\r
+ """\r
+ return None\r
\r
-class PyFilesProperty(wxpg.PyArrayStringProperty):\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
- wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
- self.SetValue(value)\r
+ def string_for_value(self, value):\r
+ """Return a string representation of `value` for loading the table.\r
+ """\r
+ return str(value)\r
\r
- def OnSetValue(self, v):\r
- self.value = v\r
- self.display = ', '.join(self.value)\r
+ def value_for_string(self, string):\r
+ """Return the value represented by `string`.\r
+ """\r
+ return string\r
\r
- def GetValueAsString(self, argFlags):\r
- return self.display\r
\r
- def PyStringToValue(self, s, flags):\r
- return [a.strip() for a in s.split(',')]\r
+class StringProperty (Property):\r
+ def __init__(self, **kwargs):\r
+ assert 'type' not in kwargs, kwargs\r
+ if 'default' not in kwargs:\r
+ kwargs['default'] = 0\r
+ super(StringProperty, self).__init__(type='string', **kwargs)\r
\r
- def OnEvent(self, propgrid, ctrl, event):\r
- if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
- # Show dialog to select a string, call DoSetValue and\r
- # return True, if value changed.\r
- return True\r
+ def get_editor(self):\r
+ return wx.grid.GridCellTextEditor()\r
\r
- return False\r
+ def get_renderer(self):\r
+ return wx.grid.GridCellStringRenderer()\r
\r
\r
-class PyObjectPropertyValue:\r
- """\\r
- Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
- list of string given as argument to constructor.\r
- """\r
- def __init__(self, s=None):\r
- try:\r
- self.ls = [a.strip() for a in s.split('-')]\r
- except:\r
- self.ls = []\r
-\r
- def __repr__(self):\r
- return ' - '.join(self.ls)\r
-\r
-\r
-class PyObjectProperty(wxpg.PyProperty):\r
- """\\r
- Another simple example. This time our value is a PyObject (NOTE: we can't\r
- return an arbitrary python object in DoGetValue. It cannot be a simple\r
- type such as int, bool, double, or string, nor an array or wxObject based.\r
- Dictionary, None, or any user-specified Python object is allowed).\r
+class BoolProperty (Property):\r
+ """A boolean property.\r
+\r
+ Notes\r
+ -----\r
+ Unfortunately, changing a boolean property takes two clicks:\r
+\r
+ 1) create the editor\r
+ 2) change the value\r
+\r
+ There are `ways around this`_, but it's not pretty.\r
+\r
+ .. _ways around this:\r
+ http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click\r
"""\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
- wxpg.PyProperty.__init__(self, label, name)\r
- self.SetValue(value)\r
+ def __init__(self, **kwargs):\r
+ assert 'type' not in kwargs, kwargs\r
+ if 'default' not in kwargs:\r
+ kwargs['default'] = True\r
+ super(BoolProperty, self).__init__(type='bool', **kwargs)\r
\r
- def GetClassName(self):\r
- return self.__class__.__name__\r
+ def get_editor(self):\r
+ return wx.grid.GridCellBoolEditor()\r
\r
- def GetEditor(self):\r
- return "TextCtrl"\r
+ def get_renderer(self):\r
+ return wx.grid.GridCellBoolRenderer()\r
\r
- def GetValueAsString(self, flags):\r
- return repr(self.GetValue())\r
+ def string_for_value(self, value):\r
+ if value == True:\r
+ return '1'\r
+ return ''\r
\r
- def PyStringToValue(self, s, flags):\r
- return PyObjectPropertyValue(s)\r
+ def value_for_string(self, string):\r
+ return string == '1'\r
\r
\r
-class ShapeProperty(wxpg.PyEnumProperty):\r
- """\\r
- Demonstrates use of OnCustomPaint method.\r
- """\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
- wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
+class IntProperty (Property):\r
+ def __init__(self, **kwargs):\r
+ assert 'type' not in kwargs, kwargs\r
+ if 'default' not in kwargs:\r
+ kwargs['default'] = 0\r
+ super(IntProperty, self).__init__(type='int', **kwargs)\r
\r
- def OnMeasureImage(self, index):\r
- return wxpg.DEFAULT_IMAGE_SIZE\r
+ def get_editor(self):\r
+ return wx.grid.GridCellNumberEditor()\r
\r
- def OnCustomPaint(self, dc, rect, paint_data):\r
- """\\r
- paint_data.m_choiceItem is -1 if we are painting the control,\r
- in which case we need to get the drawn item using DoGetValue.\r
- """\r
- item = paint_data.m_choiceItem\r
- if item == -1:\r
- item = self.DoGetValue()\r
+ def get_renderer(self):\r
+ return wx.grid.GridCellNumberRenderer()\r
\r
- dc.SetPen(wx.Pen(wx.BLACK))\r
- dc.SetBrush(wx.Brush(wx.BLACK))\r
+ def value_for_string(self, string):\r
+ return int(string)\r
\r
- if item == 0:\r
- dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
- elif item == 1:\r
- half_width = rect.width / 2\r
- dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
- elif item == 2:\r
- dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
\r
+class FloatProperty (Property):\r
+ def __init__(self, **kwargs):\r
+ assert 'type' not in kwargs, kwargs\r
+ if 'default' not in kwargs:\r
+ kwargs['default'] = 0.0\r
+ super(FloatProperty, self).__init__(type='float', **kwargs)\r
\r
-class LargeImagePickerCtrl(wx.Window):\r
- """\\r
- Control created and used by LargeImageEditor.\r
- """\r
- def __init__(self):\r
- pre = wx.PreWindow()\r
- self.PostCreate(pre)\r
-\r
- def Create(self, parent, id_, pos, size, style = 0):\r
- wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
- img_spc = size[1]\r
- self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
- self.SetBackgroundColour(wx.WHITE)\r
- self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
- self.property = None\r
- self.bmp = None\r
- self.Bind(wx.EVT_PAINT, self.OnPaint)\r
-\r
- def OnPaint(self, event):\r
- dc = wx.BufferedPaintDC(self)\r
-\r
- whiteBrush = wx.Brush(wx.WHITE)\r
- dc.SetBackground(whiteBrush)\r
- dc.Clear()\r
-\r
- bmp = self.bmp\r
- if bmp:\r
- dc.DrawBitmap(bmp, 2, 2)\r
+ def get_editor(self):\r
+ return wx.grid.GridCellFloatEditor()\r
+\r
+ def get_renderer(self):\r
+ return wx.grid.GridCellFloatRenderer()\r
+\r
+ def value_for_string(self, string):\r
+ return float(string)\r
+\r
+\r
+class ChoiceProperty (Property):\r
+ def __init__(self, choices, **kwargs):\r
+ assert 'type' not in kwargs, kwargs\r
+ if 'default' in kwargs:\r
+ if kwargs['default'] not in choices:\r
+ choices.insert(0, kwargs['default'])\r
else:\r
- dc.SetPen(wx.Pen(wx.BLACK))\r
- dc.SetBrush(whiteBrush)\r
- dc.DrawRectangle(2, 2, 64, 64)\r
+ kwargs['default'] = choices[0]\r
+ super(ChoiceProperty, self).__init__(type='choice', **kwargs)\r
+ self._choices = choices\r
\r
- def RefreshThumbnail(self):\r
- """\\r
- We use here very simple image scaling code.\r
- """\r
- if not self.property:\r
- self.bmp = None\r
- return\r
+ def get_editor(self):\r
+ choices = [self.string_for_value(c) for c in self._choices]\r
+ return wx.grid.GridCellChoiceEditor(choices=choices)\r
\r
- path = self.property.DoGetValue()\r
+ def get_renderer(self):\r
+ return None\r
+ #return wx.grid.GridCellChoiceRenderer()\r
\r
- if not os.path.isfile(path):\r
- self.bmp = None\r
- return\r
+ def string_for_value(self, value):\r
+ if hasattr(value, 'name'):\r
+ return value.name\r
+ return str(value)\r
\r
- image = wx.Image(path)\r
- image.Rescale(64, 64)\r
- self.bmp = wx.BitmapFromImage(image)\r
+ def value_for_string(self, string):\r
+ for choice in self._choices:\r
+ if self.string_for_value(choice) == string:\r
+ return choice\r
+ raise ValueError(string)\r
\r
- def SetProperty(self, property):\r
- self.property = property\r
- self.tc.SetValue(property.GetDisplayedString())\r
- self.RefreshThumbnail()\r
\r
- def SetValue(self, s):\r
- self.RefreshThumbnail()\r
- self.tc.SetValue(s)\r
+class PathProperty (StringProperty):\r
+ """Simple file or path property.\r
\r
- def GetLastPosition(self):\r
- return self.tc.GetLastPosition()\r
+ Currently there isn't a fancy file-picker popup. Perhaps in the\r
+ future.\r
+ """\r
+ def __init__(self, **kwargs):\r
+ super(PathProperty, self).__init__(**kwargs)\r
+ self.type = 'path'\r
\r
\r
-class LargeImageEditor(wxpg.PyEditor):\r
- """\\r
- Double-height text-editor with image in front.\r
+class PropertyPanel(Panel, wx.grid.Grid):\r
+ """UI to view/set config values and command argsuments.\r
"""\r
- def __init__(self):\r
- wxpg.PyEditor.__init__(self)\r
+ def __init__(self, callbacks=None, **kwargs):\r
+ super(PropertyPanel, self).__init__(\r
+ name='property editor', callbacks=callbacks, **kwargs)\r
+ self._properties = []\r
+\r
+ self.CreateGrid(numRows=0, numCols=1)\r
+ self.SetColLabelValue(0, 'value')\r
\r
- def CreateControls(self, propgrid, property, pos, sz):\r
- try:\r
- h = 64 + 6\r
- x = propgrid.GetSplitterPosition()\r
- x2 = propgrid.GetClientSize().x\r
- bw = propgrid.GetRowHeight()\r
- lipc = LargeImagePickerCtrl()\r
- if sys.platform == 'win32':\r
- lipc.Hide()\r
- lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
- lipc.SetProperty(property)\r
- # Hmmm.. how to have two-stage creation without subclassing?\r
- #btn = wx.PreButton()\r
- #pre = wx.PreWindow()\r
- #self.PostCreate(pre)\r
- #if sys.platform == 'win32':\r
- # btn.Hide()\r
- #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
- btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
- return (lipc, btn)\r
- except:\r
- import traceback\r
- print traceback.print_exc()\r
-\r
- def UpdateControl(self, property, ctrl):\r
- ctrl.SetValue(property.GetDisplayedString())\r
-\r
- def DrawValue(self, dc, property, rect):\r
- if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
- dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
-\r
- def OnEvent(self, propgrid, ctrl, event):\r
- if not ctrl:\r
- return False\r
-\r
- evtType = event.GetEventType()\r
-\r
- if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
- if propgrid.IsEditorsValueModified():\r
- return True\r
-\r
- elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
- if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
- ctrl.GetLastPosition() > 0:\r
-\r
- # We must check this since an 'empty' text event\r
- # may be triggered when creating the property.\r
- PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
- if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
- event.Skip();\r
- event.SetId(propgrid.GetId());\r
-\r
- propgrid.EditorsValueWasModified();\r
-\r
- return False\r
-\r
-\r
- def CopyValueFromControl(self, property, ctrl):\r
- tc = ctrl.tc\r
- res = property.SetValueFromString(tc.GetValue(),0)\r
- # Changing unspecified always causes event (returning\r
- # true here should be enough to trigger it).\r
- if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
- res = True\r
-\r
- return res\r
-\r
- def SetValueToUnspecified(self, ctrl):\r
- ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
-\r
- def SetControlStringValue(self, ctrl, txt):\r
- ctrl.SetValue(txt)\r
-\r
- def OnFocus(self, property, ctrl):\r
- ctrl.tc.SetSelection(-1,-1)\r
- ctrl.tc.SetFocus()\r
-\r
-\r
-class PropertyEditor(wx.Panel):\r
-\r
- def __init__(self, parent):\r
- # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
- wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))\r
-\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
-\r
- self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)\r
-\r
- # Show help as tooltips\r
- self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)\r
-\r
- #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)\r
- #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)\r
- #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)\r
-\r
- # Needed by custom image editor\r
- wx.InitAllImageHandlers()\r
-\r
- #\r
- # Let's create a simple custom editor\r
- #\r
- # NOTE: Editor must be registered *before* adding a property that uses it.\r
- self.pg.RegisterEditor(LargeImageEditor)\r
-\r
- '''\r
- #\r
- # Add properties\r
- #\r
-\r
- pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )\r
- pg.Append( wxpg.StringProperty("String",value="Some Text") )\r
- pg.Append( wxpg.IntProperty("Int",value=100) )\r
- pg.Append( wxpg.FloatProperty("Float",value=100.0) )\r
- pg.Append( wxpg.BoolProperty("Bool",value=True) )\r
- pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )\r
- pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)\r
-\r
- pg.Append( wxpg.PropertyCategory("2 - More Properties") )\r
- pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )\r
- pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )\r
- pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )\r
- pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )\r
-\r
- pg.Append( wxpg.EnumProperty("Enum","Enum",\r
- ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],\r
- [10,11,12],0) )\r
- pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )\r
-\r
- pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )\r
- pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )\r
- pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )\r
- pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )\r
- pg.Append( wxpg.SystemColourProperty("SystemColour") )\r
- pg.Append( wxpg.ImageFileProperty("ImageFile") )\r
- pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )\r
-\r
- pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )\r
- pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )\r
- pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )\r
- pg.Append( wxpg.FontDataProperty("FontData") )\r
- pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )\r
- pg.SetPropertyEditor("IntWithSpin","SpinCtrl")\r
- pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )\r
- pg.SetPropertyHelpString( "String", "String Property help string!" )\r
- pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )\r
-\r
- pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )\r
- pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )\r
- pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )\r
-\r
- pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )\r
- pg.Append( IntProperty2("IntProperty2", value=1024) )\r
-\r
- pg.Append( ShapeProperty("ShapeProperty", value=0) )\r
- pg.Append( PyObjectProperty("PyObjectProperty") )\r
-\r
- pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )\r
- pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")\r
-\r
-\r
- pg.SetPropertyClientData( "Point", 1234 )\r
- if pg.GetPropertyClientData( "Point" ) != 1234:\r
- raise ValueError("Set/GetPropertyClientData() failed")\r
-\r
- # Test setting unicode string\r
- pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")\r
-\r
- #\r
- # Test some code that *should* fail (but not crash)\r
- #try:\r
- #a_ = pg.GetPropertyValue( "NotARealProperty" )\r
- #pg.EnableProperty( "NotAtAllRealProperty", False )\r
- #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )\r
- #except:\r
- #pass\r
- #raise\r
-\r
- '''\r
- sizer.Add(self.pg, 1, wx.EXPAND)\r
- self.SetSizer(sizer)\r
- sizer.SetSizeHints(self)\r
-\r
- self.SelectedTreeItem = None\r
-\r
- def GetPropertyValues(self):\r
- return self.pg.GetPropertyValues()\r
-\r
- def Initialize(self, properties):\r
- pg = self.pg\r
- pg.Clear()\r
-\r
- if properties:\r
- for element in properties:\r
- if element[1]['type'] == 'arraystring':\r
- elements = element[1]['elements']\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- #retrieve individual strings\r
- property_value = split(property_value, ' ')\r
- #remove " delimiters\r
- values = [value.strip('"') for value in property_value]\r
- pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
-\r
- if element[1]['type'] == 'boolean':\r
- if 'value' in element[1]:\r
- property_value = element[1].as_bool('value')\r
- else:\r
- property_value = element[1].as_bool('default')\r
- property_control = wxpg.BoolProperty(element[0], value=property_value)\r
- pg.Append(property_control)\r
- pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
-\r
- #if element[0] == 'category':\r
- #pg.Append(wxpg.PropertyCategory(element[1]))\r
-\r
- if element[1]['type'] == 'color':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- property_value = eval(property_value)\r
- pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
-\r
- if element[1]['type'] == 'enum':\r
- elements = element[1]['elements']\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
-\r
- if element[1]['type'] == 'filename':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
-\r
- if element[1]['type'] == 'float':\r
- if 'value' in element[1]:\r
- property_value = element[1].as_float('value')\r
- else:\r
- property_value = element[1].as_float('default')\r
- property_control = wxpg.FloatProperty(element[0], value=property_value)\r
- pg.Append(property_control)\r
-\r
- if element[1]['type'] == 'folder':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
-\r
- if element[1]['type'] == 'integer':\r
- if 'value' in element[1]:\r
- property_value = element[1].as_int('value')\r
- else:\r
- property_value = element[1].as_int('default')\r
- property_control = wxpg.IntProperty(element[0], value=property_value)\r
- if 'maximum' in element[1]:\r
- property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
- if 'minimum' in element[1]:\r
- property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
- property_control.SetAttribute('Wrap', True)\r
- pg.Append(property_control)\r
- pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
-\r
- if element[1]['type'] == 'string':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
-\r
- pg.Refresh()\r
-\r
- def OnReserved(self, event):\r
- pass\r
+ self._last_tooltip = None\r
+ self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_mouse_over)\r
+\r
+ def _on_mouse_over(self, event):\r
+ """Enable tooltips.\r
+ """\r
+ x,y = self.CalcUnscrolledPosition(event.GetPosition())\r
+ col,row = self.XYToCell(x, y)\r
+ if col == -1 or row == -1:\r
+ msg = ''\r
+ else:\r
+ msg = self._properties[row].help or ''\r
+ if msg != self._last_tooltip:\r
+ self._last_tooltip = msg\r
+ event.GetEventObject().SetToolTipString(msg)\r
+\r
+ def append_property(self, property):\r
+ if len([p for p in self._properties if p.label == property.label]) > 0:\r
+ raise ValueError(property) # property.label collision\r
+ self._properties.append(property)\r
+ row = len(self._properties) - 1\r
+ self.AppendRows(numRows=1)\r
+ self.SetRowLabelValue(row, property.label)\r
+ self.SetCellEditor(row=row, col=0, editor=property.get_editor())\r
+ r = property.get_renderer()\r
+ if r != None:\r
+ self.SetCellRenderer(row=row, col=0, renderer=r)\r
+ self.set_property(property.label, property.default)\r
+\r
+ def remove_property(self, label):\r
+ row,property = self._property_by_label(label)\r
+ self._properties.pop(row)\r
+ self.DeleteRows(pos=row)\r
+\r
+ def clear(self):\r
+ while(len(self._properties) > 0):\r
+ self.remove_property(self._properties[-1].label)\r
+\r
+ def set_property(self, label, value):\r
+ row,property = self._property_by_label(label)\r
+ self.SetCellValue(row=row, col=0, s=property.string_for_value(value))\r
+\r
+ def get_property(self, label):\r
+ row,property = self._property_by_label(label)\r
+ string = self.GetCellValue(row=row, col=0)\r
+ return property.value_for_string(string)\r
+\r
+ def get_values(self):\r
+ return dict([(p.label, self.get_property(p.label))\r
+ for p in self._properties])\r
+\r
+ def _property_by_label(self, label):\r
+ props = [(i,p) for i,p in enumerate(self._properties)\r
+ if p.label == label]\r
+ assert len(props) == 1, props\r
+ row,property = props[0]\r
+ return (row, property)\r
+++ /dev/null
-# Copyright\r
-\r
-"""Property editor panel for Hooke.\r
-\r
-wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_. Until\r
-then, we'll avoid it because of the *nix build problems.\r
-\r
-This module hacks together a workaround to be used until 2.9.1 is\r
-widely installed (or at least released ;).\r
-\r
-.. _included:\r
- http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
-"""\r
-\r
-import wx.grid\r
-\r
-from . import Panel\r
-\r
-\r
-def prop_from_argument(argument, curves=None, playlists=None):\r
- """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.\r
- """\r
- type = argument.type\r
- if type in ['driver']: # intentionally not handled (yet)\r
- return None\r
- if argument.count != 1:\r
- raise NotImplementedError(argument)\r
- kwargs = {\r
- 'label':argument.name,\r
- 'default':argument.default,\r
- 'help':argument.help(),\r
- }\r
- # type consolidation\r
- if type == 'file':\r
- type = 'path'\r
- # type handling\r
- if type in ['string', 'bool', 'int', 'float', 'path']:\r
- _class = globals()['%sProperty' % type.capitalize()]\r
- return _class(**kwargs)\r
- elif type in ['curve', 'playlist']:\r
- if type == 'curve':\r
- choices = curves # extracted from the current playlist\r
- else:\r
- choices = playlists\r
- return ChoiceProperty(choices=choices, **kwargs)\r
- raise NotImplementedError(argument.type)\r
-\r
-def prop_from_setting(setting):\r
- """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.\r
- """ \r
- raise NotImplementedError()\r
-\r
-\r
-class Property (object):\r
- def __init__(self, type, label, default, help=None):\r
- self.type = type\r
- self.label = label\r
- self.default = default\r
- self.help = help\r
-\r
- def get_editor(self):\r
- """Return a suitable grid editor.\r
- """\r
- raise NotImplementedError()\r
-\r
- def get_renderer(self):\r
- """Return a suitable grid renderer.\r
-\r
- Returns `None` if no special renderer is required.\r
- """\r
- return None\r
-\r
- def string_for_value(self, value):\r
- """Return a string representation of `value` for loading the table.\r
- """\r
- return str(value)\r
-\r
- def value_for_string(self, string):\r
- """Return the value represented by `string`.\r
- """\r
- return string\r
-\r
-\r
-class StringProperty (Property):\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = 0\r
- super(StringProperty, self).__init__(type='string', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellTextEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellStringRenderer()\r
-\r
-\r
-class BoolProperty (Property):\r
- """A boolean property.\r
-\r
- Notes\r
- -----\r
- Unfortunately, changing a boolean property takes two clicks:\r
-\r
- 1) create the editor\r
- 2) change the value\r
-\r
- There are `ways around this`_, but it's not pretty.\r
-\r
- .. _ways around this:\r
- http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click\r
- """\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = True\r
- super(BoolProperty, self).__init__(type='bool', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellBoolEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellBoolRenderer()\r
-\r
- def string_for_value(self, value):\r
- if value == True:\r
- return '1'\r
- return ''\r
-\r
- def value_for_string(self, string):\r
- return string == '1'\r
-\r
-\r
-class IntProperty (Property):\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = 0\r
- super(IntProperty, self).__init__(type='int', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellNumberEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellNumberRenderer()\r
-\r
- def value_for_string(self, string):\r
- return int(string)\r
-\r
-\r
-class FloatProperty (Property):\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = 0.0\r
- super(FloatProperty, self).__init__(type='float', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellFloatEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellFloatRenderer()\r
-\r
- def value_for_string(self, string):\r
- return float(string)\r
-\r
-\r
-class ChoiceProperty (Property):\r
- def __init__(self, choices, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' in kwargs:\r
- if kwargs['default'] not in choices:\r
- choices.insert(0, kwargs['default'])\r
- else:\r
- kwargs['default'] = choices[0]\r
- super(ChoiceProperty, self).__init__(type='choice', **kwargs)\r
- self._choices = choices\r
-\r
- def get_editor(self):\r
- choices = [self.string_for_value(c) for c in self._choices]\r
- return wx.grid.GridCellChoiceEditor(choices=choices)\r
-\r
- def get_renderer(self):\r
- return None\r
- #return wx.grid.GridCellChoiceRenderer()\r
-\r
- def string_for_value(self, value):\r
- if hasattr(value, 'name'):\r
- return value.name\r
- return str(value)\r
-\r
- def value_for_string(self, string):\r
- for choice in self._choices:\r
- if self.string_for_value(choice) == string:\r
- return choice\r
- raise ValueError(string)\r
-\r
-\r
-class PathProperty (StringProperty):\r
- """Simple file or path property.\r
-\r
- Currently there isn't a fancy file-picker popup. Perhaps in the\r
- future.\r
- """\r
- def __init__(self, **kwargs):\r
- super(PathProperty, self).__init__(**kwargs)\r
- self.type = 'path'\r
-\r
-\r
-class PropertyPanel(Panel, wx.grid.Grid):\r
- """UI to view/set config values and command argsuments.\r
- """\r
- def __init__(self, callbacks=None, **kwargs):\r
- super(PropertyPanel, self).__init__(\r
- name='propertyeditor', callbacks=callbacks, **kwargs)\r
- self._properties = []\r
-\r
- self.CreateGrid(numRows=0, numCols=1)\r
- self.SetColLabelValue(0, 'value')\r
-\r
- self._last_tooltip = None\r
- self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_mouse_over)\r
-\r
- def _on_mouse_over(self, event):\r
- """Enable tooltips.\r
- """\r
- x,y = self.CalcUnscrolledPosition(event.GetPosition())\r
- col,row = self.XYToCell(x, y)\r
- if col == -1 or row == -1:\r
- msg = ''\r
- else:\r
- msg = self._properties[row].help or ''\r
- if msg != self._last_tooltip:\r
- self._last_tooltip = msg\r
- event.GetEventObject().SetToolTipString(msg)\r
-\r
- def append_property(self, property):\r
- if len([p for p in self._properties if p.label == property.label]) > 0:\r
- raise ValueError(property) # property.label collision\r
- self._properties.append(property)\r
- row = len(self._properties) - 1\r
- self.AppendRows(numRows=1)\r
- self.SetRowLabelValue(row, property.label)\r
- self.SetCellEditor(row=row, col=0, editor=property.get_editor())\r
- r = property.get_renderer()\r
- if r != None:\r
- self.SetCellRenderer(row=row, col=0, renderer=r)\r
- self.set_property(property.label, property.default)\r
-\r
- def remove_property(self, label):\r
- row,property = self._property_by_label(label)\r
- self._properties.pop(row)\r
- self.DeleteRows(pos=row)\r
-\r
- def clear(self):\r
- while(len(self._properties) > 0):\r
- self.remove_property(self._properties[-1].label)\r
-\r
- def set_property(self, label, value):\r
- row,property = self._property_by_label(label)\r
- self.SetCellValue(row=row, col=0, s=property.string_for_value(value))\r
-\r
- def get_property(self, label):\r
- row,property = self._property_by_label(label)\r
- string = self.GetCellValue(row=row, col=0)\r
- return property.value_for_string(string)\r
-\r
- def get_values(self):\r
- return dict([(p.label, self.get_property(p.label))\r
- for p in self._properties])\r
-\r
- def _property_by_label(self, label):\r
- props = [(i,p) for i,p in enumerate(self._properties)\r
- if p.label == label]\r
- assert len(props) == 1, props\r
- row,property = props[0]\r
- return (row, property)\r