def add_operator_storage(klass): """ Add a member to the class named '__op_storage__'. This member will be added as needed, and can be used to store instance specific data needed by the operators. The value of the storage will be a sortedmap. """ members = klass.members() if "__op_storage__" not in members: m = Typed(sortedmap, ()) m.set_name("__op_storage__") m.set_index(len(members)) members["__op_storage__"] = m
class WxDateSelector(WxBoundedDate, ProxyDateSelector): """ A Wx implementation of an Enaml ProxyDateSelector. """ #: A reference to the widget created by the proxy. widget = Typed(wx.DatePickerCtrl) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the wx.DatePickerCtrl widget. """ self.widget = wx.DatePickerCtrl(self.parent_widget()) def init_widget(self): """ Initialize the widget. """ super(WxDateSelector, self).init_widget() d = self.declaration self.set_date_format(d.date_format) self.set_calendar_popup(d.calendar_popup) self.widget.Bind(wx.EVT_DATE_CHANGED, self.on_date_changed) #-------------------------------------------------------------------------- # Abstract API Implementation #-------------------------------------------------------------------------- def get_date(self): """ Return the current date in the control. Returns ------- result : date The current control date as a date object. """ return as_py_date(self.widget.GetValue()) def set_minimum(self, date): """ Set the widget's minimum date. Parameters ---------- date : date The date object to use for setting the minimum date. """ widget = self.widget widget.SetRange(as_wx_date(date), widget.GetUpperLimit()) def set_maximum(self, date): """ Set the widget's maximum date. Parameters ---------- date : date The date object to use for setting the maximum date. """ widget = self.widget widget.SetRange(widget.GetLowerLimit(), as_wx_date(date)) def set_date(self, date): """ Set the widget's current date. Parameters ---------- date : date The date object to use for setting the date. """ self._guard |= CHANGED_GUARD try: self.widget.SetValue(as_wx_date(date)) finally: self._guard &= ~CHANGED_GUARD def set_date_format(self, format): """ Set the widget's date format. Parameters ---------- format : str A Python time formatting string. .. note:: Changing the format on wx is not supported. See http://trac.wxwidgets.org/ticket/10988 """ pass def set_calendar_popup(self, popup): """ This is not supported on Wx. """ pass
class Season(FormulaModel): season = Coerced(int) races = Property() _races = Typed(Races) drivers = Property() _drivers = Typed(Drivers) constructors = Property() _constructors = Typed(Constructors) def _get_races(self): if not self._races: self.races = self.api.races(year=self.season) return self._races def _set_races(self, races): if not isinstance(races, Races): self._races = Races(races) else: self._races = races def _get_drivers(self): if not self._drivers: self.drivers = self.api.query(year=self.season, query_type='drivers') return self._drivers def _set_drivers(self, drivers): if not isinstance(drivers, Drivers): self._drivers = Drivers(drivers) else: self._drivers = drivers def _get_constructors(self): if not self._constructors: self.constructors = self.api.query(year=self.season, query_type='constructors') return self._constructors def _set_constructors(self, constructors): if not isinstance(constructors, Drivers): self._constructors = Drivers(constructors) else: self._constructors = constructors @classmethod def from_dict(cls, kwargs): if 'races' in kwargs.keys(): kwargs['races'] = [ Race.from_dict(race) for race in kwargs.pop('races') ] return cls(**kwargs) def to_rows(self): if self.races: rows = [] for race in self.races: race_row = race.to_row() race_row['season'] = self.season rows.append(race_row) else: rows = {'season': self.season} return rows def to_row(self): return {'season': self.season} def to_df(self): return pd.DataFrame(self.to_rows()) def __repr__(self): return self.__id__ @property def __id__(self): return 's' + str(self.season)
class QtDockItem(QtWidget, ProxyDockItem): """ A Qt implementation of an Enaml ProxyDockItem. """ #: A reference to the widget created by the proxy. widget = Typed(QCustomDockItem) #: Cyclic notification guard. This a bitfield of multiple guards. _guard = Int(0) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying QDockItem widget. """ self.widget = QCustomDockItem(self.parent_widget()) def init_widget(self): """ Initialize the state of the underlying widget. """ super(QtDockItem, self).init_widget() d = self.declaration self.set_title(d.title) self.set_title_editable(d.title_editable) if not d.title_bar_visible: self.set_title_bar_visible(d.title_bar_visible) if d.icon is not None: self.set_icon(d.icon) if -1 not in d.icon_size: self.set_icon_size(d.icon_size) self.set_stretch(d.stretch) self.set_closable(d.closable) def init_layout(self): """ Initialize the layout for the underyling widget. """ super(QtDockItem, self).init_layout() widget = self.widget widget.setDockWidget(self.dock_widget()) # Use a queued connection so the dock manager can finish # closing the dock item before the signal handler runs. widget.titleEdited.connect(self.on_title_edited) widget.titleBarRightClicked.connect(self.on_title_bar_right_clicked) widget.closed.connect(self.on_closed, Qt.QueuedConnection) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def dock_widget(self): """ Find and return the dock widget child for this widget. """ d = self.declaration.dock_widget() if d is not None: return d.proxy.widget #-------------------------------------------------------------------------- # Reimplementations #-------------------------------------------------------------------------- def refresh_style_sheet(self): """ A reimplemented styling method. The dock item uses custom stylesheet processing. """ parts = [] name = self.widget.objectName() for style in StyleCache.styles(self.declaration): t = translate_dock_item_style(name, style) if t: parts.append(t) if len(parts) > 0: stylesheet = '\n\n'.join(parts) else: stylesheet = '' self.widget.setStyleSheet(stylesheet) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def on_title_edited(self, text): """ Handle the 'titleEdited' signal on the dock item. """ d = self.declaration if d is not None: self._guard |= TITLE_GUARD try: d.title = text finally: self._guard &= ~TITLE_GUARD def on_title_bar_right_clicked(self, pos): """ Handle the 'titleBarRightClicked' signal on the dock item. """ d = self.declaration if d is not None: d.title_bar_right_clicked() def on_closed(self): """ Handle the closed signal from the dock item. """ d = self.declaration if d is not None: d._item_closed() #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def child_added(self, child): """ Handle the child added event for a QtDockItem. """ super(QtDockItem, self).child_added(child) self.widget.setDockWidget(self.dock_widget()) def child_removed(self, child): """ Handle the child added event for a QtDockItem. """ super(QtDockItem, self).child_removed(child) self.widget.setDockWidget(self.dock_widget()) #-------------------------------------------------------------------------- # ProxyDockItem API #-------------------------------------------------------------------------- def set_title(self, title): """ Set the title on the underlying widget. """ if not self._guard & TITLE_GUARD: self.widget.setTitle(title) def set_title_editable(self, editable): """ Set the title editable state on the underlying widget. """ self.widget.setTitleEditable(editable) def set_title_bar_visible(self, visible): """ Set the visibility of the widget's title bar. """ self.widget.setTitleBarForceHidden(not visible) def set_icon(self, icon): """ Set the icon on the underlying widget. """ if icon: qicon = get_cached_qicon(icon) else: qicon = QIcon() self.widget.setIcon(qicon) def set_icon_size(self, size): """ Set the icon size on the underlying widget. """ self.widget.setIconSize(QSize(*size)) def set_stretch(self, stretch): """ Set the stretch factor for the underlyling widget. """ sp = self.widget.sizePolicy() sp.setHorizontalStretch(stretch) sp.setVerticalStretch(stretch) self.widget.setSizePolicy(sp) def set_closable(self, closable): """ Set the closable flag for the underlying widget. """ self.widget.setClosable(closable) def alert(self, level, on, off, repeat, persist): """ Set the alert level on the underlying widget. """ self.widget.alert(level, on, off, repeat, persist)
class QtComboBox(QtControl, ProxyComboBox): """ A Qt implementation of an Enaml ComboBox. """ #: A reference to the widget created by the proxy. widget = Typed(QComboBox) #: Cyclic notification guard. This a bitfield of multiple guards. _guard = Int(0) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the QComboBox widget. """ box = QComboBox(self.parent_widget()) box.setInsertPolicy(QComboBox.NoInsert) self.widget = box def init_widget(self): """ Create and initialize the underlying widget. """ super(QtComboBox, self).init_widget() d = self.declaration self.set_items(d.items) self.set_index(d.index) self.set_editable(d.editable) self.widget.currentIndexChanged.connect(self.on_index_changed) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def on_index_changed(self): """ The signal handler for the index changed signal. """ if not self._guard & INDEX_GUARD: self.declaration.index = self.widget.currentIndex() #-------------------------------------------------------------------------- # ProxyComboBox API #-------------------------------------------------------------------------- def set_items(self, items): """ Set the items of the ComboBox. """ widget = self.widget count = widget.count() nitems = len(items) for idx, item in enumerate(items[:count]): widget.setItemText(idx, item) if nitems > count: for item in items[count:]: widget.addItem(item) elif nitems < count: for idx in reversed(range(nitems, count)): widget.removeItem(idx) def set_index(self, index): """ Set the current index of the ComboBox. """ self._guard |= INDEX_GUARD try: self.widget.setCurrentIndex(index) finally: self._guard &= ~INDEX_GUARD def set_editable(self, editable): """ Set whether the combo box is editable. """ # The update is needed to avoid artificats (at least on Windows) widget = self.widget widget.setEditable(editable) widget.update()
class QtLabel(QtControl, ProxyLabel): """ A Qt implementation of an Enaml ProxyLabel. """ #: A reference to the widget created by the proxy. widget = Typed(QLabel) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying label widget. """ self.widget = QLabel(self.parent_widget()) def init_widget(self): """ Initialize the underlying widget. """ super(QtLabel, self).init_widget() d = self.declaration self.set_text(d.text) self.set_align(d.align) self.set_vertical_align(d.vertical_align) self.widget.linkActivated.connect(self.on_link_activated) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def on_link_activated(self, link): """ Handle the link activated signal. """ self.declaration.link_activated(link) #-------------------------------------------------------------------------- # ProxyLabel API #-------------------------------------------------------------------------- def set_text(self, text): """ Set the text in the widget. """ with self.geometry_guard(): self.widget.setText(text) def set_align(self, align): """ Set the alignment of the text in the widget. """ widget = self.widget alignment = widget.alignment() alignment &= ~Qt.AlignHorizontal_Mask alignment |= ALIGN_MAP[align] widget.setAlignment(alignment) def set_vertical_align(self, align): """ Set the vertical alignment of the text in the widget. """ widget = self.widget alignment = widget.alignment() alignment &= ~Qt.AlignVertical_Mask alignment |= VERTICAL_ALIGN_MAP[align] widget.setAlignment(alignment)
class QtMenu(QtToolkitObject, ProxyMenu): """ A Qt implementation of an Enaml ProxyMenu. """ #: A reference to the widget created by the proxy. widget = Typed(QCustomMenu) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying menu widget. """ self.widget = QCustomMenu(self.parent_widget()) def init_widget(self): """ Initialize the widget. """ super(QtMenu, self).init_widget() d = self.declaration self.set_title(d.title) self.set_enabled(d.enabled) self.set_visible(d.visible) self.set_context_menu(d.context_menu) def init_layout(self): """ Initialize the layout of the widget. """ super(QtMenu, self).init_layout() widget = self.widget for child in self.children(): if isinstance(child, QtMenu): widget.addMenu(child.widget) elif isinstance(child, QtAction): widget.addAction(child.widget) elif isinstance(child, QtActionGroup): widget.addActions(child.actions()) #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def find_next_action(self, child): """ Get the QAction instance which follows the child. Parameters ---------- child : QtToolkitObject The child of interest. Returns ------- result : QAction or None The QAction which comes immediately after the actions of the given child, or None if no actions follow the child. """ found = False for dchild in self.children(): if found: if isinstance(dchild, QtMenu): return dchild.widget.menuAction() if isinstance(dchild, QtAction): return dchild.widget if isinstance(dchild, QtActionGroup): acts = dchild.actions() if len(acts) > 0: return acts[0] else: found = dchild is child def child_added(self, child): """ Handle the child added event for a QtMenu. """ super(QtMenu, self).child_added(child) if isinstance(child, QtMenu): before = self.find_next_action(child) self.widget.insertMenu(before, child.widget) elif isinstance(child, QtAction): before = self.find_next_action(child) self.widget.insertAction(before, child.widget) elif isinstance(child, QtActionGroup): before = self.find_next_action(child) self.widget.insertActions(before, child.actions()) def child_removed(self, child): """ Handle the child removed event for a QtMenu. """ super(QtMenu, self).child_removed(child) if isinstance(child, QtMenu): self.widget.removeAction(child.widget.menuAction()) elif isinstance(child, QtAction): self.widget.removeAction(child.widget) elif isinstance(child, QtActionGroup): self.widget.removeActions(child.actions()) #-------------------------------------------------------------------------- # ProxyMenu API #-------------------------------------------------------------------------- def set_title(self, title): """ Set the title of the underlying widget. """ self.widget.setTitle(title) def set_visible(self, visible): """ Set the visibility on the underlying widget. """ self.widget.menuAction().setVisible(visible) def set_enabled(self, enabled): """ Set the enabled state of the widget. """ self.widget.setEnabled(enabled) def set_context_menu(self, context): """ Set whether or not the menu is a context menu. """ self.widget.setContextMenu(context) def popup(self): """ Popup the menu over the current mouse location. """ self.widget.exec_(QCursor.pos())
class QtStack(QtConstraintsWidget, ProxyStack): """ A Qt implementation of an Enaml Stack. """ #: A reference to the widget created by the proxy. widget = Typed(QStack) #: Cyclic notification guards _guard = Int(0) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying QStack widget. """ self.widget = QStack(self.parent_widget()) def init_widget(self): """ Initialize the underlying control. """ super(QtStack, self).init_widget() d = self.declaration self.set_transition(d.transition) self.set_size_hint_mode(d.size_hint_mode, update=False) def init_layout(self): """ Initialize the layout of the underlying control. """ super(QtStack, self).init_layout() widget = self.widget for item in self.stack_items(): widget.addWidget(item) # Bypass the transition effect during initialization. widget.setCurrentIndex(self.declaration.index) widget.layoutRequested.connect(self.on_layout_requested) widget.currentChanged.connect(self.on_current_changed) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def stack_items(self): """ Get the stack items defined on the control. """ for d in self.declaration.stack_items(): w = d.proxy.widget if w is not None: yield w #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def child_added(self, child): """ Handle the child added event for a QtStack. """ super(QtStack, self).child_added(child) if isinstance(child, QtStackItem): for index, dchild in enumerate(self.children()): if child is dchild: self.widget.insertWidget(index, child.widget) def child_removed(self, child): """ Handle the child removed event for a QtStack. """ super(QtStack, self).child_removed(child) if isinstance(child, QtStackItem): self.widget.removeWidget(child.widget) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def on_layout_requested(self): """ Handle the `layoutRequested` signal from the QStack. """ self.geometry_updated() def on_current_changed(self): """ Handle the `currentChanged` signal from the QStack. """ if not self._guard & INDEX_FLAG: self._guard |= INDEX_FLAG try: self.declaration.index = self.widget.currentIndex() finally: self._guard &= ~INDEX_FLAG #-------------------------------------------------------------------------- # Widget Update Methods #-------------------------------------------------------------------------- def set_index(self, index): """ Set the current index of the underlying widget. """ if not self._guard & INDEX_FLAG: self._guard |= INDEX_FLAG try: self.widget.transitionTo(index) finally: self._guard &= ~INDEX_FLAG def set_transition(self, transition): """ Set the transition on the underlying widget. """ if transition: self.widget.setTransition(make_transition(transition)) else: self.widget.setTransition(None) def set_size_hint_mode(self, mode, update=True): """ Set the size hint mode for the widget. """ self.widget.setSizeHintMode(SIZE_HINT_MODE[mode]) if update: self.geometry_updated()
class QtColorDialog(QtToolkitDialog, ProxyColorDialog): """ A Qt implementation of an Enaml ProxyColorDialog. """ #: A reference to the widget created by the proxy. widget = Typed(QColorDialogEx) #: Cyclic notification guard. This a bitfield of multiple guards. _guard = Int(0) def create_widget(self): """ Create the underlying QColorDialog. """ self.widget = QColorDialogEx(self.parent_widget()) def init_widget(self): """ Initialize the underlying widget. """ super(QtColorDialog, self).init_widget() d = self.declaration self.set_current_color(d.current_color) self.set_show_alpha(d.show_alpha) self.set_show_buttons(d.show_buttons) widget = self.widget widget.currentColorChanged.connect(self.on_current_color_changed) widget.colorSelected.connect(self.on_color_selected) # use the custom finished signal instead of the superclass' widget.finished.disconnect(self.on_finished) widget.reallyFinished.connect(self.on_finished) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def get_default_title(self): """ Get the default window title for the color dialog. """ return u'Select Color' #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def on_current_color_changed(self, qcolor): """ Handle the 'currentColorChanged' signal from the widget. """ d = self.declaration if d is not None: self._guard |= CURRENT_GUARD try: d.current_color = color_from_qcolor(qcolor) finally: self._guard &= ~CURRENT_GUARD def on_color_selected(self, qcolor): """ Handle the 'colorSelected' signal from the widget. """ d = self.declaration if d is not None: d.selected_color = color_from_qcolor(qcolor) #-------------------------------------------------------------------------- # ProxyColorDialog API #-------------------------------------------------------------------------- @staticmethod def custom_count(): """ Get the number of available custom colors. """ return QColorDialog.customCount() @staticmethod def custom_color(index): """ Get the custom color for the given index. """ qrgb = QColorDialog.customColor(index) return color_from_qcolor(QColor.fromRgba(qrgb)) @staticmethod def set_custom_color(index, color): """ Set the custom color for the given index. """ QColorDialog.setCustomColor(index, color.argb) def set_current_color(self, color): """ Set the current color for the underlying widget. """ if not self._guard & CURRENT_GUARD: if color is not None: qcolor = QColor.fromRgba(color.argb) else: qcolor = QColor() self.widget.setCurrentColor(qcolor) def set_show_alpha(self, show): """ Set the show alpha option on the underlying widget. """ widget = self.widget opt = widget.options() if show: opt |= QColorDialog.ShowAlphaChannel else: opt &= ~QColorDialog.ShowAlphaChannel widget.setOptions(opt) def set_show_buttons(self, show): """ Set the show buttons option on the underlying widget. """ widget = self.widget opt = widget.options() if show: opt &= ~QColorDialog.NoButtons else: opt |= QColorDialog.NoButtons widget.setOptions(opt)
class WxMenuBar(WxToolkitObject, ProxyMenuBar): """ A Wx implementation of an Enaml ProxyMenuBar. """ #: A reference to the widget created by the proxy. widget = Typed(wxMenuBar) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying menu bar widget. """ # Wx behaves better when creating the menu bar without a parent. self.widget = wxMenuBar() def init_layout(self): """ Initialize the layout for the menu bar. """ super(WxMenuBar, self).init_layout() widget = self.widget for child in self.children(): if isinstance(child, WxMenu): widget.AddMenu(child.widget) def destroy(self): """ A reimplemented destructor. This destructor simply drops the reference to the menu bar and the enaml declaration and clears the menus in the menu bar. Destroying it will cause wx to segfault. """ if self.widget: self.widget.SetMenus([]) del self.widget del self.declaration #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def find_next_menu(self, child): """ Get the wxMenu instance which follows the child. Parameters ---------- child : WxMenu The child menu of interest. Returns ------- result : wxMenu or None The wxMenu which comes immediately after the actions of the given child, or None if no actions follow the child. """ found = False for dchild in self.children(): if found: if isinstance(dchild, WxMenu): return dchild.widget else: found = dchild is child def child_added(self, child): """ Handle the child added event for a WxMenuBar. """ super(WxMenuBar, self).child_added(child) if isinstance(child, WxMenu): before = self.find_next_menu(child) self.widget.InsertMenu(before, child.widget) def child_removed(self, child): """ Handle the child removed event for a WxMenuBar. """ super(WxMenuBar, self).child_removed(child) if isinstance(child, WxMenu): self.widget.RemoveMenu(child.widget)
class QtDockArea(QtConstraintsWidget, ProxyDockArea): """ A Qt implementation of an Enaml DockArea. """ #: A reference to the widget created by the proxy. widget = Typed(QDockArea) #: The docking manager which will drive the dock area. manager = Typed(DockManager) #: The event filter which listens for layout requests. dock_layout_filter = Typed(DockLayoutFilter) #: The event filter which listens for dock events. dock_event_filter = Typed(DockEventFilter) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying QDockArea widget. """ self.widget = QDockArea(self.parent_widget()) self.manager = DockManager(self.widget) self.dock_event_filter = DockEventFilter(self) self.dock_layout_filter = DockLayoutFilter(self) def init_widget(self): """ Initialize the underlying widget. """ super(QtDockArea, self).init_widget() d = self.declaration self.set_tab_position(d.tab_position) self.set_live_drag(d.live_drag) if d.style: # TODO remove this in Enaml 1.0 self.set_style(d.style) self.set_dock_events_enabled(d.dock_events_enabled) def init_layout(self): """ Initialize the layout of the underlying control. """ super(QtDockArea, self).init_layout() manager = self.manager for item in self.dock_items(): manager.add_item(item) d = self.declaration self.apply_layout(d.layout) self.widget.installEventFilter(self.dock_layout_filter) def destroy(self): """ A reimplemented destructor. This removes the event filter from the dock area and releases the items from the dock manager. """ self.widget.removeEventFilter(self.dock_layout_filter) self.widget.removeEventFilter(self.dock_event_filter) del self.dock_layout_filter del self.dock_event_filter self.manager.destroy() super(QtDockArea, self).destroy() #-------------------------------------------------------------------------- # Overrides #-------------------------------------------------------------------------- def refresh_style_sheet(self): """ A reimplemented styling method. The dock area uses custom stylesheet processing. """ # workaround win-7 sizing bug parts = [u'QDockTabWidget::pane {}'] name = self.widget.objectName() for style in StyleCache.styles(self.declaration): t = translate_dock_area_style(name, style) if t: parts.append(t) if len(parts) > 1: stylesheet = u'\n\n'.join(parts) else: stylesheet = u'' self.widget.setStyleSheet(stylesheet) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def dock_items(self): """ Get an iterable of QDockItem children for this area. """ for d in self.declaration.dock_items(): w = d.proxy.widget if w is not None: yield w #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def child_added(self, child): """ Handle the child added event for a QtDockArea. """ super(QtDockArea, self).child_added(child) if isinstance(child, QtDockItem): w = child.widget if w is not None: self.manager.add_item(w) def child_removed(self, child): """ Handle the child removed event for a QtDockArea. """ super(QtDockArea, self).child_removed(child) if isinstance(child, QtDockItem): w = child.widget if w is not None: self.manager.remove_item(w) #-------------------------------------------------------------------------- # ProxyDockArea API #-------------------------------------------------------------------------- def set_tab_position(self, position): """ Set the default tab position on the underyling widget. """ self.widget.setTabPosition(TAB_POSITIONS[position]) def set_live_drag(self, live_drag): """ Set the live drag state for the underlying widget. """ self.widget.setOpaqueItemResize(live_drag) def set_style(self, style): """ Set the style for the underlying widget. """ # If get_style_sheet returns something, it means the user will # have already called register_style_sheet, which will raise # a deprecation warning. TODO remove this method in Enaml 1.0. sheet = get_style_sheet(style) if sheet: self.widget.setStyleSheet(sheet) def set_dock_events_enabled(self, enabled): """ Set whether or not dock events are enabled for the area. """ widget = self.widget widget.setDockEventsEnabled(enabled) if enabled: widget.installEventFilter(self.dock_event_filter) else: widget.removeEventFilter(self.dock_event_filter) def save_layout(self): """ Save the current layout on the underlying widget. """ return self.manager.save_layout() def apply_layout(self, layout): """ Apply a new layout to the underlying widget. """ self.manager.apply_layout(layout) def update_layout(self, ops): """ Update the layout from a list of layout operations. """ self.manager.update_layout(ops)
class IconManagerPlugin(HasPreferencesPlugin): """Plugin managing icon theme and access to icon for the application. """ #: Id of the currently selected icon theme current_theme = Str('exopy.FontAwesome').tag(pref=True) #: Id of the icon theme to use as fallback if a theme fail to provide an #: icon. fallback_theme = Str('exopy.FontAwesome').tag(pref=True) #: Registered icon themes ids icon_themes = List() def start(self): """Start the plugin lifecycle and collect themes and extensions. """ super(IconManagerPlugin, self).start() checker = make_extension_validator(IconTheme, (), ()) self._icon_themes = ExtensionsCollector(workbench=self.workbench, point=ICON_THEME_POINT, ext_class=IconTheme, validate_ext=checker) self._icon_themes.start() self._list_icon_themes() if self.current_theme not in self.icon_themes: self.current_theme = self.icon_themes[0] if self.fallback_theme not in self.icon_themes: self.fallback_theme = 'exopy.FontAwesome' checker = make_extension_validator(IconThemeExtension, (), ('theme', )) self._icon_theme_extensions = \ ExtensionsCollector(workbench=self.workbench, point=ICON_THEME_EXTENSION_POINT, ext_class=IconThemeExtension, validate_ext=checker) self._icon_theme_extensions.start() self._add_extensions_to_selected_theme() self._bind_observers() def stop(self): """Stop the plugin and clean up. """ self._unbind_observers() self._icon_theme_extensions.stop() self._icon_themes.stop() def get_icon(self, icon_id): """Get an icon from the selected theme. Fallback to fallback_theme if no matching icon is found in the selected theme. """ icon_theme = self._icon_themes.contributions[self.current_theme] icon = None msg = '' try: icon = icon_theme.get_icon(self, icon_id) except Exception: msg = 'Icon theme %s failed to provide icon %s and raised:\n%s' msg = msg % (self.current_theme, icon_id, format_exc()) else: if icon is None: msg = 'Icon theme %s failed to provide icon %s without errors.' msg = msg % (self.current_theme, icon_id) if msg: fallback = self._icon_themes.contributions[self.fallback_theme] try: icon = fallback.get_icon(self, icon_id) except Exception: msg += ('Fallback theme %s failed to provide icon %s and ' 'raised:\n%s') msg = msg % (self.fallback_theme, icon_id, format_exc()) else: if icon is None: msg += ('Fallback theme %s failed to provide icon %s ' 'without errors.') msg = msg % (self.fallback_theme, icon_id) logger = logging.getLogger(__name__) logger.warning(msg) return icon # --- Private API --------------------------------------------------------- #: Collector for the declared icon themes. _icon_themes = Typed(ExtensionsCollector) #: Collector for the declared icon theme extensions. _icon_theme_extensions = Typed(ExtensionsCollector) #: Currently selected theme. _current_theme = Typed(IconTheme) def _add_extensions_to_selected_theme(self, change=None): """Add contributed theme extension to the selected theme. """ selected = self._current_theme # Assign all contributed icons from all extensions. if change is None: for k, v in self._icon_theme_extensions.contributions.items(): if v.theme == selected.id: selected.insert_children(None, v.icons()) # Only update icons provided by new extensions. else: added = set(change['value']) - set(change.get('oldvalue', {})) removed = set(change.get('oldvalue', {})) - set(change['value']) ext = dict(change['value']) ext.update(change.get('oldvalue', {})) for k in added: v = ext[k] if v.theme == selected.id: selected.insert_children(None, v.icons()) for k in removed: v = ext[k] if v.theme == selected.id: v.insert_children(None, v.icons()) del selected._icons def _post_setattr_current_theme(self, old, new): """Add the extension icons to the theme. """ del self._current_theme if self._icon_theme_extensions: self._add_extensions_to_selected_theme() def _list_icon_themes(self, change=None): """List the declared icon themes. """ self.icon_themes = sorted(self._icon_themes.contributions) def _bind_observers(self): """Setup the observers on the contributions. """ self._icon_themes.observe('contributions', self._list_icon_themes) callback = self._add_extensions_to_selected_theme self._icon_theme_extensions.observe('contributions', callback) def _unbind_observers(self): """Remove the observers on the contributions. """ self._icon_themes.unobserve('contributions', self._list_icon_themes) callback = self._add_extensions_to_selected_theme self._icon_theme_extensions.unobserve('contributions', callback) def _default__current_theme(self): """Get the current theme object based on the current_theme member. """ return self._icon_themes.contributions[self.current_theme]
class OccShape(ProxyShape): #: A reference to the toolkit shape created by the proxy. shape = Typed(TopoDS_Shape) #: The shape that was shown on the screen ais_shape = Instance(AIS_Shape) #: Topology explorer of the shape topology = Typed(Topology) #: Class reference url reference = Str() # ------------------------------------------------------------------------- # Initialization API # ------------------------------------------------------------------------- def create_shape(self): """ Create the toolkit shape for the proxy object. This method is called during the top-down pass, just before the 'init_shape()' method is called. This method should create the toolkit widget and assign it to the 'widget' attribute. """ raise NotImplementedError def init_shape(self): """ Initialize the state of the toolkit widget. This method is called during the top-down pass, just after the 'create_widget()' method is called. This method should init the state of the widget. The child widgets will not yet be created. """ pass def init_layout(self): """ Initialize the layout of the toolkit shape. """ pass def activate_top_down(self): """ Activate the proxy for the top-down pass. """ #log.debug(f"{self}.create_shape()") self.create_shape() #log.debug(f"{self}.init_shape()") self.init_shape() def activate_bottom_up(self): """ Activate the proxy tree for the bottom-up pass. """ #log.debug(f"{self}.init_layout()") self.init_layout() # ------------------------------------------------------------------------- # Defaults and Observers # ------------------------------------------------------------------------- def _default_topology(self): if self.shape is None: self.create_shape() return Topology(shape=self.shape) @observe('shape') def update_topology(self, change): if self.shape is not None: self.topology = self._default_topology() #@observe('shape') #def update_display(self, change): # parent = self.parent() # if parent: # parent.update_display(change) def get_first_child(self): """ Return shape to apply the operation to. """ for child in self.children(): if isinstance(child, OccShape): return child def child_shapes(self): """ Iterator of all child shapes """ for child in self.children(): if isinstance(child, OccShape): if hasattr(child, 'shapes'): for s in child.shapes: yield s else: yield child.shape # ------------------------------------------------------------------------- # Proxy API # ------------------------------------------------------------------------- def get_transform(self): """ Create a transform which rotates the default axis to align with the normal given by the position Returns ------- transform: gp_Trsf """ d = self.declaration # Move to position and align along direction axis t = gp_Trsf() if d.direction.is_parallel(DZ): t.SetRotation(AZ, d.direction.angle(DZ) + d.rotation) else: d1 = d.direction.cross(DZ) axis = gp_Ax1(gp_Pnt(0, 0, 0), d1.proxy) t.SetRotation(axis, d.direction.angle(DZ)) # Apply the rotation an reverse any rotation added in sign = 1 if d1.y >= 0 else -1 angle = d.rotation + sign * d1.angle(DX) if angle: rot = gp_Trsf() rot.SetRotation(AZ, angle) t.Multiply(rot) t.SetTranslationPart(gp_Vec(*d.position)) return t def set_direction(self, direction): self.create_shape() def set_axis(self, axis): self.create_shape() def parent_shape(self): return self.parent().shape def get_bounding_box(self, shape=None): shape = shape or self.shape if not shape: return BBox() bbox = Bnd_Box() BRepBndLib.Add_(shape, bbox) pmin, pmax = bbox.CornerMin(), bbox.CornerMax() return BBox(*(pmin.X(), pmin.Y(), pmin.Z(), pmax.X(), pmax.Y(), pmax.Z()))
class QtToolkitDialog(QtToolkitObject, ProxyToolkitDialog): """ A Qt implementation of an Enaml ProxyToolkitDialog. """ #: A reference to the widget created by the proxy. widget = Typed(QDialog) def create_widget(self): """ Create the underlying QColorDialog. """ self.widget = QDialog(self.parent_widget()) def init_widget(self): """ Initialize the underlying widget. """ super(QtToolkitDialog, self).init_widget() self.set_title(self.declaration.title) self.widget.finished.connect(self.on_finished) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def get_default_title(self): """ Get the default window title for the dialog. This can be reimplemented by subclass to provide a default window title. The base implementation returns an empty string. """ return u'' #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def on_finished(self, result): """ Handle the 'finished' signal from the widget. """ d = self.declaration if d is not None: d._proxy_finished(bool(result)) #-------------------------------------------------------------------------- # ProxyToolkitDialog API #-------------------------------------------------------------------------- def set_title(self, title): """ Set the window title for the underlying widget. """ self.widget.setWindowTitle(title or self.get_default_title()) def show(self): """ Open the dialog as non modal. """ self.widget.show() def open(self): """ Open the dialog as window modal. """ self.widget.open() def exec_(self): """ Open the dialog as application modal. """ self.widget.exec_() def accept(self): """ Accept the current state and close the dialog. """ self.widget.accept() def reject(self): """ Reject the current state and close the dialog. """ self.widget.reject()
class QtActionGroup(QtToolkitObject, ProxyActionGroup): """ A Qt implementation of an Enaml ProxyActionGroup. """ #: A reference to the widget created by the proxy. widget = Typed(QCustomActionGroup) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying action group widget. """ self.widget = QCustomActionGroup(self.parent_widget()) def init_widget(self): """ Initialize the control. """ super(QtActionGroup, self).init_widget() d = self.declaration self.set_exclusive(d.exclusive) self.set_enabled(d.enabled) self.set_visible(d.visible) def init_layout(self): """ Initialize the layout for the control. """ super(QtActionGroup, self).init_layout() widget = self.widget for action in self.actions(): widget.addAction(action) #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def find_next_action(self, child): """ Locate the QAction object which logically follows the child. If the given child is last in the list of children, then the parent object will be invoked to find the QAction which follows this action group. Parameters ---------- child : QtToolkitObject The child object of interest. Returns ------- result : QAction or None The QAction which logically follows the position of the child in the list of children. None will be returned if a relevant QAction is not found. """ found = False for dchild in self.children(): if found and isinstance(dchild, QtAction): return dchild.widget else: found = child is dchild parent = self.parent() if parent is not None: return parent.find_next_action(self) def child_added(self, child): """ Handle the child added event for a QtActionGroup. This handler will also add the widget to the parent widget, since a QActionGroup only serves as a management container. """ super(QtActionGroup, self).child_added(child) if isinstance(child, QtAction): self.widget.addAction(child.widget) parent = self.parent() if parent is not None: before = self.find_next_action(child) parent.widget.insertAction(before, child.widget) def child_removed(self, child): """ Handle the child removed event for a QtActionGroup. This handler will also remove the widget to the parent widget, since a QActionGroup only serves as a management container. """ super(QtActionGroup, self).child_removed(child) if isinstance(child, QtAction) and child.widget is not None: self.widget.removeAction(child.widget) parent = self.parent() if parent is not None: parent.widget.removeAction(child.widget) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def actions(self): """ Get the QAction children for this action group. Returns ------- result : list The list of QAction instances which are children of this action group. Unlike the list returned by the `actions` method of the QActionGroup, the children in this list will have the correct order. """ isinst = isinstance return [c.widget for c in self.children() if isinst(c, QtAction)] #-------------------------------------------------------------------------- # ProxyActionGroup API #-------------------------------------------------------------------------- def set_exclusive(self, exclusive): """ Set the exclusive state of the underlying control. """ self.widget.setExclusive(exclusive) def set_enabled(self, enabled): """ Set the enabled state of the underlying control. """ self.widget.setEnabled(enabled) def set_visible(self, visible): """ Set the visible state of the underlying control. """ self.widget.setVisible(visible)
class AndroidMapView(AndroidFrameLayout, ProxyMapView): """ An Android implementation of an Enaml ProxyMapView. """ #: Holder widget = Typed(FrameLayout) #: A reference to the widget created by the proxy. fragment = Typed(MapFragment) #: Map options options = Typed(GoogleMapOptions) #: Map instance map = Typed(GoogleMap) #: TODO: Lookup table for markers markers = Dict() #: Camera updating _update_blocked = Bool() #: Info window adapter adapter = Typed(GoogleMap.InfoWindowAdapter) # ------------------------------------------------------------------------- # Initialization API # ------------------------------------------------------------------------- def create_widget(self): """ Create the underlying widget. """ self.init_options() #: Retrieve the actual map MapFragment.newInstance(self.options).then( self.on_map_fragment_created) # Holder for the fragment self.widget = FrameLayout(self.get_context()) # I wrote this a few days ago and already forget how this hack works... # lol We can't simply get a map reference using getMapAsync in the # return value like we normally do with a normal call function return # value. The bridge design was modified to store an object that cannot # be decoded normally (via a standard Bridge.Packer) by saving the new # object in the cache returning the id of the handler or proxy that # invoked it. This way we can manually create a new id and pass that # "future reference-able" object as our listener. At which point the # bridge will create a reference entry in the cache for us with the of # the object we gave it. Once in the cache we can use it like any # bridge object we created. self.map = GoogleMap(__id__=bridge.generate_id()) def init_options(self): """ Initialize the underlying map options. """ self.options = GoogleMapOptions() d = self.declaration self.set_map_type(d.map_type) if d.ambient_mode: self.set_ambient_mode(d.ambient_mode) if (d.camera_position or d.camera_zoom or d.camera_tilt or d.camera_bearing): self.update_camera() if d.map_bounds: self.set_map_bounds(d.map_bounds) if not d.show_compass: self.set_show_compass(d.show_compass) if not d.show_zoom_controls: self.set_show_zoom_controls(d.show_zoom_controls) if not d.show_toolbar: self.set_show_toolbar(d.show_toolbar) if d.lite_mode: self.set_lite_mode(d.lite_mode) if not d.rotate_gestures: self.set_rotate_gestures(d.rotate_gestures) if not d.scroll_gestures: self.set_scroll_gestures(d.scroll_gestures) if not d.tilt_gestures: self.set_tilt_gestures(d.tilt_gestures) if not d.zoom_gestures: self.set_zoom_gestures(d.zoom_gestures) if d.min_zoom: self.set_min_zoom(d.min_zoom) if d.max_zoom: self.set_max_zoom(d.max_zoom) def init_map(self): """ Add markers, polys, callouts, etc..""" d = self.declaration if d.show_location: self.set_show_location(d.show_location) if d.show_traffic: self.set_show_traffic(d.show_traffic) if d.show_indoors: self.set_show_indoors(d.show_indoors) if d.show_buildings: self.set_show_buildings(d.show_buildings) #: Local ref access is faster mapview = self.map mid = mapview.getId() #: Connect signals #: Camera mapview.onCameraChange.connect(self.on_camera_changed) mapview.onCameraMoveStarted.connect(self.on_camera_move_started) mapview.onCameraMoveCanceled.connect(self.on_camera_move_stopped) mapview.onCameraIdle.connect(self.on_camera_move_stopped) mapview.setOnCameraChangeListener(mid) mapview.setOnCameraMoveStartedListener(mid) mapview.setOnCameraMoveCanceledListener(mid) mapview.setOnCameraIdleListener(mid) #: Clicks mapview.onMapClick.connect(self.on_map_clicked) mapview.setOnMapClickListener(mid) mapview.onMapLongClick.connect(self.on_map_long_clicked) mapview.setOnMapLongClickListener(mid) #: Markers mapview.onMarkerClick.connect(self.on_marker_clicked) mapview.setOnMarkerClickListener(self.map.getId()) mapview.onMarkerDragStart.connect(self.on_marker_drag_start) mapview.onMarkerDrag.connect(self.on_marker_drag) mapview.onMarkerDragEnd.connect(self.on_marker_drag_end) mapview.setOnMarkerDragListener(mid) #: Info window mapview.onInfoWindowClick.connect(self.on_info_window_clicked) mapview.onInfoWindowLongClick.connect(self.on_info_window_long_clicked) mapview.onInfoWindowClose.connect(self.on_info_window_closed) mapview.setOnInfoWindowClickListener(mid) mapview.setOnInfoWindowCloseListener(mid) mapview.setOnInfoWindowLongClickListener(mid) #: Polys mapview.onPolygonClick.connect(self.on_poly_clicked) mapview.onPolylineClick.connect(self.on_poly_clicked) mapview.setOnPolygonClickListener(mid) mapview.setOnPolylineClickListener(mid) #: Circle mapview.onCircleClick.connect(self.on_circle_clicked) mapview.setOnCircleClickListener(mid) def init_info_window_adapter(self): """ Initialize the info window adapter. Should only be done if one of the markers defines a custom view. """ adapter = self.adapter if adapter: return #: Already initialized adapter = GoogleMap.InfoWindowAdapter() adapter.getInfoContents.connect(self.on_info_window_contents_requested) adapter.getInfoWindow.connect(self.on_info_window_requested) self.map.setInfoWindowAdapter(adapter) # ------------------------------------------------------------------------- # Google Maps API # ------------------------------------------------------------------------- def on_map_fragment_created(self, obj_id): """ Create the fragment and pull the map reference when it's loaded. """ self.fragment = MapFragment(__id__=obj_id) #: Setup callback so we know when the map is ready self.map.onMapReady.connect(self.on_map_ready) self.fragment.getMapAsync(self.map.getId()) context = self.get_context() def on_transaction(id): trans = FragmentTransaction(__id__=id) trans.add(self.widget.getId(), self.fragment) trans.commit() def on_fragment_manager(id): fm = FragmentManager(__id__=id) fm.beginTransaction().then(on_transaction) context.widget.getSupportFragmentManager().then(on_fragment_manager) # #: Get GoogleMap instance when ready # #: Doesn't work... # def get_map(result): # print("Maps initializer result: {}".format(result)) # if result==ConnectionResult.SUCCESS: # self.fragment.onMapReady.connect(self.on_map_ready) # self.fragment.getMapAsync(self.fragment.getId()) # else: # app = self.get_context() # app.show_error("Error getting map: {}".format(result)) # MapsInitializer.initialize(self.get_context()).then(get_map) def on_map_ready(self, map_id): #: At this point the map is valid self.init_map() #: Reload markers for child in self.children(): if isinstance(child, AndroidMapItemBase): child.add_to_map(self.map) def child_added(self, child): if isinstance(child, AndroidMapItemBase): child.add_to_map(self.map) else: super(AndroidMapView, self).child_added(child) def child_removed(self, child): if isinstance(child, AndroidMapItemBase): pass #: It removes itself else: super(AndroidMapView, self).child_removed(child) def on_map_clicked(self, pos): """ Called when the map is clicked """ d = self.declaration d.clicked({'click': 'short', 'position': tuple(pos)}) def on_map_long_clicked(self, pos): """ Called when the map is clicked """ d = self.declaration d.clicked({'click': 'long', 'position': tuple(pos)}) # ------------------------------------------------------------------------- # Camera API # ------------------------------------------------------------------------- def on_camera_move_started(self, reason): d = self.declaration if reason == GoogleMap.CAMERA_REASON_GESTURE: d.dragging = True else: d.animating = True def on_camera_move_stopped(self): d = self.declaration d.dragging = False d.animating = False def on_camera_changed(self, camera): pos, zoom, tilt, bearing = camera d = self.declaration #: Don't update self._update_blocked = True try: d.camera_position = tuple(pos) d.camera_zoom = zoom d.camera_tilt = tilt d.camera_bearing = bearing finally: self._update_blocked = False # ------------------------------------------------------------------------- # Marker API # ------------------------------------------------------------------------- def on_marker_clicked(self, marker): mid, pos = marker m = self.markers.get(mid) if m: return m.on_click() return False def on_marker_drag(self, marker): mid, pos = marker m = self.markers.get(mid) if m: m.on_drag(pos) def on_marker_drag_start(self, marker): mid, pos = marker m = self.markers.get(mid) if m: m.on_drag_start(pos) def on_marker_drag_end(self, marker): mid, pos = marker m = self.markers.get(mid) if m: m.on_drag_end(pos) # ------------------------------------------------------------------------- # Info window API # ------------------------------------------------------------------------- def on_info_window_requested(self, marker): mid, pos = marker m = self.markers.get(mid) if m: return m.on_info_window_requested() def on_info_window_contents_requested(self, marker): mid, pos = marker m = self.markers.get(mid) if m: return m.on_info_window_contents_requested() def on_info_window_clicked(self, marker): mid, pos = marker m = self.markers.get(mid) if m: m.on_info_window_clicked('short') def on_info_window_long_clicked(self, marker): mid, pos = marker m = self.markers.get(mid) if m: m.on_info_window_clicked('long') def on_info_window_closed(self, marker): mid, pos = marker m = self.markers.get(mid) #: This can come later when it's removed so check the declaration if m and m.declaration: m.on_info_window_closed() # ------------------------------------------------------------------------- # Polygon and PolyLine API # ------------------------------------------------------------------------- def on_poly_clicked(self, poly): m = self.markers.get(poly) if m: m.on_click() # ------------------------------------------------------------------------- # Circle API # ------------------------------------------------------------------------- def on_circle_clicked(self, circle): m = self.markers.get(circle) if m: m.on_click() # ------------------------------------------------------------------------- # ProxyMapView API # ------------------------------------------------------------------------- def set_map_bounds(self, bounds): raise NotImplementedError def set_map_type(self, map_type): if self.map: self.map.setMapType(GoogleMap.MAP_TYPES[map_type]) else: self.options.mapType(GoogleMap.MAP_TYPES[map_type]) def set_show_toolbar(self, show): if self.map: pass else: self.options.mapToolbarEnabled(show) def set_show_compass(self, show): if self.map: pass else: self.options.compassEnabled(show) def set_show_zoom_controls(self, show): if self.map: pass else: self.options.zoomControlsEnabled(show) def set_show_location(self, show): if self.map: if show: def on_result(allowed): if allowed: self.map.setMyLocationEnabled(True) else: self.declaration.show_location = False LocationManager.check_permission().then(on_result) else: self.map.setMyLocationEnabled(False) def set_show_buildings(self, show): if self.map: self.map.setBuildingsEnabled(show) def set_show_traffic(self, show): if self.map: self.map.setTrafficEnabled(show) def set_show_indoors(self, show): if self.map: self.map.setBuildingsEnabled(show) def update_camera(self): if self._update_blocked: return d = self.declaration if self.map: # Bit of a hack but it "should" work hahah # The future created to handle returned values creates an id for # itself. The bridge will save objects created (if they cannot be # packed by a specific Packer) using that ID, hence we can # reference it right away without actually waiting # until we get a return value back across the bridge. self.map.animateCamera( CameraUpdateFactory.newCameraPosition( CameraPosition(LatLng(*d.camera_position), d.camera_zoom, d.camera_tilt, d.camera_bearing))) else: self.options.camera( CameraPosition(LatLng(*d.camera_position), d.camera_zoom, d.camera_tilt, d.camera_bearing)) def set_camera_zoom(self, zoom): self.update_camera() def set_camera_position(self, position): self.update_camera() def set_camera_bearing(self, bearing): self.update_camera() def set_camera_tilt(self, tilt): self.update_camera() def set_ambient_mode(self, enabled): if self.map: pass else: self.options.ambientEnabled(enabled) def set_lite_mode(self, enabled): if self.map: pass else: self.options.liteMode(enabled) def set_min_zoom(self, zoom): if self.map: self.map.setMinZoomPreference(zoom) else: self.options.minZoomPreference(zoom) def set_max_zoom(self, zoom): if self.map: self.map.setMaxZoomPreference(zoom) else: self.options.maxZoomPreference(zoom) def set_rotate_gestures(self, enabled): if self.map: pass else: self.options.rotateGesturesEnabled(enabled) def set_scroll_gestures(self, enabled): if self.map: pass else: self.options.scrollGesturesEnabled(enabled) def set_tilt_gestures(self, enabled): if self.map: pass else: self.options.tiltGesturesEnabled(enabled) def set_zoom_gestures(self, enabled): if self.map: pass else: self.options.zoomGesturesEnabled(enabled)
class AbsoluteOrientationTriggeredCalibrationController( LiveCalibrationController): is_ready = Bool(False) result_count = Int(128) errors_translation = Typed(np.ndarray) errors_rotation = Typed(np.ndarray) max_error_translation = Float(0.0) max_error_rotation = Float(0.0) initial_error_translation = Float(-1) initial_error_rotation = Float(-1) last_result = Value(None) results_txt = Value() progress_bar = Value() def setupController(self, active_widgets=None): super(AbsoluteOrientationTriggeredCalibrationController, self).setupController(active_widgets=active_widgets) if active_widgets is not None: w = active_widgets[0] self.results_txt = w.find('results_txt') self.progress_bar = w.find('progress_bar') if self.autocomplete_maxerror_str != "": translation, rotation = [ s.strip() for s in self.autocomplete_maxerror_str.split(",") ] self.max_error_translation = float(translation) self.max_error_rotation = float(rotation) # needs to match the SRG !! self.sync_source = 'calib_absolute_orientation' self.required_sinks = [ 'calib_absolute_orientation', ] # setup a errors buffer self.errors_translation = np.array([np.nan] * self.result_count, dtype=np.double) self.errors_rotation = np.array([np.nan] * self.result_count, dtype=np.double) if self.facade is not None: self.facade.observe("is_loaded", self.connector_setup) def connector_setup(self, change): if change['value'] and self.verify_connector(): self.connector.setup(self.facade.instance) self.connector.observe(self.sync_source, self.handle_data) self.is_ready = True def handle_data(self, c): if self.connector.calib_absolute_orientation is not None: ao = self.connector.calib_absolute_orientation.get() self.results_txt.text = "Result:\n%s" % str(ao) if self.last_result is not None: t_error = norm(ao.translation() - self.last_result.translation()) self.errors_translation[0] = t_error # implement simple ringbuffer self.errors_translation = np.roll(self.errors_translation, 1) if self.initial_error_translation == -1: self.initial_error_translation = t_error r_error = abs( math.Quaternion(ao.rotation().inverted() * self.last_result.rotation()).angle()) self.errors_rotation[0] = r_error # implement simple ringbuffer self.errors_rotation = np.roll(self.errors_rotation, 1) if self.initial_error_rotation == -1: self.initial_error_rotation = r_error self.last_result = ao # update progress bar if self.initial_error_translation != -1 and self.initial_error_translation != -1: t_p = t_error / (self.initial_error_translation - self.max_error_translation) r_p = r_error / (self.initial_error_rotation - self.max_error_rotation) pv = int(np.sqrt(1 - max(0, min(max(t_p, r_p), 1))) * 100) if pv > self.progress_bar.value: self.progress_bar.value = pv # check if the minimum of self.result_count results have been received if not np.isnan(np.sum(self.errors_translation)) and not np.isnan( np.sum(self.errors_rotation)): if np.all(self.errors_translation < self.max_error_translation) and \ np.all(self.errors_rotation < self.max_error_rotation): log.info( "Absolute Orientation: Results are satisfactory for translation (<%s) min: %s max: %s and rotation (<%s) min: %s max %s" % (self.max_error_translation, np.min(self.errors_translation), np.max(self.errors_translation), self.max_error_rotation, np.min(self.errors_rotation), np.max(self.errors_rotation))) self.result_ok = True self.progress_bar.value = 100 if self.autocomplete_enable: self.stopCalibration() def handle_keypress(self, key): if not self.is_ready: return if key == 32: self.capturePoseAO() def capturePoseAO(self): if self.connector is not None: # use space a default trigger log.info("Capture Pose Absolute_orientation") self.connector.capture_pose(" ")
class TaskManagerPlugin(HasPrefPlugin): """Plugin responsible for collecting and providing tasks. """ #: Folders containings templates which should be loaded. templates_folders = List(default=[TEMPLATE_PATH]).tag(pref=True) #: Known templates (store full path to .ini). #: This should not be manipulated by user code. templates = Dict() #: List of the filters. filters = List() #: Path to the file in which the names for the tasks are located. auto_task_path = Unicode(os.path.join(FOLDER_PATH, 'tasknames.txt')).tag(pref=True) #: List of names to use when creating a new task. auto_task_names = List() def start(self): """Collect all declared tasks and start observers. """ super(TaskManagerPlugin, self).start() core = self.workbench.get_plugin('enaml.workbench.core') core.invoke_command('ecpy.app.errors.enter_error_gathering') if not os.path.isdir(TEMPLATE_PATH): try: os.mkdir(TEMPLATE_PATH) except Exception: if TEMPLATE_PATH in self.templates_folders: self.templates_folders.remove(TEMPLATE_PATH) core = self.workbench.get_plugin('enaml.workbench.core') msg = 'Failed to create template folder.' # Python 2 windows issue try: msg += 'Traceback : %s' % format_exc() except UnicodeError: msg += 'Failed to format error message.' core.invoke_command('ecpy.app.errors.signal', dict(kind='error', message=msg)) self._filters = ExtensionsCollector(workbench=self.workbench, point=FILTERS_POINT, ext_class=TaskFilter) self._filters.start() self.filters = list(self._filters.contributions) self._configs = DeclaratorsCollector(workbench=self.workbench, point=CONFIG_POINT, ext_class=(TaskConfig, TaskConfigs)) self._configs.start() self._tasks = DeclaratorsCollector(workbench=self.workbench, point=TASK_EXT_POINT, ext_class=(Tasks, Task, Interfaces, Interface)) self._tasks.start() self._refresh_templates() if self.auto_task_path: self.load_auto_task_names() self._bind_observers() core.invoke_command('ecpy.app.errors.exit_error_gathering') def stop(self): """Discard collected tasks and remove observers. """ self._unbind_observers() self._tasks.stop() self.templates.clear() self._filters.stop() self._configs.stop() def list_tasks(self, filter='All'): """List the known tasks using the specified filter. Parameters ---------- filter : unicode, optional Name of the filter to use Returns ------- tasks : list(unicode) or None Task ids selected by the filter, or None if the filter does not exist. """ t_filter = self._filters.contributions.get(filter) if t_filter: return t_filter.filter_tasks(self._tasks.contributions, self.templates) def get_task_infos(self, task): """Access a given task infos. Parameters ---------- task : unicode Id of the task class for which to return the actual class. Returns ------- infos : TaskInfos or None Object containing all the infos about the requested task. This object should never be manipulated directly by user code. """ if task not in self._tasks.contributions: return None return self._tasks.contributions[task] def get_task(self, task, view=False): """Access a given task class. Parameters ---------- task : unicode Id of the task class for which to return the actual class. view : bool, optional Whether or not to return the view assoicated with the task. Returns ------- task_cls : type or None Class associated to the requested task or None if the task was not found. task_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_task_infos(task) if infos is None: answer = None if not view else (None, None) return answer return infos.cls if not view else (infos.cls, infos.view) def get_tasks(self, tasks): """Access an ensemble of task classes. Parameters ---------- tasks : list(unicode) Ids of the task classes for which to return the actual classes. Returns ------- tasks_cls : dict Dictionary mapping the requested tasks to the actual classes. missing : list List of classes that were not found. """ tasks_cls = {} missing = [] for t in tasks: res = self.get_task(t) if res: tasks_cls[t] = res else: missing.append(t) return tasks_cls, missing def get_interface_infos(self, interface): """Access a given interface infos. Parameters ---------- interface : tuple[unicode|tuple|list] - Name of the task class for which to return the actual class. - Name of the task to which this interface is linked and names of the intermediate interfaces if any (going from the most general ones to the more specialised ones). views : bool, optional Whether or not to return the views assoicated with the interface. Returns ------- infos : InterfaceInfos Object containing all the infos about the requested interface. this object should never be manipulated directly by user code. """ lookup_dict = self._tasks.contributions interface_cls_name, interface_anchor = interface if not isinstance(interface_anchor, (list, tuple)): interface_anchor = [interface_anchor] try: for anchor in interface_anchor: lookup_dict = lookup_dict[anchor].interfaces except KeyError: logger = logging.getLogger(__name__) msg = 'Looking for {} (anchor {}) failed to found {}' logger.debug( msg.format(interface_cls_name, interface_anchor, anchor)) return None if interface_cls_name in lookup_dict: return lookup_dict[interface_cls_name] else: return None def get_interface(self, interface, views=False): """Access a given interface class. Parameters ---------- interface: tuple[unicode|tuple|list] - Name of the task class for which to return the actual class. - Name of the task to which this interface is linked and names of the intermediate interfaces if any (going from the most general ones to the more specialised ones). views : bool, optional Whether or not to return the views assoicated with the interface. Returns ------- interface_cls : type or None Class corresponding to the requested interface or None if the class was not found. views : list or None, optional List of views associated with the interface. """ infos = self.get_interface_infos(interface) if infos is not None: return infos.cls if not views else (infos.cls, infos.views) else: return None if not views else (None, None) def get_interfaces(self, interfaces): """Access an ensemble of interface classes. Parameters ---------- interfaces : list[tuple[unicode|tuple|list]] List of pairs (name of the interface class, corrisponding anchor) for which to return the actual classes. Returns ------- interfaces_cls : dict Dictionary mapping the requested interfaces to the actual classes. missing : list List of classes that were not found. """ interfaces_cls = {} missing = [] for i in interfaces: i_cls = self.get_interface(i) if i_cls: interfaces_cls[i] = i_cls else: missing.append(i) return interfaces_cls, missing def get_config(self, task_id): """Access the proper config for a task. Parameters ---------- task : unicode Id of the task for which a config is required Returns ------- config : tuple Tuple containing the requested config object, and its visualisation. """ templates = self.templates if task_id in templates: infos = configs = self._configs.contributions['__template__'] config = infos.cls(manager=self, template_path=templates[task_id]) return config, infos.view(config=config) elif task_id in self._tasks.contributions: configs = self._configs.contributions # Look up the hierarchy of the selected task to get the appropriate # TaskConfig task_class = self._tasks.contributions[task_id].cls for t_class in type.mro(task_class): if t_class in configs: infos = configs[t_class] c = infos.cls(manager=self, task_class=task_class) return c, infos.view(config=c) return None, None def load_auto_task_names(self): """ Generate a list of task names from a file. """ path = self.auto_task_path if not os.path.isfile(path): core = self.workbench.get_plugin('enaml.workbench.core') msg = 'Path {} does not point to a real file.'.format(path) core.invoke_command('ecpy.app.errors.signal', dict(kind='error', message=msg)) return with open(path) as f: aux = f.readlines() self.auto_task_names = [l.rstrip() for l in aux] # ========================================================================= # --- Private API --------------------------------------------------------- # ========================================================================= #: Dictionary storing all known tasks declarartion, using TaskInfos. _tasks = Typed(DeclaratorsCollector) #: Private storage keeping track of which extension declared which object. _extensions = Typed(defaultdict, (list, )) #: Contributed task filters. _filters = Typed(ExtensionsCollector) #: Contributed task configs. _configs = Typed(DeclaratorsCollector) #: Watchdog observer tracking changes to the templates folders. _observer = Typed(Observer, ()) def _refresh_templates(self): """Refresh the list of template tasks. """ # TODO rework to handle in an nicer fashion same template in multiple # folders templates = {} for path in self.templates_folders: if os.path.isdir(path): filenames = sorted(f for f in os.listdir(path) if f.endswith('.task.ini') and ( os.path.isfile(os.path.join(path, f)))) for filename in filenames: template_path = os.path.join(path, filename) # Beware redundant names are overwrited name = filename[:-len('.task.ini')] templates[name] = template_path else: logger = logging.getLogger(__name__) logger.warn('{} is not a valid directory'.format(path)) self.templates = templates def _post_setattr_templates_folders(self, old, new): """Ensure that the template observer always watch the right folder. """ self._observer.unschedule_all() for folder in self.templates_folders: if os.path.isdir(folder): handler = SystematicFileUpdater(self._update_templates) self._observer.schedule(handler, folder, recursive=True) def _update_templates(self): """Simply refresh the templates task. """ self._refresh_templates() def _update_filters(self, change): """Update the available list of filters. """ self.filters = list(change['value'].keys()) def _bind_observers(self): """Setup all observers. """ for folder in self.templates_folders: handler = SystematicFileUpdater(self._update_templates) self._observer.schedule(handler, folder, recursive=True) self._observer.start() self._filters.observe('contributions', self._update_filters) def _unbind_observers(self): """Remove all observers. """ self._filters.unobserve('contributions', self._update_filters) self._observer.unschedule_all() self._observer.stop() try: self._observer.join() except RuntimeError: pass
class DockManager(Atom): """ A class which manages the docking behavior of a dock area. """ #: The handler which holds the primary dock area. _dock_area = Typed(QDockArea) #: The overlay used when hovering over a dock area. _overlay = Typed(DockOverlay, ()) #: The list of QDockFrame instances maintained by the manager. The #: QDockFrame class maintains this list in proper Z-order. _dock_frames = List() #: The set of QDockItem instances added to the manager. _dock_items = Typed(set, ()) #: The distance to use for snapping floating dock frames. _snap_dist = Int(factory=lambda: QApplication.startDragDistance() * 2) #: A proximity handler which manages proximal floating frames. _proximity_handler = Typed(ProximityHandler, ()) #: A container monitor which tracks toplevel container changes. _container_monitor = Typed(DockContainerMonitor) def _default__container_monitor(self): return DockContainerMonitor(self) def __init__(self, dock_area): """ Initialize a DockingManager. Parameters ---------- dock_area : QDockArea The primary dock area to be managed. Docking will be restricted to this area and to windows spawned by the area. """ assert dock_area is not None self._dock_area = dock_area self._overlay = DockOverlay(dock_area) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def dock_area(self): """ Get the dock area to which the manager is attached. Returns ------- result : QDockArea The dock area to which the manager is attached. """ return self._dock_area def add_item(self, item): """ Add a dock item to the dock manager. If the item has already been added, this is a no-op. Parameters ---------- items : QDockItem The item to be managed by this dock manager. It will be reparented to a dock container and made available to the the layout system. """ if item in self._dock_items: return self._dock_items.add(item) item._manager = self container = QDockContainer(self, self._dock_area) container.setDockItem(item) container.setObjectName(item.objectName()) monitor = self._container_monitor container.topLevelChanged.connect(monitor.onTopLevelChanged) self._dock_frames.append(container) def remove_item(self, item): """ Remove a dock item from the dock manager. If the item has not been added to the manager, this is a no-op. Parameters ---------- items : QDockItem The item to remove from the dock manager. It will be hidden and unparented, but not destroyed. """ if item not in self._dock_items: return item._manager = None for container in self.dock_containers(): if container.dockItem() is item: if not container.isWindow(): container.unplug() container.hide() self._free_container(container) break def save_layout(self): """ Get the current layout of the dock area. Returns ------- result : docklayout A docklayout instance which represents the current layout state. """ items = [self._dock_area] + self.floating_frames() return DockLayout(*map(LayoutSaver(), items)) def apply_layout(self, layout): """ Apply a layout to the dock area. Parameters ---------- layout : DockLayout The dock layout to apply to the managed area. """ available = (i.objectName() for i in self._dock_items) DockLayoutValidator(available)(layout) LayoutBuilder(self)(layout) def update_layout(self, ops): """ Update the layout for a list of layout operations. Parameters ---------- ops : list A list of LayoutOp objects to use for updating the layout. """ builder = LayoutBuilder(self) for op in ops: builder(op) def destroy(self): """ Destroy the dock manager. This method will free all of the resources held by the dock manager. The primary dock area and dock items will not be destroyed. After the method is called, the dock manager is invalid and should no longer be used. """ for frame in self._dock_frames: if isinstance(frame, QDockContainer): frame.setDockItem(None) frame.setParent(None, Qt.Widget) frame.hide() for frame in self._dock_frames: if isinstance(frame, QDockWindow): frame.setParent(None, Qt.Widget) frame.hide() for item in self._dock_items: item._manager = None self._dock_area.setCentralWidget(None) self._dock_area.setMaximizedWidget(None) del self._dock_area del self._dock_frames del self._dock_items del self._proximity_handler del self._container_monitor del self._overlay #-------------------------------------------------------------------------- # Framework API #-------------------------------------------------------------------------- def dock_containers(self): """ Get an iterable of QDockContainer instances. This method is called by the framework at the appropriate times and should not be called directly by user code. Returns ------- result : list A list of QDockContainer instances owned by this dock manager. """ f = lambda w: isinstance(w, QDockContainer) return filter(f, self._dock_frames) def dock_windows(self): """ Get an iterable of QDockWindow instances. This method is called by the framework at the appropriate times and should not be called directly by user code. Returns ------- result : list A list of QDockWindow instances owned by this dock manager. """ f = lambda w: isinstance(w, QDockWindow) return filter(f, self._dock_frames) def floating_frames(self): """ Get an iterable of floating dock frames. This method is called by the framework at the appropriate times and should not be called directly by user code. Returns ------- result : list A list toplevel QDockFrame instances. """ f = lambda w: w.isWindow() return filter(f, self._dock_frames) def add_window(self, window): """ Add a floating QDockWindow to the dock manager. This method is called by the framework at the appropriate times and should not be called directly by user code. Parameters ---------- window : QDockWindow A newly created dock window which should be tracked by the dock manager. """ self._dock_frames.append(window) self._proximity_handler.addFrame(window) def close_container(self, container, event): """ Handle a close request for a QDockContainer. This method is called by the framework at the appropriate times and should not be called directly by user code. Parameters ---------- window : QDockContainer The dock container to close. event : QCloseEvent The close event passed to the event handler. """ item = container.dockItem() if item is None or item.close(): if not container.isWindow(): container.unplug() self._free_container(container) else: event.ignore() def close_window(self, window, event): """ Handle a close request for a QDockWindow. This method is called by the framework at the appropriate times and should not be called directly by user code. Parameters ---------- window : QDockWindow The dock window to close. event : QCloseEvent The close event passed to the event handler. """ area = window.dockArea() if area is not None: containers = list(iter_containers(area)) geometries = {} for container in containers: pos = container.mapToGlobal(QPoint(0, 0)) size = container.size() geometries[container] = QRect(pos, size) for container, ignored in area.dockBarContainers(): containers.append(container) size = container.sizeHint() geometries[container] = QRect(window.pos(), size) for container in containers: if not container.close(): container.unplug() container.float() container.setGeometry(geometries[container]) container.show() self._free_window(window) def raise_frame(self, frame): """ Raise a frame to the top of the Z-order. This method is called by the framework at the appropriate times and should not be called directly by user code. Parameters ---------- frame : QDockFrame The frame to raise to the top of the Z-order. """ frames = self._dock_frames handler = self._proximity_handler if handler.hasLinkedFrames(frame): linked = set(handler.linkedFrames(frame)) ordered = [f for f in frames if f in linked] for other in ordered: frames.remove(other) frames.append(other) other.raise_() frame.raise_() frames.remove(frame) frames.append(frame) def frame_resized(self, frame): """ Handle the post-processing for a resized floating frame. This method is called by the framework at the appropriate times and should not be called directly by user code. Parameters ---------- frame : QDockFrame The frame which has been resized. """ # If the frame is linked, the resize may have changed the frame # geometry such that the existing links are no longer valid. # The links are refreshed and the link button state is updated. if frame.isLinked(): handler = self._proximity_handler handler.updateLinks(frame) if not handler.hasLinkedFrames(frame): frame.setLinked(False) def drag_move_frame(self, frame, target_pos, mouse_pos): """ Move the floating frame to the target position. This method is called by a floating frame in response to a user moving it by dragging on it's title bar. It takes into account neighboring windows and will snap the frame edge to another window if it comes close to the boundary. It also ensures that the guide overlays are shown at the proper position. This method should not be called by user code. Parameters ---------- frame : QDockFrame The floating QDockFrame which should be moved. target_pos : QPoint The global position which is the target of the move. mouse_pos : QPoint The global mouse position. """ # If the frame is linked, it and any of its linked frames are # moved the same amount with no snapping. An unlinked window # is free to move and will snap to any other floating window # that has an opposite edge lying within the snap distance. # The overlay is hidden when the frame has proximal frames # since such a frame is not allowed to be docked. show_drag_overlay = True handler = self._proximity_handler if frame.isLinked(): delta = target_pos - frame.pos() frame.move(target_pos) if handler.hasLinkedFrames(frame): show_drag_overlay = False for other in handler.linkedFrames(frame): other.move(other.pos() + delta) else: f_size = frame.frameGeometry().size() f_rect = QRect(target_pos, f_size) f_x = target_pos.x() f_y = target_pos.y() f_w = f_size.width() f_h = f_size.height() dist = self._snap_dist filt = lambda n: -dist < n < dist for other in handler.proximalFrames(f_rect, dist): if other is not frame: o_geo = other.frameGeometry() o_x = o_geo.left() o_y = o_geo.top() o_right = o_x + o_geo.width() o_bottom = o_y + o_geo.height() dx = filter(filt, ( o_x - f_x, o_x - (f_x + f_w), o_right - f_x, o_right - (f_x + f_w), )) if dx: f_x += min(dx) dy = filter(filt, ( o_y - f_y, o_y - (f_y + f_h), o_bottom - f_y, o_bottom - (f_y + f_h), )) if dy: f_y += min(dy) frame.move(f_x, f_y) if show_drag_overlay: self._update_drag_overlay(frame, mouse_pos) else: self._overlay.hide() def drag_release_frame(self, frame, pos): """ Handle the dock frame being released by the user. This method is called by the framework at the appropriate times and should not be called directly by user code. It will redock a floating dock item if it is released over a dock guide. Parameters ---------- frame : QDockFrame The dock frame being dragged by the user. pos : QPoint The global coordinates of the mouse position. """ # Docking is disallowed for frames which have linked proximal # frames, or if the target dock area has a maximized widget. # This prevents a situation where the docking logic would be # non-sensical and maintains a consistent user experience. overlay = self._overlay overlay.hide() guide = overlay.guide_at(pos) if guide == QGuideRose.Guide.NoGuide: return if self._proximity_handler.hasLinkedFrames(frame): return builder = LayoutBuilder(self) target = self._dock_target(frame, pos) if isinstance(target, QDockArea): if target.maximizedWidget() is not None: return with builder.drop_frame(frame): local = target.mapFromGlobal(pos) widget = layout_hit_test(target, local) plug_frame(target, widget, frame, guide) elif isinstance(target, QDockContainer): with builder.dock_context(target): with builder.drop_frame(frame): area = target.parentDockArea() if area is not None: plug_frame(area, target, frame, guide) #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def _free_container(self, container): """ Free the resources attached to the container. Parameters ---------- container : QDockContainer The container which should be cleaned up. It should be unplugged from any layout before being passed to this method. """ item = container.dockItem() container.setParent(None) container.setDockItem(None) container._manager = None self._dock_items.discard(item) self._dock_frames.remove(container) self._proximity_handler.removeFrame(container) def _free_window(self, window): """ Free the resources attached to the window. Parameters ---------- window : QDockWindow The Window which should be cleaned up. """ window.setParent(None) window.setDockArea(None) window._manager = None self._dock_frames.remove(window) self._proximity_handler.removeFrame(window) def _iter_dock_targets(self, frame): """ Get an iterable of potential dock targets. Parameters ---------- frame : QDockFrame The frame which is being docked, and therefore excluded from the target search. Returns ------- result : generator A generator which yields the dock container and dock area instances which are potential dock targets. """ for target in reversed(self._dock_frames): if target is not frame and target.isWindow(): if isinstance(target, QDockContainer): yield target elif isinstance(target, QDockWindow): yield target.dockArea() yield self._dock_area def _dock_target(self, frame, pos): """ Get the dock target for the given frame and position. Parameters ---------- frame : QDockFrame The dock frame which should be docked. pos : QPoint The global mouse position. Returns ------- result : QDockArea, QDockContainer, or None The potential dock target for the frame and position. """ for target in self._iter_dock_targets(frame): # Hit test the central pane instead of the entire dock area # so that mouse movement over the dock bars is ignored. if isinstance(target, QDockArea): pane = target.centralPane() local = pane.mapFromGlobal(pos) if pane.rect().contains(local): return target else: local = target.mapFromGlobal(pos) if target.rect().contains(local): return target def _update_drag_overlay(self, frame, pos): """ Update the overlay for a dragged frame. Parameters ---------- frame : QDockFrame The dock frame being dragged by the user. pos : QPoint The global coordinates of the mouse position. """ overlay = self._overlay target = self._dock_target(frame, pos) if isinstance(target, QDockContainer): local = target.mapFromGlobal(pos) overlay.mouse_over_widget(target, local) elif isinstance(target, QDockArea): # Disallow docking onto an area with a maximized widget. # This prevents a non-intuitive user experience. if target.maximizedWidget() is not None: overlay.hide() return local = target.mapFromGlobal(pos) widget = layout_hit_test(target, local) overlay.mouse_over_area(target, widget, local) else: overlay.hide()
class QtWidget(QtToolkitObject, ProxyWidget): """ A Qt implementation of an Enaml ProxyWidget. """ #: A reference to the toolkit widget created by the proxy. widget = Typed(QWidget) #: A QWidgetItem created on-demand for the widget. This is used by #: the layout engine to compute correct size hints for the widget. widget_item = Typed(QWidgetItem) def _default_widget_item(self): return QWidgetItem(self.widget) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying QWidget object. """ self.widget = QWidget(self.parent_widget()) def init_widget(self): """ Initialize the underlying QWidget object. """ super(QtWidget, self).init_widget() d = self.declaration if d.background: self.set_background(d.background) if d.foreground: self.set_foreground(d.foreground) if d.font: self.set_font(d.font) if d.show_focus_rect is not None: self.set_show_focus_rect(d.show_focus_rect) if -1 not in d.minimum_size: self.set_minimum_size(d.minimum_size) if -1 not in d.maximum_size: self.set_maximum_size(d.maximum_size) if d.tool_tip: self.set_tool_tip(d.tool_tip) if d.status_tip: self.set_status_tip(d.status_tip) self.set_enabled(d.enabled) # Don't make toplevel widgets visible during init or they will # flicker onto the screen. This applies particularly for things # like status bar widgets which are created with no parent and # then reparented by the status bar. Real top-level widgets must # be explicitly shown by calling their .show() method after they # are created. if self.widget.parent() or not d.visible: self.set_visible(d.visible) #-------------------------------------------------------------------------- # ProxyWidget API #-------------------------------------------------------------------------- def set_minimum_size(self, min_size): """ Sets the minimum size of the widget. """ # QWidget uses (0, 0) as the minimum size. if -1 in min_size: min_size = (0, 0) self.widget.setMinimumSize(QSize(*min_size)) def set_maximum_size(self, max_size): """ Sets the maximum size of the widget. """ # QWidget uses 16777215 as the max size if -1 in max_size: max_size = (16777215, 16777215) self.widget.setMaximumSize(QSize(*max_size)) def set_enabled(self, enabled): """ Set the enabled state of the widget. """ self.widget.setEnabled(enabled) def set_visible(self, visible): """ Set the visibility of the widget. """ self.widget.setVisible(visible) def set_background(self, background): """ Set the background color of the widget. """ widget = self.widget role = widget.backgroundRole() if background is not None: qcolor = get_cached_qcolor(background) widget.setAutoFillBackground(True) else: app_palette = QApplication.instance().palette(widget) qcolor = app_palette.color(role) widget.setAutoFillBackground(False) palette = widget.palette() palette.setColor(role, qcolor) widget.setPalette(palette) def set_foreground(self, foreground): """ Set the foreground color of the widget. """ widget = self.widget role = widget.foregroundRole() if foreground is not None: qcolor = get_cached_qcolor(foreground) else: app_palette = QApplication.instance().palette(widget) qcolor = app_palette.color(role) palette = widget.palette() palette.setColor(role, qcolor) widget.setPalette(palette) def set_font(self, font): """ Set the font of the widget. """ widget = self.widget if font is not None: widget.setFont(get_cached_qfont(font)) else: widget.setFont(QFont()) def set_show_focus_rect(self, show): """ Set whether or not to show the focus rect. This is currently only supported on OSX. """ if sys.platform == 'darwin': self.widget.setAttribute(Qt.WA_MacShowFocusRect, bool(show)) def set_tool_tip(self, tool_tip): """ Set the tool tip for the widget. """ self.widget.setToolTip(tool_tip) def set_status_tip(self, status_tip): """ Set the status tip for the widget. """ self.widget.setStatusTip(status_tip) def ensure_visible(self): """ Ensure the widget is visible. """ self.widget.setVisible(True) def ensure_hidden(self): """ Ensure the widget is hidden. """ self.widget.setVisible(False)
class QtWidget(QtToolkitObject, ProxyWidget): """ A Qt implementation of an Enaml ProxyWidget. """ #: A reference to the toolkit widget created by the proxy. widget = Typed(QWidget) #: A private copy of the declaration features. This ensures that #: feature cleanup will proceed correctly in the event that user #: code modifies the declaration features value at runtime. _features = Coerced(Feature.Flags) #: Internal storage for the shared widget action. _widget_action = Typed(QWidgetAction) #: Internal storage for the drag origin position. _drag_origin = Typed(QPoint) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the underlying QWidget object. """ self.widget = QWidget(self.parent_widget()) def init_widget(self): """ Initialize the underlying QWidget object. """ super(QtWidget, self).init_widget() widget = self.widget focus_registry.register(widget, self) self._setup_features() d = self.declaration if d.background: self.set_background(d.background) if d.foreground: self.set_foreground(d.foreground) if d.font: self.set_font(d.font) if -1 not in d.minimum_size: self.set_minimum_size(d.minimum_size) if -1 not in d.maximum_size: self.set_maximum_size(d.maximum_size) if d.tool_tip: self.set_tool_tip(d.tool_tip) if d.status_tip: self.set_status_tip(d.status_tip) if not d.enabled: self.set_enabled(d.enabled) self.refresh_style_sheet() # Don't make toplevel widgets visible during init or they will # flicker onto the screen. This applies particularly for things # like status bar widgets which are created with no parent and # then reparented by the status bar. Real top-level widgets must # be explicitly shown by calling their .show() method after they # are created. if widget.parent() or not d.visible: self.set_visible(d.visible) def destroy(self): """ Destroy the underlying QWidget object. """ self._teardown_features() focus_registry.unregister(self.widget) super(QtWidget, self).destroy() # If a QWidgetAction was created for this widget, then it has # taken ownership of the widget and the widget will be deleted # when the QWidgetAction is garbage collected. This means the # superclass destroy() method must run before the reference to # the QWidgetAction is dropped. del self._widget_action #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def _setup_features(self): """ Setup the advanced widget feature handlers. """ features = self._features = self.declaration.features if not features: return if features & Feature.FocusTraversal: self.hook_focus_traversal() if features & Feature.FocusEvents: self.hook_focus_events() if features & Feature.DragEnabled: self.hook_drag() if features & Feature.DropEnabled: self.hook_drop() def _teardown_features(self): """ Teardowns the advanced widget feature handlers. """ features = self._features if not features: return if features & Feature.FocusTraversal: self.unhook_focus_traversal() if features & Feature.FocusEvents: self.unhook_focus_events() if features & Feature.DragEnabled: self.unhook_drag() if features & Feature.DropEnabled: self.unhook_drop() #-------------------------------------------------------------------------- # Protected API #-------------------------------------------------------------------------- def refresh_style_sheet(self): """ Refresh the widget style sheet with the current style data. """ parts = [] name = self.widget.objectName() for style in StyleCache.styles(self.declaration): t = translate_style(name, style) if t: parts.append(t) if len(parts) > 0: stylesheet = u'\n\n'.join(parts) else: stylesheet = u'' self.widget.setStyleSheet(stylesheet) def tab_focus_request(self, reason): """ Handle a custom tab focus request. This method is called when focus is being set on the proxy as a result of a user-implemented focus traversal handler. This can be reimplemented by subclasses as needed. Parameters ---------- reason : Qt.FocusReason The reason value for the focus request. Returns ------- result : bool True if focus was set, False otherwise. """ widget = self.focus_target() if not widget.focusPolicy & Qt.TabFocus: return False if not widget.isEnabled(): return False if not widget.isVisibleTo(widget.window()): return False widget.setFocus(reason) return False def focus_target(self): """ Return the current focus target for a focus request. This can be reimplemented by subclasses as needed. The default implementation of this method returns the current proxy widget. """ return self.widget def hook_focus_traversal(self): """ Install the hooks for focus traversal. This method may be overridden by subclasses as needed. """ self.widget.focusNextPrevChild = self.focusNextPrevChild def unhook_focus_traversal(self): """ Remove the hooks for the next/prev child focusing. This method may be overridden by subclasses as needed. """ del self.widget.focusNextPrevChild def hook_focus_events(self): """ Install the hooks for focus events. This method may be overridden by subclasses as needed. """ widget = self.widget widget.focusInEvent = self.focusInEvent widget.focusOutEvent = self.focusOutEvent def unhook_focus_events(self): """ Remove the hooks for the focus events. This method may be overridden by subclasses as needed. """ widget = self.widget del widget.focusInEvent del widget.focusOutEvent def focusNextPrevChild(self, next_child): """ The default 'focusNextPrevChild' implementation. """ fd = focus_registry.focused_declaration() if next_child: child = self.declaration.next_focus_child(fd) reason = Qt.TabFocusReason else: child = self.declaration.previous_focus_child(fd) reason = Qt.BacktabFocusReason if child is not None and child.proxy_is_active: return child.proxy.tab_focus_request(reason) widget = self.widget return type(widget).focusNextPrevChild(widget, next_child) def focusInEvent(self, event): """ The default 'focusInEvent' implementation. """ widget = self.widget type(widget).focusInEvent(widget, event) self.declaration.focus_gained() def focusOutEvent(self, event): """ The default 'focusOutEvent' implementation. """ widget = self.widget type(widget).focusOutEvent(widget, event) self.declaration.focus_lost() def hook_drag(self): """ Install the hooks for drag operations. """ widget = self.widget widget.mousePressEvent = self.mousePressEvent widget.mouseMoveEvent = self.mouseMoveEvent widget.mouseReleaseEvent = self.mouseReleaseEvent def unhook_drag(self): """ Remove the hooks for drag operations. """ widget = self.widget del widget.mousePressEvent del widget.mouseMoveEvent del widget.mouseReleaseEvent def mousePressEvent(self, event): """ Handle the mouse press event for a drag operation. """ if event.button() == Qt.LeftButton: self._drag_origin = event.pos() widget = self.widget type(widget).mousePressEvent(widget, event) def mouseMoveEvent(self, event): """ Handle the mouse move event for a drag operation. """ if event.buttons() & Qt.LeftButton and self._drag_origin is not None: dist = (event.pos() - self._drag_origin).manhattanLength() if dist >= QApplication.startDragDistance(): self.do_drag() self._drag_origin = None return widget = self.widget type(widget).mouseMoveEvent(widget, event) def mouseReleaseEvent(self, event): """ Handle the mouse release event for the drag operation. """ if event.button() == Qt.LeftButton: self._drag_origin = None widget = self.widget type(widget).mouseReleaseEvent(widget, event) def hook_drop(self): """ Install hooks for drop operations. """ widget = self.widget widget.setAcceptDrops(True) widget.dragEnterEvent = self.dragEnterEvent widget.dragMoveEvent = self.dragMoveEvent widget.dragLeaveEvent = self.dragLeaveEvent widget.dropEvent = self.dropEvent def unhook_drop(self): """ Remove hooks for drop operations. """ widget = self.widget widget.setAcceptDrops(False) del widget.dragEnterEvent del widget.dragMoveEvent del widget.dragLeaveEvent del widget.dropEvent def do_drag(self): """ Perform the drag operation for the widget. """ drag_data = self.declaration.drag_start() if drag_data is None: return widget = self.widget qdrag = QDrag(widget) qdrag.setMimeData(drag_data.mime_data.q_data()) if drag_data.image is not None: qimg = get_cached_qimage(drag_data.image) qdrag.setPixmap(QPixmap.fromImage(qimg)) else: if __version_info__ < (5, ): qdrag.setPixmap(QPixmap.grabWidget(widget)) else: qdrag.setPixmap(widget.grab()) if drag_data.hotspot: qdrag.setHotSpot(QPoint(*drag_data.hotspot)) else: cursor_position = widget.mapFromGlobal(QCursor.pos()) qdrag.setHotSpot(cursor_position) default = Qt.DropAction(drag_data.default_drop_action) supported = Qt.DropActions(drag_data.supported_actions) qresult = qdrag.exec_(supported, default) self.declaration.drag_end(drag_data, DropAction(int(qresult))) def dragEnterEvent(self, event): """ Handle the drag enter event for the widget. """ self.declaration.drag_enter(QtDropEvent(event)) def dragMoveEvent(self, event): """ Handle the drag move event for the widget. """ self.declaration.drag_move(QtDropEvent(event)) def dragLeaveEvent(self, event): """ Handle the drag leave event for the widget. """ self.declaration.drag_leave() def dropEvent(self, event): """ Handle the drop event for the widget. """ self.declaration.drop(QtDropEvent(event)) #-------------------------------------------------------------------------- # Framework API #-------------------------------------------------------------------------- def get_action(self, create=False): """ Get the shared widget action for this widget. This API is used to support widgets in tool bars and menus. Parameters ---------- create : bool, optional Whether to create the action if it doesn't already exist. The default is False. Returns ------- result : QWidgetAction or None The cached widget action or None, depending on arguments. """ action = self._widget_action if action is None and create: action = self._widget_action = QWidgetAction(None) action.setDefaultWidget(self.widget) return action #-------------------------------------------------------------------------- # ProxyWidget API #-------------------------------------------------------------------------- def set_minimum_size(self, min_size): """ Sets the minimum size of the widget. """ # QWidget uses (0, 0) as the minimum size. if -1 in min_size: min_size = (0, 0) self.widget.setMinimumSize(QSize(*min_size)) def set_maximum_size(self, max_size): """ Sets the maximum size of the widget. """ # QWidget uses 16777215 as the max size if -1 in max_size: max_size = (16777215, 16777215) self.widget.setMaximumSize(QSize(*max_size)) def set_enabled(self, enabled): """ Set the enabled state of the widget. """ self.widget.setEnabled(enabled) action = self._widget_action if action is not None: action.setEnabled(enabled) def set_visible(self, visible): """ Set the visibility of the widget. """ self.widget.setVisible(visible) action = self._widget_action if action is not None: action.setVisible(visible) def set_background(self, background): """ Set the background color of the widget. """ widget = self.widget role = widget.backgroundRole() if background is not None: qcolor = get_cached_qcolor(background) widget.setAutoFillBackground(True) else: app_palette = QApplication.instance().palette(widget) qcolor = app_palette.color(role) widget.setAutoFillBackground(False) palette = widget.palette() palette.setColor(role, qcolor) widget.setPalette(palette) def set_foreground(self, foreground): """ Set the foreground color of the widget. """ widget = self.widget role = widget.foregroundRole() if foreground is not None: qcolor = get_cached_qcolor(foreground) else: app_palette = QApplication.instance().palette(widget) qcolor = app_palette.color(role) palette = widget.palette() palette.setColor(role, qcolor) widget.setPalette(palette) def set_font(self, font): """ Set the font of the widget. """ if font is not None: self.widget.setFont(get_cached_qfont(font)) else: self.widget.setFont(QFont()) def set_tool_tip(self, tool_tip): """ Set the tool tip for the widget. """ self.widget.setToolTip(tool_tip) def set_status_tip(self, status_tip): """ Set the status tip for the widget. """ self.widget.setStatusTip(status_tip) def ensure_visible(self): """ Ensure the widget is visible. """ self.widget.setVisible(True) action = self._widget_action if action is not None: action.setVisible(True) def ensure_hidden(self): """ Ensure the widget is hidden. """ self.widget.setVisible(False) action = self._widget_action if action is not None: action.setVisible(False) def restyle(self): """ Restyle the widget with the current style data. """ self.refresh_style_sheet() def set_focus(self): """ Set the keyboard input focus to this widget. """ self.focus_target().setFocus(Qt.OtherFocusReason) def clear_focus(self): """ Clear the keyboard input focus from this widget. """ self.focus_target().clearFocus() def has_focus(self): """ Test whether this widget has input focus. """ return self.focus_target().hasFocus() def focus_next_child(self): """ Give focus to the next widget in the focus chain. """ self.focus_target().focusNextChild() def focus_previous_child(self): """ Give focus to the previous widget in the focus chain. """ self.focus_target().focusPreviousChild()
class Channel(PSIContribution): #: Globally-unique name of channel used for identification name = d_(Str()).tag(metadata=True) #: Code assigned by subclasses to identify channel type type_code = Str() #: Unique reference label used for tracking identity throughout #: psiexperiment reference = Str().tag(metadata=True) #: Label of channel used in GUI label = d_(Str()).tag(metadata=True) #: Is channel active during experiment? active = Property().tag(metadata=True) # SI unit (e.g., V) unit = d_(Str()).tag(metadata=True) # Number of samples to acquire before task ends. Typically will be set to # 0 to indicate continuous acquisition. samples = d_(Int(0)).tag(metadata=True) # Used to properly configure data storage. dtype = d_(Str()).tag(metadata=True) # Parent engine (automatically derived by Enaml hierarchy) engine = Property().tag(metadata=True) # Calibration of channel calibration = d_(Typed(BaseCalibration, factory=FlatCalibration.unity)) calibration.tag(metadata=True) # Can the user modify the channel calibration? calibration_user_editable = d_(Bool(False)).tag(metadata=True) filter_delay = d_(Float(0).tag(metadata=True)) def _observe_name(self, event): self.reference = self._default_reference() def _default_reference(self): return f'{self.type_code}::{self.name}' def __init__(self, *args, **kwargs): # This is a hack due to the fact that name is defined as a Declarative # member and each Mixin will overwrite whether or not the name is # tagged. super().__init__(*args, **kwargs) self.members()['name'].tag(metadata=True) def _get_engine(self): return self.parent def _set_engine(self, engine): self.set_parent(engine) def configure(self): pass def sync_start(self, channel): ''' Synchronize with channel so that sampling begins at the same time Parameters ---------- channel : instance of Channel Channel to synchronize with. ''' raise NotImplementedError def _get_active(self): raise NotImplementedError def __str__(self): return self.label
class DockOverlay(Atom): """ An object which manages the overlays for dock widgets. This manager handles the state transitions for the overlays. The transitions are performed on a slightly-delayed timer to provide a more fluid user interaction experience. """ # PySide requires weakrefs for using bound methods as slots if QT_API == 'pyside': __slots__ = '__weakref__' #: The size of the rubber band when docking on the border, in px. border_size = Int(60) #: The delay to use when triggering the rose timer, in ms. rose_delay = Int(30) #: The delay to use when triggering the band timer, in ms. band_delay = Int(50) #: The target opacity to use when making the band visible. band_target_opacity = Float(1.0) #: The duration of the band visibilty animation, in ms. band_vis_duration = Int(100) #: the duration of the band geometry animation, in ms. band_geo_duration = Int(100) #: The overlayed guide rose. _rose = Typed(QGuideRose, ()) #: The overlayed rubber band. _band = Typed(QDockRubberBand, ()) #: The property animator for the rubber band geometry. _geo_animator = Typed(QPropertyAnimation) #: The property animator for the rubber band visibility. _vis_animator = Typed(QPropertyAnimation) #: The target mode to apply to the rose on timeout. _target_rose_mode = Int(QGuideRose.Mode.NoMode) #: The target geometry to apply to rubber band on timeout. _target_band_geo = Typed(QRect, factory=lambda: QRect()) #: The value of the last guide which was hit in the rose. _last_guide = Int(-1) #: A flag indicating whether it is safe to show the band. _show_band = Bool(False) #: The hover position of the mouse to use for state changes. _hover_pos = Typed(QPoint, factory=lambda: QPoint()) #: The timer for changing the state of the rose. _rose_timer = Typed(QTimer) #: The timer for changing the state of the band. _band_timer = Typed(QTimer) def __init__(self, parent=None): """ Initialize a DockOverlay. Parameters ---------- parent : QWidget, optional The parent of the overlay. This will be used as the parent widget for the dock rubber band. The overlay guides do not have a parent. """ self._band = QDockRubberBand(parent) #-------------------------------------------------------------------------- # Default Value Methods #-------------------------------------------------------------------------- def _default__rose_timer(self): """ Create the default timer for the rose state changes. """ timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(self._on_rose_timer) return timer def _default__band_timer(self): """ Create the default timer for the band state changes. """ timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(self._on_band_timer) return timer def _default__geo_animator(self): """ Create the default property animator for the rubber band. """ p = QPropertyAnimation(self._band, 'geometry') p.setDuration(self.band_geo_duration) return p def _default__vis_animator(self): """ Create the default property animator for the rubber band. """ p = QPropertyAnimation(self._band, 'windowOpacity') p.setDuration(self.band_vis_duration) p.finished.connect(self._on_vis_finished) return p #-------------------------------------------------------------------------- # Timer Handlers #-------------------------------------------------------------------------- def _on_rose_timer(self): """ Handle the timeout event for the internal rose timer. This handler transitions the rose to its new state and updates the position of the rubber band. """ rose = self._rose rose.setMode(self._target_rose_mode) rose.mouseOver(self._hover_pos) self._show_band = True self._update_band_state() def _on_band_timer(self): """ Handle the timeout event for the internal band timer. This handler updates the position of the rubber band. """ self._update_band_state() #-------------------------------------------------------------------------- # Animation Handlers #-------------------------------------------------------------------------- def _on_vis_finished(self): """ Handle the 'finished' signal from the visibility animator. This handle will hide the rubber band when its opacity is 0. """ band = self._band if band.windowOpacity() == 0.0: band.hide() #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def _update_band_state(self): """ Refresh the geometry and visible state of the rubber band. The state will be updated using animated properties to provide a nice fluid user experience. """ # A valid geometry indicates that the rubber should be shown on # the screen. An invalid geometry means it should be hidden. If # the validity is changed during animation, the animators are # restarted using the current state as their starting point. band = self._band geo = self._target_band_geo if geo.isValid() and self._show_band: # If the band is already hidden, the geometry animation can # be bypassed since the band can be located anywhere. if band.isHidden(): band.setGeometry(geo) self._start_vis_animator(self.band_target_opacity) self._rose.raise_() else: self._start_vis_animator(self.band_target_opacity) self._start_geo_animator(geo) else: self._start_vis_animator(0.0) def _start_vis_animator(self, opacity): """ (Re)start the visibility animator. Parameters ---------- opacity : float The target opacity of the target object. """ animator = self._vis_animator if animator.state() == animator.Running: animator.stop() target = animator.targetObject() if target.isHidden() and opacity != 0.0: target.setWindowOpacity(0.0) target.show() animator.setStartValue(target.windowOpacity()) animator.setEndValue(opacity) animator.start() def _start_geo_animator(self, geo): """ (Re)start the visibility animator. Parameters ---------- geo : QRect The target geometry for the target object. """ animator = self._geo_animator if animator.state() == animator.Running: animator.stop() target = animator.targetObject() animator.setStartValue(target.geometry()) animator.setEndValue(geo) animator.start() def _band_geometry(self, widget, guide): """ Compute the geometry for an overlay rubber band. Parameters ---------- widget : QWidget The widget to which the band geometry should be fit. guide : Guide The rose guide under the mouse. This determines how the geometry of the band will be fit to the widget. """ Guide = QGuideRose.Guide if guide == Guide.NoGuide: return QRect() # border hits border_size = self.border_size rect = widget.contentsRect() if guide == Guide.BorderNorth: rect.setHeight(border_size) elif guide == Guide.BorderEast: rect.setLeft(rect.right() + 1 - border_size) elif guide == Guide.BorderSouth: rect.setTop(rect.bottom() + 1 - border_size) elif guide == Guide.BorderWest: rect.setWidth(border_size) # For the next 4 conditions `widget` will be a QDockArea elif guide == Guide.BorderExNorth: bar_rect = widget.dockBarGeometry(QDockBar.North) if bar_rect.isValid(): rect = bar_rect else: rect.setHeight(border_size / 2) elif guide == Guide.BorderExEast: bar_rect = widget.dockBarGeometry(QDockBar.East) if bar_rect.isValid(): rect = bar_rect else: rect.setLeft(rect.right() + 1 - border_size / 2) elif guide == Guide.BorderExSouth: bar_rect = widget.dockBarGeometry(QDockBar.South) if bar_rect.isValid(): rect = bar_rect else: rect.setTop(rect.bottom() + 1 - border_size / 2) elif guide == Guide.BorderExWest: bar_rect = widget.dockBarGeometry(QDockBar.West) if bar_rect.isValid(): rect = bar_rect else: rect.setWidth(border_size / 2) # compass hits elif guide == Guide.CompassNorth: rect.setHeight(rect.height() / 3) elif guide == Guide.CompassEast: rect.setLeft(2 * rect.width() / 3) elif guide == Guide.CompassSouth: rect.setTop(2 * rect.height() / 3) elif guide == Guide.CompassWest: rect.setWidth(rect.width() / 3) elif guide == Guide.CompassCenter: pass # nothing to do elif guide == Guide.CompassExNorth: pass # nothing to do elif guide == Guide.CompassExEast: pass # nothing to do elif guide == Guide.CompassExSouth: pass # nothing to do elif guide == Guide.CompassExWest: pass # nothing to do # splitter handle hits elif guide == Guide.SplitHorizontal: wo, r = divmod(border_size - rect.width(), 2) rect.setWidth(2 * (wo + r) + rect.width()) rect.moveLeft(rect.x() - (wo + r)) elif guide == Guide.SplitVertical: ho, r = divmod(border_size - widget.height(), 2) rect.setHeight(2 * (ho + r) + rect.height()) rect.moveTop(rect.y() - (ho + r)) # single center elif guide == Guide.AreaCenter: pass # nothing to do # default no-op else: return QRect() pt = widget.mapToGlobal(rect.topLeft()) return QRect(pt, rect.size()) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def guide_at(self, pos): """ Get the dock guide for a given position. Parameters ---------- pos : QPoint The position of interest, expressed in global coordinates. Returns ------- result : Guide The guide enum which lies under the given point. """ rose = self._rose pos = rose.mapFromGlobal(pos) return rose.guideAt(pos) def hide(self): """ Hide the overlay. This method will stop the timers and set the visibility of the guide rose and the rubber band to False. """ self._rose_timer.stop() self._band_timer.stop() self._rose.hide() self._band.hide() def mouse_over_widget(self, widget, pos, empty=False): """ Update the overlays based on the mouse position. This handler should be invoked when the mouse hovers over a single widget (such as a floating dock container) as opposed to an area of docked widgets. The guide rose will be displayed in the center of the widget with no border guides. Parameters ---------- widget : QWidget The widget under the mouse. pos : QPoint The hover position, expressed in the local coordinates of the widget. empty : bool, optional Whether the widget represents an empty widget. If this is True, a single center guide will be shown instead of the guide rose. """ Mode = QGuideRose.Mode rose = self._rose target_mode = Mode.AreaCenter if empty else Mode.CompassEx self._target_rose_mode = target_mode if rose.mode() != target_mode: rose.setMode(Mode.NoMode) self._rose_timer.start(self.rose_delay) self._band_timer.start(self.band_delay) origin = widget.mapToGlobal(QPoint(0, 0)) geo = QRect(origin, widget.size()) dirty = rose.geometry() != geo if dirty: rose.hide() rose.setMode(Mode.NoMode) rose.setGeometry(geo) guide = rose.guideAt(pos, target_mode) if dirty or guide != self._last_guide: self._last_guide = guide self._target_band_geo = self._band_geometry(widget, guide) self._band_timer.start(self.band_delay) rose.setCenterPoint(QPoint(geo.width() / 2, geo.height() / 2)) rose.mouseOver(pos) rose.show() def mouse_over_area(self, area, widget, pos): """ Update the overlays based on the mouse position. Parameters ---------- area : QDockArea The dock area which contains the dock items onto which the overlay will be displayed. widget : QWidget The dock widget in the area which is under the mouse, or None if there is no relevant widget. pos : QPoint The hover position, expressed in the local coordinates of the overlayed dock area. """ Mode = QGuideRose.Mode Guide = QGuideRose.Guide pane = area.centralPane() pos = pane.mapFrom(area, pos) if widget is None: if area.centralWidget() is None: self.mouse_over_widget(pane, pos, empty=True) return # Compute the target mode for the guide rose based on the dock # widget which lies under the mouse position. target_mode = Mode.Border if isinstance(widget, QDockContainer): target_mode |= Mode.CompassEx elif isinstance(widget, QDockTabWidget): target_mode |= Mode.Compass elif isinstance(widget, QDockSplitterHandle): if widget.orientation() == Qt.Horizontal: target_mode |= Mode.SplitHorizontal else: target_mode |= Mode.SplitVertical # Get the local area coordinates for the center of the widget. center = widget.mapTo(pane, QPoint(0, 0)) center += QPoint(widget.width() / 2, widget.height() / 2) # Update the state of the rose. If it is to be hidden, it is # done so immediately. If the target mode is different from # the current mode, the rose is hidden and the state changes # are collapsed on a timer. rose = self._rose self._hover_pos = pos self._show_band = True self._target_rose_mode = target_mode if target_mode != rose.mode(): rose.setMode(Mode.Border) self._rose_timer.start(self.rose_delay) self._show_band = False # Update the geometry of the rose if needed. This ensures that # the rose does not change geometry while visible. origin = pane.mapToGlobal(QPoint(0, 0)) geo = QRect(origin, pane.size()) dirty = rose.geometry() != geo if dirty: rose.hide() rose.setMode(Mode.NoMode) rose.setGeometry(geo) # Hit test the rose and update the target geometry for the # rubber band if the target guide has changed. rose.setCenterPoint(center) guide = rose.guideAt(pos, target_mode) if dirty or guide != self._last_guide: self._last_guide = guide if guide >= Guide.BorderNorth and guide <= Guide.BorderWest: band_geo = self._band_geometry(pane, guide) elif guide >= Guide.BorderExNorth and guide <= Guide.BorderExWest: band_geo = self._band_geometry(area, guide) else: band_geo = self._band_geometry(widget, guide) self._target_band_geo = band_geo self._band_timer.start(self.band_delay) # Finally, make the rose visible and issue a mouseover command # so that the guides are highlighted. rose.mouseOver(pos) rose.show()
class Field(Control): """ A single line editable text widget. """ #: The text to display in the field. text = d_(Unicode()) #: The mask to use for text input: #: http://qt-project.org/doc/qt-4.8/qlineedit.html#inputMask-prop #: #: The summary of the mask grammar is as follows: #: A ASCII alphabetic character required. A-Z, a-z. #: a ASCII alphabetic character permitted but not required. #: N ASCII alphanumeric character required. A-Z, a-z, 0-9. #: n ASCII alphanumeric character permitted but not required. #: X Any character required. #: x Any character permitted but not required. #: 9 ASCII digit required. 0-9. #: 0 ASCII digit permitted but not required. #: D ASCII digit required. 1-9. #: d ASCII digit permitted but not required (1-9). #: # ASCII digit or plus/minus sign permitted but not required. #: H Hexadecimal character required. A-F, a-f, 0-9. #: h Hexadecimal character permitted but not required. #: B Binary character required. 0-1. #: b Binary character permitted but not required. #: > All following alphabetic characters are uppercased. #: < All following alphabetic characters are lowercased. #: ! Switch off case conversion. #: \ Use \ to escape the special characters listed above to use them as separators. #: #: The mask consists of a string of mask characters and separators, optionally #: followed by a semicolon and the character used for blanks #: Eg: 9 digit phone number: (999) 999-9999;_ mask = d_(Unicode()) #: The validator to use for this field. If the validator provides #: a client side validator, then text will only be submitted if it #: passes that validator. validator = d_(Typed(Validator)) #: The list of actions which should cause the client to submit its #: text to the server for validation and update. The currently #: supported values are 'lost_focus', 'return_pressed', and 'auto_sync'. #: The 'auto_sync' mode will attempt to validate and synchronize the #: text when the user stops typing. submit_triggers = d_( List(Enum('lost_focus', 'return_pressed', 'auto_sync'), ['lost_focus', 'return_pressed'])) #: The grayed-out text to display if the field is empty and the #: widget doesn't have focus. Defaults to the empty string. placeholder = d_(Unicode()) #: How to display the text in the field. Valid values are 'normal' #: which displays the text as normal, 'password' which displays the #: text with an obscured character, and 'silent' which displays no #: text at all but still allows input. echo_mode = d_(Enum('normal', 'password', 'silent')) #: The maximum length of the field in characters. The default value #: is Zero and indicates there is no maximum length. max_length = d_(Int(0)) #: Whether or not the field is read only. Defaults to False. read_only = d_(Bool(False)) #: How strongly a component hugs it's contents' width. Fields ignore #: the width hug by default, so they expand freely in width. hug_width = set_default('ignore') #: A reference to the ProxyField object. proxy = Typed(ProxyField) #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('text', 'mask', 'submit_triggers', 'placeholder', 'echo_mode', 'max_length', 'read_only') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass implementation is sufficient. super(Field, self)._update_proxy(change) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def field_text(self): """ Get the text stored in the field control. Depending on the state of the field, this text may be different than that stored in the 'text' attribute. Returns ------- result : str The text stored in the field. """ if self.proxy_is_active: return self.proxy.field_text() return ''
class QtSeparator(QtControl, ProxySeparator): """ A Qt implementation of an Enaml ProxySeparator. """ #: A reference to the widget created by the proxy. widget = Typed(QSeparator) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create underlying QSeparator control. """ self.widget = QSeparator(self.parent_widget()) def init_widget(self): """ Initialize the underlying widget. """ super(QtSeparator, self).init_widget() d = self.declaration self.set_orientation(d.orientation, sh_guard=False) self.set_line_style(d.line_style, sh_guard=False) self.set_line_width(d.line_width, sh_guard=False) self.set_midline_width(d.midline_width, sh_guard=False) #-------------------------------------------------------------------------- # Widget Update Methods #-------------------------------------------------------------------------- def set_orientation(self, orientation, sh_guard=True): """ Set the orientation of the underlying widget. """ if sh_guard: with size_hint_guard(self): self.widget.setFrameShape(LINE_SHAPES[orientation]) else: self.widget.setFrameShape(LINE_SHAPES[orientation]) def set_line_style(self, style, sh_guard=True): """ Set the line style of the underlying widget. """ if sh_guard: with size_hint_guard(self): self.widget.setFrameShadow(LINE_STYLES[style]) else: self.widget.setFrameShadow(LINE_STYLES[style]) def set_line_width(self, width, sh_guard=True): """ Set the line width of the underlying widget. """ if sh_guard: with size_hint_guard(self): self.widget.setLineWidth(width) else: self.widget.setLineWidth(width) self.widget.update() def set_midline_width(self, width, sh_guard=True): """ Set the midline width of the underlying widget. """ if sh_guard: with size_hint_guard(self): self.widget.setMidLineWidth(width) else: self.widget.setMidLineWidth(width) self.widget.update()
class Race(FormulaModel): date = Typed(datetime.datetime) name = Unicode() round = Coerced(int) season = Coerced(int) url = Unicode() temp = Coerced(float) #ToDo: should affect tires rain = Bool(default=False ) #ToDo: rain should affect prob wreck, safety car, car speeds circuit = Typed(Circuit) drivers = Property() _drivers = Typed(Drivers) laps = Property() _laps = Typed(Laps) standings = Property() _standings = Typed(Dict) def _get_drivers(self): if not self._drivers: query = { 'year': self.season, 'circuit_id': self.circuit.circuitId, 'query_type': 'drivers' } self.drivers = self.api.query(**query) return self._drivers def _set_drivers(self, drivers): self._drivers = Drivers(drivers) def _get_laps(self): if not self._laps: query = { 'year': self.season, 'race_num': str(self.round), 'query_type': 'laps' } tmp_season = self.api.query(**query) self.laps = tmp_season.races[0].laps return self._laps def _set_laps(self, laps): if not isinstance(laps, Laps): laps = Laps(laps, race=self) self._laps = laps def _get_standings(self): if not self._standings: query = { 'year': self.season, 'race_num': str(self.round), 'query_type': 'driverStandings' } driver_standings = Standings(self.api.query(**query)) query = { 'year': self.season, 'race_num': str(self.round), 'query_type': 'constructorStandings' } constructor_standings = Standings(self.api.query(**query)) standings_group = Dict() standings_group.drivers = driver_standings standings_group.constructors = constructor_standings self.standings = standings_group return self._standings def _set_standings(self, standings): self._standings = standings @classmethod def from_dict(cls, kwargs): date = kwargs.pop('date') if 'time' in kwargs.keys(): race_time = kwargs.pop('time') kwargs['date'] = datetime.datetime.strptime( date + ' ' + race_time[:-1], '%Y-%m-%d %H:%M:%S') else: kwargs['date'] = datetime.datetime.strptime(date, '%Y-%m-%d') kwargs['circuit'] = Circuit.from_dict(kwargs.pop('Circuit')) kwargs['name'] = kwargs.pop('raceName') if 'Laps' in kwargs: kwargs['laps'] = [Lap.from_dict(lap) for lap in kwargs.pop('Laps')] return cls(**kwargs) @property def time(self): return self.date.time() def to_row(self): dict_props = ['date', 'name', 'round', 'season', 'time'] cir = self.circuit.to_row() return dict({k: getattr(self, k) for k in dict_props}.items() + cir.items()) def __repr__(self): return ('%s-%s: %s') % (self.season, self.round, self.name) def __str__(self): name = variablize(self.name) return name + '_' + str(self.round)
class AndroidViewPager(AndroidViewGroup, ProxyViewPager): """ An Android implementation of an Enaml ProxyViewPager. """ #: A reference to the widget created by the proxy. widget = Typed(ViewPager) #: Adapter adapter = Typed(BridgedFragmentStatePagerAdapter) #: Pending changes _notify_count = Int() _notify_delay = Int(2) _pending_calls = List() @property def pages(self): """ Get pages """ #: Defer the import for p in self.declaration.pages: yield p.proxy # ------------------------------------------------------------------------- # Initialization API # ------------------------------------------------------------------------- def create_widget(self): """ Create the underlying widget. """ self.widget = ViewPager(self.get_context()) self.adapter = BridgedFragmentStatePagerAdapter() def init_layout(self): super(AndroidViewPager, self).init_layout() d = self.declaration w = self.widget #: Set adapter w.setAdapter(self.adapter) w.addOnPageChangeListener(w.getId()) w.onPageSelected.connect(self.on_page_selected) if d.current_index: self.set_current_index(d.current_index) def child_added(self, child): """ When a child is added, schedule a data changed notification """ super(AndroidViewPager, self).child_added(child) self._notify_count += 1 self.get_context().timed_call(self._notify_delay, self._notify_change) def child_removed(self, child): """ When a child is removed, schedule a data changed notification """ super(AndroidViewPager, self).child_removed(child) self._notify_count += 1 self.get_context().timed_call(self._notify_delay, self._notify_change) def _notify_change(self): """ After all changes have settled, tell Java it changed """ d = self.declaration self._notify_count -= 1 if self._notify_count == 0: #: Tell the UI we made changes self.adapter.notifyDataSetChanged(now=True) self.get_context().timed_call(500, self._queue_pending_calls) def _queue_pending_calls(self): #: Now wait for current page to load, then invoke any pending calls for i, page in enumerate(self.pages): #: Wait for first page! #: Trigger when the current page is loaded page.ready.then(self._run_pending_calls) #: If the page is already complete it will be called right away break def _run_pending_calls(self, *args): if self._pending_calls: for call in self._pending_calls: call() self._pending_calls = [] # ------------------------------------------------------------------------- # OnItemRequestedListener API # ------------------------------------------------------------------------- # def on_item_requested(self, position): # print "on_item_requested" # for i, c in enumerate(self.children()): # if i == position: # return c.widget # ------------------------------------------------------------------------- # OnPageChangeListener API # ------------------------------------------------------------------------- def on_page_scroll_state_changed(self, state): pass def on_page_scrolled(self, position, offset, offset_pixels): pass def on_page_selected(self, position): d = self.declaration with self.widget.setCurrentItem.suppressed(): d.current_index = position # ------------------------------------------------------------------------- # ProxyViewPager API # ------------------------------------------------------------------------- def set_current_index(self, index): """ We can only set the index once the page has been created. otherwise we get `FragmentManager is already executing transactions` errors in Java. To avoid this, we only call this once has been loaded. """ # d = self.declaration # #: We have to wait for the current_index to be ready before we can # #: change pages if self._notify_count > 0: self._pending_calls.append( lambda index=index: self.widget.setCurrentItem(index)) else: self.widget.setCurrentItem(index) def set_offscreen_page_limit(self, limit): self.widget.setOffscreenPageLimit(limit) def set_page_margin(self, margin): self.widget.setPageMargin(margin) def set_paging_enabled(self, enabled): self.widget.setPagingEnabled(enabled) def set_transition(self, transition): self.widget.setPageTransformer(True, PageTransformer.from_name(transition)) def create_layout_params(self, child, layout): """ Override as there is no (width, height) constructor. """ from .android_fragment import AndroidFragment if isinstance(child, AndroidFragment): return super(AndroidViewPager, self).create_layout_params(child, layout) # Only apply to decor views dp = self.dp w, h = (coerce_size(layout.get('width', 'match_parent')), coerce_size(layout.get('height', 'wrap_content'))) w = w if w < 0 else int(w * dp) h = h if h < 0 else int(h * dp) # No (w,h) constructor params = ViewPagerLayoutParams() params.width = w params.height = h params.isDecor = True return params def apply_layout(self, child, layout): super(AndroidViewPager, self).apply_layout(child, layout) if 'gravity' in layout: child.layout_params.gravity = coerce_gravity(layout['gravity'])
class BottomSheetDialog(Dialog): """ A dialog that slides up from the bottom of the screen. """ #: A reference to the proxy object. proxy = Typed(ProxyBottomSheetDialog)
class AndroidMapPolyline(AndroidMapItemBase, ProxyMapPolyline): """ An Android implementation of an Enaml ProxyMapPolyline. """ #: Hold the points points = Typed(LatLngList) def create_widget(self): """ Create the MarkerOptions for this map marker this later gets converted into a "Marker" instance when addMarker is called """ self.options = PolylineOptions() #: List to hold our points self.points = LatLngList() def add_to_map(self, mapview): mapview.addPolyline(self.options).then(self.on_marker) def init_widget(self): super(AndroidMapPolyline, self).init_widget() d = self.declaration self.set_points(d.points) #if d.clickable: # self.set_clickable(d.clickable) if d.color: self.set_color(d.color) if d.end_cap != 'butt': self.set_end_cap(d.end_cap) if d.start_cap != 'butt': self.set_start_cap(d.start_cap) if d.geodesic: self.set_geodesic(d.geodesic) if d.joint_type: self.set_joint_type(d.joint_type) if d.width != 10: self.set_width(d.width) # ------------------------------------------------------------------------- # Polyline API # ------------------------------------------------------------------------- def on_marker(self, mid): """ Convert our options into the actual marker object""" #mid, pos = marker self.marker = Polyline(__id__=mid) self.parent().markers[mid] = self self.marker.setTag(mid) d = self.declaration if d.clickable: self.set_clickable(d.clickable) #: Can free the options now del self.options def on_click(self): d = self.declaration d.clicked() # ------------------------------------------------------------------------- # ProxyMapPolyline API # ------------------------------------------------------------------------- def set_points(self, points): #: Have to hold on until after added to the ArrayList #: or the GC cleans them up and the bridge destroys them self.points.refresh_points(points) if self.marker: self.marker.setPoints(self.points) else: self.options.addAll(self.points) def update_points(self, change): """ Update the points in a smart way without passing them over the bridge with every change. """ #: Delegate to the special LatLngList self.points.handle_change(change) #: Trigger update self.marker.setPoints(self.points) def set_clickable(self, clickable): if self.marker: self.marker.setClickable(clickable) else: self.options.clickable(clickable) def set_color(self, color): if self.marker: self.marker.setColor(color) else: self.options.color(color) def set_end_cap(self, cap): if self.marker: self.marker.setEndCap(Polyline.CAPS[cap]()) else: self.options.endCap(Polyline.CAPS[cap]()) def set_geodesic(self, geodesic): if self.marker: self.marker.setGeodesic(geodesic) else: self.options.geodesic(geodesic) def set_joint_type(self, joint_type): if self.marker: self.marker.setJointType(Polyline.JOINT_TYPES[joint_type]) else: self.options.jointType(Polyline.JOINT_TYPES[joint_type]) def set_start_cap(self, cap): if self.marker: self.marker.setStartCap(Polyline.CAPS[cap]()) else: self.options.startCap(Polyline.CAPS[cap]()) def set_width(self, width): if self.marker: self.marker.setWidth(width) else: self.options.width(width)
class AndroidMapPolygon(AndroidMapItemBase, ProxyMapPolygon): """ An Android implementation of an Enaml ProxyMapPolygon. """ #: Hold the points points = Typed(LatLngList) #: Hold the holes #holes = List(ArrayList) def create_widget(self): """ Create the MarkerOptions for this map marker this later gets converted into a "Marker" instance when addMarker is called """ self.options = PolygonOptions() self.points = LatLngList() def add_to_map(self, mapview): mapview.addPolygon(self.options).then(self.on_marker) def init_widget(self): super(AndroidMapPolygon, self).init_widget() d = self.declaration self.set_points(d.points) #if d.clickable: # self.set_clickable(d.clickable) if d.fill_color: self.set_fill_color(d.fill_color) if d.geodesic: self.set_geodesic(d.geodesic) if d.stroke_joint_type: self.set_stroke_joint_type(d.joint_type) if d.stroke_color: self.set_stroke_color(d.stroke_color) if d.stroke_width != 10: self.set_stroke_width(d.stroke_width) # ------------------------------------------------------------------------- # Marker API # ------------------------------------------------------------------------- def on_marker(self, mid): """ Convert our options into the actual marker object""" #mid, pos = marker self.marker = Polygon(__id__=mid) self.parent().markers[mid] = self self.marker.setTag(mid) d = self.declaration if d.clickable: self.set_clickable(d.clickable) #: Can free the options now del self.options def on_click(self): d = self.declaration d.clicked() # ------------------------------------------------------------------------- # ProxyMapMarker API # ------------------------------------------------------------------------- def set_points(self, points): #: Have to hold on until after added to the ArrayList #: or the GC cleans them up and the bridge destroys them self.points.refresh_points(points) if self.marker: self.marker.setPoints(self.points) else: self.options.addAll(self.points) def update_points(self, change): #: Defer to points self.points.handle_change(change) #: Trigger update self.marker.setPoints(self.points) def set_clickable(self, clickable): if self.marker: self.marker.setClickable(clickable) else: self.options.clickable(clickable) def set_holes(self, holes): if self.marker: self.marker.setHoles( [bridge.encode(LatLng(*p)) for hole in holes for p in hole]) else: for hole in holes: self.options.addHole([bridge.encode(LatLng(*p)) for p in hole]) def set_fill_color(self, color): if self.marker: self.marker.setFillColor(color) else: self.options.fillColor(color) def set_geodesic(self, geodesic): if self.marker: self.marker.setGeodesic(geodesic) else: self.options.geodesic(geodesic) def set_stroke_color(self, color): if self.marker: self.marker.setStrokeColor(color) else: self.options.strokeColor(color) def set_stroke_joint_type(self, joint_type): if self.marker: self.marker.setStrokeJointType(Polyline.JOINT_TYPES[joint_type]) else: self.options.strokeJointType(Polyline.JOINT_TYPES[joint_type]) def set_stroke_width(self, width): if self.marker: self.marker.setStrokeWidth(width) else: self.options.strokeWidth(width)
class Looper(Pattern): """ A pattern object that repeats its children over an iterable. The children of a `Looper` are used as a template when creating new objects for each item in the given `iterable`. Each iteration of the loop will be given an indenpendent scope which is the union of the outer scope and any identifiers created during the iteration. This scope will also contain `loop_index` and `loop_item` variables which are the index and value of the iterable, respectively. All items created by the looper will be added as children of the parent of the `Looper`. The `Looper` keeps ownership of all items it creates. When the iterable for the looper is changed, the looper will only create and destroy children for the items in the iterable which have changed. """ #: The iterable to use when creating the items for the looper. #: The items in the iterable must be unique. This allows the #: Looper to optimize the creation and destruction of widgets. iterable = d_(Instance(Iterable)) #: The list of items created by the conditional. Each item in the #: list represents one iteration of the loop and is a list of the #: items generated during that iteration. This list should not be #: manipulated directly by user code. items = List() #: Private data storage which maps the user iterable data to the #: list of items created for that iteration. This allows the looper #: to only create and destroy the items which have changed. _iter_data = Typed(sortedmap, ()) #-------------------------------------------------------------------------- # Lifetime API #-------------------------------------------------------------------------- def destroy(self): """ A reimplemented destructor. The looper will release the owned items on destruction. """ super(Looper, self).destroy() del self.iterable del self.items del self._iter_data #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- def _observe_iterable(self, change): """ A private observer for the `iterable` attribute. If the iterable changes while the looper is active, the loop items will be refreshed. """ if change['type'] == 'update' and self.is_initialized: self.refresh_items() #-------------------------------------------------------------------------- # Pattern API #-------------------------------------------------------------------------- def pattern_items(self): """ Get a list of items created by the pattern. """ return sum(self.items, []) def refresh_items(self): """ Refresh the items of the pattern. This method destroys the old items and creates and initializes the new items. """ old_items = self.items[:] old_iter_data = self._iter_data iterable = self.iterable pattern_nodes = self.pattern_nodes new_iter_data = sortedmap() new_items = [] if iterable is not None and len(pattern_nodes) > 0: for loop_index, loop_item in enumerate(iterable): iteration = old_iter_data.get(loop_item) if iteration is not None: new_iter_data[loop_item] = iteration new_items.append(iteration) old_items.remove(iteration) continue iteration = [] new_iter_data[loop_item] = iteration new_items.append(iteration) for nodes, key, f_locals in pattern_nodes: with new_scope(key, f_locals) as f_locals: f_locals['loop_index'] = loop_index f_locals['loop_item'] = loop_item for node in nodes: child = node(None) if isinstance(child, list): iteration.extend(child) else: iteration.append(child) for iteration in old_items: for old in iteration: if not old.is_destroyed: old.destroy() if len(new_items) > 0: expanded = [] recursive_expand(sum(new_items, []), expanded) self.parent.insert_children(self, expanded) self.items = new_items self._iter_data = new_iter_data