Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/propwidgets.py
diff options
context:
space:
mode:
authorVincent Vinet <vince.vinet@gmail.com>2009-11-19 16:33:26 (GMT)
committer Vincent Vinet <vince.vinet@gmail.com>2009-11-19 16:34:28 (GMT)
commitdee6412f6beae82952ed0a07fc2bfdfb0cbb3e79 (patch)
tree1fa73b93a70150dc23f610d34e8b62e727f0f2da /tutorius/propwidgets.py
parent40f836b057896469bca69772d9fc7168b2f8c644 (diff)
refac property editing in the creator
Diffstat (limited to 'tutorius/propwidgets.py')
-rw-r--r--tutorius/propwidgets.py489
1 files changed, 489 insertions, 0 deletions
diff --git a/tutorius/propwidgets.py b/tutorius/propwidgets.py
new file mode 100644
index 0000000..7e78ba4
--- /dev/null
+++ b/tutorius/propwidgets.py
@@ -0,0 +1,489 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+Property Widgets.
+
+Allows displaying properties cleanly.
+"""
+import gtk
+import gobject
+
+from . import gtkutils, overlayer
+###########################################################################
+# Dialog classes
+###########################################################################
+class TextInputDialog(gtk.MessageDialog):
+ def __init__(self, parent, text, field):
+ gtk.MessageDialog.__init__(self, parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK,
+ None)
+ self.set_markup(text)
+ self.entry = gtk.Entry()
+ self.entry.connect("activate", self._dialog_done_cb, gtk.RESPONSE_OK)
+ hbox = gtk.HBox()
+ lbl = gtk.Label(field)
+ hbox.pack_start(lbl, False)
+ hbox.pack_end(self.entry)
+ self.vbox.pack_end(hbox, True, True)
+ self.show_all()
+
+ def pop(self):
+ self.run()
+ self.hide()
+ text = self.entry.get_text()
+ return text
+
+ def _dialog_done_cb(self, entry, response):
+ self.response(response)
+
+class SignalInputDialog(gtk.MessageDialog):
+ def __init__(self, parent, text, field, addr):
+ """
+ Create a gtk signal selection dialog.
+
+ @param parent: the parent window this dialog should stay over.
+ @param text: the title of the dialog.
+ @param field: the field description of the dialog.
+ @param addr: the widget address from which to fetch signal list.
+ """
+ gtk.MessageDialog.__init__(self, parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK,
+ None)
+ self.set_markup(text)
+ self.model = gtk.ListStore(str)
+ widget = gtkutils.find_widget(parent, addr)
+ for signal_name in gobject.signal_list_names(widget):
+ self.model.append(row=(signal_name,))
+ self.entry = gtk.ComboBox(self.model)
+ cell = gtk.CellRendererText()
+ self.entry.pack_start(cell)
+ self.entry.add_attribute(cell, 'text', 0)
+ hbox = gtk.HBox()
+ lbl = gtk.Label(field)
+ hbox.pack_start(lbl, False)
+ hbox.pack_end(self.entry)
+ self.vbox.pack_end(hbox, True, True)
+ self.show_all()
+
+ def pop(self):
+ """
+ Show the dialog. It will run in it's own loop and return control
+ to the caller when a signal has been selected.
+
+ @returns: a signal name or None if no signal was selected
+ """
+ self.run()
+ self.hide()
+ iter = self.entry.get_active_iter()
+ if iter:
+ text = self.model.get_value(iter, 0)
+ return text
+ return None
+
+ def _dialog_done_cb(self, entry, response):
+ self.response(response)
+
+class WidgetSelector(object):
+ """
+ Allow selecting a widget from within a window without interrupting the
+ flow of the current call.
+
+ The selector will run on the specified window until either a widget
+ is selected or abort() gets called.
+ """
+ def __init__(self, window):
+ super(WidgetSelector, self).__init__()
+ self.window = window
+ self._intro_mask = None
+ self._intro_handle = None
+ self._select_handle = None
+ self._prelight = None
+
+ def select(self):
+ """
+ Starts selecting a widget, by grabbing control of the mouse and
+ highlighting hovered widgets until one is clicked.
+ @returns: a widget address or None
+ """
+ if not self._intro_mask:
+ self._prelight = None
+ self._intro_mask = overlayer.Mask(catch_events=True)
+ self._select_handle = self._intro_mask.connect_after(
+ "button-press-event", self._end_introspect)
+ self._intro_handle = self._intro_mask.connect_after(
+ "motion-notify-event", self._intro_cb)
+ self.window._overlayer.put(self._intro_mask, 0, 0)
+ self.window._overlayer.queue_draw()
+
+ while bool(self._intro_mask) and not gtk.main_iteration():
+ pass
+
+ return gtkutils.raddr_lookup(self._prelight)
+
+ def _end_introspect(self, widget, evt):
+ if evt.type == gtk.gdk.BUTTON_PRESS and self._prelight:
+ self._intro_mask.catch_events = False
+ self._intro_mask.disconnect(self._intro_handle)
+ self._intro_handle = None
+ self._intro_mask.disconnect(self._select_handle)
+ self._select_handle = None
+ self.window._overlayer.remove(self._intro_mask)
+ self._intro_mask = None
+ # for some reason, gtk may not redraw after this unless told to.
+ self.window.queue_draw()
+
+ def _intro_cb(self, widget, evt):
+ """
+ Callback for capture of widget events, when in introspect mode.
+ """
+ # widget has focus, let's hilight it
+ win = gtk.gdk.display_get_default().get_window_at_pointer()
+ if not win:
+ return
+ click_wdg = win[0].get_user_data()
+ if not click_wdg.is_ancestor(self.window._overlayer):
+ # as popups are not (yet) supported, it would break
+ # badly if we were to play with a widget not in the
+ # hierarchy.
+ return
+ for hole in self._intro_mask.pass_thru:
+ self._intro_mask.mask(hole)
+ self._intro_mask.unmask(click_wdg)
+ self._prelight = click_wdg
+
+ self.window.queue_draw()
+
+ def abort(self):
+ """
+ Ends the selection. The control will return to the select() caller
+ with a return value of None, as selection was aborted.
+ """
+ self._intro_mask.catch_events = False
+ self._intro_mask.disconnect(self._intro_handle)
+ self._intro_handle = None
+ self._intro_mask.disconnect(self._select_handle)
+ self._select_handle = None
+ self.window._overlayer.remove(self._intro_mask)
+ self._intro_mask = None
+ self._prelight = None
+
+###########################################################################
+# Property Widget Classes
+###########################################################################
+class PropWidget(object):
+ """
+ Base Class for property editing widgets.
+ Subclasses should implement create_widget, run_dialog and refresh_widget
+ """
+ def __init__(self, parent, edit_object, prop_name, changed_callback=None):
+ """Constructor
+ @param parent parent widget
+ @param edit_object TPropContainer being edited
+ @param prop_name name of property being edited
+ @param changed_callback optional callable to call on value changes
+ """
+ self._parent = parent
+ self._edit_object = edit_object
+ self._propname = prop_name
+ self._widget = None
+ self._changed_cb = changed_callback
+
+ ############################################################
+ # Begin Properties
+ ############################################################
+ def set_objprop(self, value):
+ """Setter for object property value"""
+ setattr(self._edit_object, self._propname, value)
+ def get_objprop(self):
+ """Getter for object property value"""
+ return getattr(self._edit_object, self._propname)
+ def _get_widget(self):
+ """Getter for widget. Creates the widget if necessary"""
+ if self._widget is None:
+ self._widget = self.create_widget(self.obj_prop)
+ return self._widget
+ def _get_prop_class(self):
+ """Getter for property type"""
+ return getattr(type(self._edit_object), self._propname)
+ def _get_parent(self):
+ """Getter for parent"""
+ return self._parent
+
+ obj_prop = property(get_objprop, set_objprop)
+ widget = property(_get_widget)
+ prop_class = property(_get_prop_class)
+ parent = property(_get_parent)
+
+ ############################################################
+ # End Properties
+ ############################################################
+
+ def notify(self):
+ """Notify a calling object that the property was changed"""
+ if self._changed_cb:
+ self._changed_cb()
+
+ ############################################################
+ # Public Interface -- Redefine those function in subclasses
+ ############################################################
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ widget = gtk.Entry()
+ widget.set_text(str(init_value or ""))
+ return widget
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ raise NotImplementedError()
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ pass
+
+class StringPropWidget(PropWidget):
+ """
+ Allows editing a str property
+ """
+ @classmethod
+ def _extract_value(cls, widget):
+ """
+ Class Method
+ extracts the value from the widget
+ """
+ buf = widget.get_buffer()
+ return cls._from_text(
+ buf.get_text(buf.get_start_iter(), buf.get_end_iter())
+ )
+
+ @classmethod
+ def _from_text(cls, text):
+ """
+ Class Method
+ transforms the text value into the correct type if required
+ """
+ return text
+
+ def _text_changed(self, widget, evt):
+ """callback for text change event in the edit box"""
+ self.obj_prop = self._extract_value(widget)
+ self.notify()
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ propwdg = gtk.TextView()
+ propwdg.get_buffer().set_text(init_value or "")
+ propwdg.connect_after("focus-out-event", \
+ self._text_changed)
+
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ self.widget.get_buffer().set_text(str(self.obj_prop)) #unicode() ?
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ dlg = TextInputDialog(parent,
+ text="Mandatory property",
+ field=propname)
+ setattr(obj_prop, propname, cls._from_text(dlg.pop()))
+
+class IntPropWidget(StringPropWidget):
+ """
+ Allows editing an int property with boundaries
+ """
+ @classmethod
+ def _extract_value(cls, widget):
+ """
+ Class Method
+ extracts the value from the widget
+ """
+ return widget.get_value_as_int()
+
+ @classmethod
+ def _from_text(cls, text):
+ """
+ Class Method
+ transforms the text value into the correct type if required
+ """
+ return int(text)
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ prop = self.prop_class
+ adjustment = gtk.Adjustment(value=self.obj_prop,
+ lower=prop.lower_limit.limit,
+ upper=prop.upper_limit.limit,
+ step_incr=1)
+ propwdg = gtk.SpinButton(adjustment=adjustment)
+ propwdg.connect_after("focus-out-event", \
+ self._text_changed)
+
+class FloatPropWidget(StringPropWidget):
+ """Allows editing a float property"""
+ @classmethod
+ def _from_text(cls, text):
+ """
+ Class Method
+ transforms the text value into the correct type if required
+ """
+ return float(text)
+
+class UAMPropWidget(PropWidget):
+ """Allows editing an UAM property with a widget chooser"""
+ def _show_uam_chooser(self, widget):
+ """show the UAM chooser"""
+ selector = WidgetSelector(self.parent)
+ self.obj_prop = selector.select()
+ self.notify()
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ propwdg = gtk.Button(self.obj_prop)
+ propwdg.connect_after("clicked", self._show_uam_chooser)
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ self.widget.set_label(self.obj_prop)
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ selector = WidgetSelector(parent)
+ value = selector.select()
+ setattr(obj_prop, propname, selector.select())
+
+class EventTypePropWidget(PropWidget):
+ """Allows editing an EventType property"""
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ self.widget.set_text(str(self.obj_prop))
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ try:
+ dlg = SignalInputDialog(parent,
+ text="Mandatory property",
+ field=propname,
+ addr=obj_prop.object_id)
+ setattr(obj_prop, propname, dlg.pop())
+ except AttributeError:
+ return
+
+class IntArrayPropWidget(PropWidget):
+ """Allows editing an array of ints property"""
+ def _item_changed(self, widget, evt, idx):
+ """callback for text changed in one of the entries"""
+ try:
+ #Save props as tuples so that they can be hashed
+ attr = list(self.obj_prop)
+ attr[idx] = int(widget.get_text())
+ self.obj_prop = tuple(attr)
+ except ValueError:
+ widget.set_text(str(self.obj_prop[idx]))
+ self.notify()
+
+ def create_widget(self, init_value=None):
+ """
+ Create the Edit Widget for a property
+ @param init_value initial value
+ @return gtk.Widget
+ """
+ value = self.obj_prop
+ propwdg = gtk.HBox()
+ for i in xrange(len(value)):
+ entry = gtk.Entry()
+ entry.set_text(str(value[i]))
+ propwdg.pack_start(entry)
+ entry.connect_after("focus-out-event", \
+ self._item_changed, i)
+ return propwdg
+
+ def refresh_widget(self):
+ """
+ Force the widget to update it's value in case the property has changed
+ """
+ children = self.widget.get_children()
+ value = self.obj_prop
+ for i in xrange(len(value)):
+ children[i].set_text(str(value[i]))
+
+ @classmethod
+ def run_dialog(cls, parent, obj_prop, propname):
+ """
+ Class Method.
+ Prompts the user for changing an object's property
+ @param parent widget
+ @param obj_prop TPropContainer to edit
+ @param propname name of property to edit
+ """
+ pass