# 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