pax_global_header00006660000000000000000000000064145661373140014524gustar00rootroot0000000000000052 comment=ed7ac31923d2cd77508e9a20749177833efea1c7 enamlx-0.6.4/000077500000000000000000000000001456613731400130175ustar00rootroot00000000000000enamlx-0.6.4/.gitignore000066400000000000000000000001471456613731400150110ustar00rootroot00000000000000*.pyc *.enamlc __pycache__/ __enamlcache__/ build/ dist/ .*/ *.egg-info *.dist-info *.kdev4 *.kate-swp enamlx-0.6.4/.isort.cfg000066400000000000000000000000321456613731400147110ustar00rootroot00000000000000[settings] profile=black enamlx-0.6.4/.project000066400000000000000000000005501456613731400144660ustar00rootroot00000000000000 enamlx org.python.pydev.PyDevBuilder org.python.pydev.pythonNature enamlx-0.6.4/.pydevproject000066400000000000000000000006611456613731400155410ustar00rootroot00000000000000 python 2.7 Default /${PROJECT_DIR_NAME} enamlx-0.6.4/LICENSE000066400000000000000000000020651456613731400140270ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 frmdstryr Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. enamlx-0.6.4/README.md000066400000000000000000000023511456613731400142770ustar00rootroot00000000000000# enamlx Additional Qt Widgets for Enaml, mainly used for the Tree and Table widgets. Supports 3.5+ Qt5 and Qt6. ## Install Now on [pypi](https://pypi.org/project/enamlx/). ```bash pip install enamlx ``` #### Widgets #### 1. TableView 2. TreeView 3. DoubleSpinBox 4. GraphicsView 5. PyQtGraph Plot 6. KeyEvent #### Examples #### __TableView__ Table view using enaml syntax. See example for usage. ![table view](https://lh6.googleusercontent.com/FUfzbzZpsMuGymnNdzBeXgONZXJGQreswK05lMP1zRlesxY70Xo14dxYBBOrqb23DCf6yOMeXYqHNxEaNtdc13GNmri6-pQ3-uoq4rcgRvHh3b8J58MVx_xZaifCHz2Hv0Q3CoQ) 1. Text/Icons/Checkboxes 2. Delegate widgets (any widget can be child of a cell) 3. Right click menus per item 4. Tested and working with 1 million+ rows. __DoubleSpinBox__ SpinBox that works with float values __PlotItem__ Plot widgets using PyQtGraph ![plot item](https://lh5.googleusercontent.com/pqa4WZnMzaU72pYnqc75AghnJGC8Z6kCELcsHkR3n_VTQzEmCB9di7reqqQbCIpnfAVXSCEXK6y07_DMyQ51XUCUAOe-xczfKsYKCRROPbUlDHcGMNSFaBmZRGxXP9Clya_q34I) __GraphicsView__ A "canvas" Widget for drawing with Qt's GraphicsView. # Usage ```python import enamlx enamlx.install() # Then use like any enaml widget from enamlx.widgets.api import TreeView # etc.. ``` enamlx-0.6.4/enamlx/000077500000000000000000000000001456613731400143035ustar00rootroot00000000000000enamlx-0.6.4/enamlx/__init__.py000066400000000000000000000002461456613731400164160ustar00rootroot00000000000000def install(): """Install the toolkit factory widgets for the widgets provided by this library. """ from enamlx.qt import qt_factories # noqa: F401 enamlx-0.6.4/enamlx/core/000077500000000000000000000000001456613731400152335ustar00rootroot00000000000000enamlx-0.6.4/enamlx/core/__init__.py000066400000000000000000000000001456613731400173320ustar00rootroot00000000000000enamlx-0.6.4/enamlx/core/block.py000066400000000000000000000032221456613731400166760ustar00rootroot00000000000000""" Created on Apr 15, 2017 @author: jrm """ from atom.api import ForwardInstance from enaml.core.declarative import Declarative, d_ class Block(Declarative): """An object which dynamically insert's its children into another block's parent object. The 'Block' object is used to cleanly and easily insert it's children into the children of another object. The 'Object' instance assigned to the 'object' property of the 'Block' will be parented with the parent of the 'Include'. Creating an 'Include' with no parent is a programming error. """ #: The Block to which this blocks children should be inserted into block = d_(ForwardInstance(lambda: Block)) def initialize(self): """A reimplemented initializer. This method will add the include objects to the parent of the include and ensure that they are initialized. """ super(Block, self).initialize() if self.block: self.block.parent.insert_children(self.block, self.children) def _observe_block(self, change): """A change handler for the 'objects' list of the Include. If the object is initialized objects which are removed will be unparented and objects which are added will be reparented. Old objects will be destroyed if the 'destroy_old' flag is True. """ if self.is_initialized: if change["type"] == "update": old_block = change["oldvalue"] old_block.parent.remove_children(old_block, self.children) new_block = change["value"] new_block.parent.insert_children(new_block, self.children) enamlx-0.6.4/enamlx/core/looper.py000066400000000000000000000227061456613731400171140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Aug 29, 2015 Looper that only shows visible elements @author: jrm """ from atom.api import ContainerList, Instance, List, observe from enaml.core.declarative import d_ from enaml.core.looper import Looper, new_scope, recursive_expand, sortedmap from enamlx.widgets.abstract_item_view import AbstractItemView class ListLooper(Looper): """A looper handles ContainerList updates""" items = ContainerList() _expanded = List() def _observe_iterable(self, change): if not self.is_initialized: return elif change["type"] == "update": self.refresh_items() elif change["type"] == "container": if change["operation"] == "append": self.append_items([change["item"]]) elif change["operation"] == "extend": self.append_items(change["items"]) elif change["operation"] == "insert": self.insert_item(change["index"], change["item"]) elif change["operation"] == "remove": self.remove_items([change["item"]]) elif change["operation"] == "pop": self.pop_item(change["index"]) elif change["operation"] in ["reverse", "sort"]: self.refresh_items() def remove_items(self, old_items): for item in old_items: index = self.items.index(item) self.pop_item(index) def pop_item(self, index): for iteration in self.items.pop(index): for old in iteration: if not old.is_destroyed: old.destroy() def insert_item(self, loop_index, loop_item): iteration = [] self._iter_data[loop_item] = iteration for nodes, key, f_locals in self.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) expanded = [] recursive_expand(sum([iteration], []), expanded) # Where do I insert it! self.parent.insert_children(self, expanded) self.items.insert(loop_index, iteration) def append_item(self, item): self.insert_item(len(self.items), item) 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[:] # if self._dirty else [] old_iter_data = self._iter_data # if self._dirty else {} 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._expanded = expanded self.parent.insert_children(self, expanded) self.items = new_items self._iter_data = new_iter_data class ItemViewLooper(Looper): """A looper that only creates the objects in the visible window. """ item_view = d_(Instance(AbstractItemView)) # -------------------------------------------------------------------------- # Observers # -------------------------------------------------------------------------- @observe("item_view.iterable") def _observe_iterable(self, change): super(ItemViewLooper, self)._observe_iterable(change) def _observe_item_view(self, change): """A private observer for the `window_size` attribute. If the iterable changes while the looper is active, the loop items will be refreshed. """ self.iterable = self.item_view.iterable @observe( "item_view.iterable_index", "item_view.iterable_fetch_size", "item_view.iterable_prefetch", ) def _refresh_window(self, change): if change["type"] == "update" and self.is_initialized: self.refresh_items() @observe("item_view.visible_rect") def _prefetch_items(self, change): """When the current_row in the model changes (whether from scrolling) or set by the application. Make sure the results are loaded! """ if self.is_initialized: view = self.item_view upper_limit = ( view.iterable_index + view.iterable_fetch_size - view.iterable_prefetch ) lower_limit = max(0, view.iterable_index + view.iterable_prefetch) offset = int(view.iterable_fetch_size / 2.0) upper_visible_row = view.visible_rect[2] lower_visible_row = view.visible_rect[0] print("Visible rect = %s" % view.visible_rect) if upper_visible_row >= upper_limit: next_index = max(0, upper_visible_row - offset) # Center on current row # Going up works... if next_index > view.iterable_index: print("Auto prefetch upper limit %s!" % upper_limit) view.iterable_index = next_index # view.model().reset() # But doewn doesnt? elif view.iterable_index > 0 and lower_visible_row < lower_limit: next_index = max(0, lower_visible_row - offset) # Center on current row # Going down works if next_index < view.iterable_index: print( "Auto prefetch lower limit=%s, iterable=%s, setting next=%s!" % (lower_limit, view.iterable_index, next_index) ) view.iterable_index = next_index # view.model().reset() @property def windowed_iterable(self): """That returns only the window""" # Seek to offset effective_offset = max(0, self.item_view.iterable_index) for i, item in enumerate(self.iterable): if i < effective_offset: continue elif i >= (effective_offset + self.item_view.iterable_fetch_size): return yield item 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[:] # if self._dirty else [] old_iter_data = self._iter_data # if self._dirty else {} iterable = self.windowed_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) # Add to old items list # self.old_items.extend(old_items) # if self._dirty: 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 # if self._dirty else new_items+old_items self._iter_data = new_iter_data enamlx-0.6.4/enamlx/qt/000077500000000000000000000000001456613731400147275ustar00rootroot00000000000000enamlx-0.6.4/enamlx/qt/__init__.py000066400000000000000000000000001456613731400170260ustar00rootroot00000000000000enamlx-0.6.4/enamlx/qt/qt_abstract_item.py000066400000000000000000000051071456613731400206310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 24, 2015 """ from atom.api import Bool, ForwardInstance, Instance from enaml.core.pattern import Pattern from enaml.qt.qt_control import QtControl from enaml.qt.qt_menu import QtMenu from enaml.qt.qt_widget import QtWidget from qtpy.QtCore import QModelIndex, Qt from qtpy.QtWidgets import QHeaderView from enamlx.widgets.abstract_item import ( ProxyAbstractWidgetItem, ProxyAbstractWidgetItemGroup, ) TEXT_H_ALIGNMENTS = { "left": Qt.AlignLeft, "right": Qt.AlignRight, "center": Qt.AlignHCenter, "justify": Qt.AlignJustify, } TEXT_V_ALIGNMENTS = { "top": Qt.AlignTop, "bottom": Qt.AlignBottom, "center": Qt.AlignVCenter, } RESIZE_MODES = { "interactive": QHeaderView.Interactive, "fixed": QHeaderView.Fixed, "stretch": QHeaderView.Stretch, "resize_to_contents": QHeaderView.ResizeToContents, "custom": QHeaderView.Custom, } class AbstractQtWidgetItemGroup(QtControl, ProxyAbstractWidgetItemGroup): """Base class for Table and Tree Views""" #: Context menu for this group menu = Instance(QtMenu) def init_layout(self): for child in self.children(): if isinstance(child, QtMenu): self.menu = child def refresh_style_sheet(self): pass # Takes a lot of time def _abstract_item_view(): from .qt_abstract_item_view import QtAbstractItemView return QtAbstractItemView class AbstractQtWidgetItem(AbstractQtWidgetItemGroup, ProxyAbstractWidgetItem): #: is_destroyed = Bool() #: Index within the view index = Instance(QModelIndex) #: Delegate widget to display when editing the cell #: if the widget is editable delegate = Instance(QtWidget) #: Reference to view view = ForwardInstance(_abstract_item_view) def create_widget(self): # View items have no widget! for child in self.children(): if isinstance(child, (Pattern, QtWidget)): self.delegate = child def init_widget(self): pass def init_layout(self): super(AbstractQtWidgetItem, self).init_layout() self._update_index() def _update_index(self): """Update where this item is within the model""" raise NotImplementedError def destroy(self): """Set the flag so we know when this item is destroyed""" self.is_destroyed = True super(AbstractQtWidgetItem, self).destroy() enamlx-0.6.4/enamlx/qt/qt_abstract_item_view.py000066400000000000000000000360041456613731400216630ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 20, 2015 """ from atom.api import Instance, Int from enaml.application import timed_call from enaml.qt.q_resource_helpers import get_cached_qcolor, get_cached_qicon from enaml.qt.qt_control import QtControl from qtpy.QtCore import QAbstractItemModel, QItemSelectionModel, Qt from qtpy.QtWidgets import QAbstractItemView from enamlx.qt.qt_abstract_item import TEXT_H_ALIGNMENTS, TEXT_V_ALIGNMENTS from enamlx.widgets.abstract_item_view import ProxyAbstractItemView SELECTION_MODES = { "extended": QAbstractItemView.ExtendedSelection, "single": QAbstractItemView.SingleSelection, "contiguous": QAbstractItemView.ContiguousSelection, "multi": QAbstractItemView.MultiSelection, "none": QAbstractItemView.NoSelection, } SELECTION_BEHAVIORS = { "items": QAbstractItemView.SelectItems, "rows": QAbstractItemView.SelectRows, "columns": QAbstractItemView.SelectColumns, } class QAbstractAtomItemModel(object): """A mixin for an ItemModel""" declaration = None def setDeclaration(self, declaration): """Set the declaration this model will use for rendering the the headers. """ assert isinstance(declaration.proxy, ProxyAbstractItemView), ( "The model declaration must be a QtAbstractItemView subclass. " "Got {}".format(declaration) ) self.declaration = declaration def data(self, index, role): """Retrieve the data for the item at the given index""" item = self.itemAt(index) if not item: return None d = item.declaration if role == Qt.DisplayRole: return d.text elif role == Qt.ToolTipRole: return d.tool_tip elif role == Qt.CheckStateRole and d.checkable: return d.checked and Qt.Checked or Qt.Unchecked elif role == Qt.DecorationRole and d.icon: return get_cached_qicon(d.icon) elif role == Qt.EditRole and d.editable: return d.text elif role == Qt.StatusTipRole: return d.status_tip elif role == Qt.TextAlignmentRole: h, v = d.text_alignment return TEXT_H_ALIGNMENTS[h] | TEXT_V_ALIGNMENTS[v] elif role == Qt.ForegroundRole and d.foreground: return get_cached_qcolor(d.foreground) elif role == Qt.BackgroundRole and d.background: return get_cached_qcolor(d.background) # elif role == Qt.SizeHintRole and d.minimum_size: # return d.minimum_size return None def itemAt(self, index): """Get the item at the given model index. Returns ------- item: """ raise NotImplementedError def flags(self, index): item = self.itemAt(index) if not item: return Qt.NoItemFlags d = item.declaration flags = Qt.ItemIsEnabled if d.editable: flags |= Qt.ItemIsEditable if d.checkable: flags |= Qt.ItemIsUserCheckable if d.selectable: flags |= Qt.ItemIsSelectable return flags def setData(self, index, value, role=Qt.EditRole): """Set the data for the item at the given index to the given value.""" item = self.itemAt(index) if not item: return False d = item.declaration if role == Qt.CheckStateRole: checked = Qt.CheckState(value) == Qt.CheckState.Checked if checked != d.checked: d.checked = checked d.toggled(checked) return True elif role == Qt.EditRole: if value != d.text: d.text = value return True return super(QAbstractAtomItemModel, self).setData(index, value, role) def headerData(self, index, orientation, role): """QHeaderView respects the following item data roles: TextAlignmentRole, DisplayRole, FontRole, DecorationRole, ForegroundRole, BackgroundRole. """ d = self.declaration if orientation == Qt.Horizontal and role == Qt.DisplayRole: try: return d.horizontal_headers[index] if d.horizontal_headers else index except IndexError: return index elif orientation == Qt.Vertical and role == Qt.DisplayRole: try: return d.vertical_headers[index] if d.vertical_headers else index except IndexError: return index return None def clear(self): self.beginResetModel() d = self.declaration if d.items: d.items = [] self.endResetModel() class QtAbstractItemView(QtControl, ProxyAbstractItemView): #: Reference to the view widget = Instance(QAbstractItemView) #: View model model = Instance(QAbstractItemModel) #: Hold reference to selection model to PySide segfault selection_model = Instance(QItemSelectionModel) #: Refreshing the view on every update makes it really slow #: So if we defer refreshing until everything is added it's fast :) _pending_timeout = Int(100) _pending_view_refreshes = Int(0) _pending_row_refreshes = Int(0) _pending_column_refreshes = Int(0) def init_widget(self): super(QtAbstractItemView, self).init_widget() self.init_model() d = self.declaration #: Enable context menus self.widget.setContextMenuPolicy(Qt.CustomContextMenu) self.set_selection_mode(d.selection_mode) self.set_selection_behavior(d.selection_behavior) self.set_alternating_row_colors(d.alternating_row_colors) self.set_word_wrap(d.word_wrap) self.set_resize_mode(d.resize_mode) self.set_show_vertical_header(d.show_vertical_header) self.set_show_horizontal_header(d.show_horizontal_header) self.set_horizontal_stretch(d.horizontal_stretch) self.set_vertical_stretch(d.vertical_stretch) if d.cell_padding: self.set_cell_padding(d.cell_padding) if d.vertical_minimum_section_size: self.set_vertical_minimum_section_size(d.vertical_minimum_section_size) if d.vertical_sizes: self.set_vertical_sizes(d.vertical_sizes) if d.horizontal_minimum_section_size: self.set_horizontal_minimum_section_size(d.horizontal_minimum_section_size) if d.horizontal_sizes: self.set_horizontal_sizes(d.horizontal_sizes) self.init_signals() def init_model(self): raise NotImplementedError def init_signals(self): """Connect signals""" self.widget.activated.connect(self.on_item_activated) self.widget.clicked.connect(self.on_item_clicked) self.widget.doubleClicked.connect(self.on_item_double_clicked) self.widget.entered.connect(self.on_item_entered) self.widget.pressed.connect(self.on_item_pressed) self.widget.customContextMenuRequested.connect( self.on_custom_context_menu_requested ) self.selection_model = self.widget.selectionModel() self.selection_model.selectionChanged.connect(self.on_selection_changed) self.widget.horizontalScrollBar().valueChanged.connect( self.on_horizontal_scrollbar_moved ) self.widget.verticalScrollBar().valueChanged.connect( self.on_vertical_scrollbar_moved ) def item_at(self, index): return self.model.itemAt(index) def destroy(self): """Make sure all the table widgets are destroyed first.""" self.model.clear() super(QtAbstractItemView, self).destroy() # ------------------------------------------------------------------------- # Widget Setters # ------------------------------------------------------------------------- def set_selection_mode(self, mode): self.widget.setSelectionMode(SELECTION_MODES[mode]) def set_selection_behavior(self, behavior): self.widget.setSelectionBehavior(SELECTION_BEHAVIORS[behavior]) def set_scroll_to_bottom(self, enabled): if enabled: self.widget.scrollToBottom() def set_alternating_row_colors(self, enabled): self.widget.setAlternatingRowColors(enabled) def set_sortable(self, sortable): self.widget.setSortingEnabled(sortable) def set_word_wrap(self, wrap): self.widget.setWordWrap(wrap) def set_auto_resize_columns(self, enabled): if enabled: self.widget.resizeColumnsToContents() def set_visible_row(self, row): self.widget.verticalScrollBar().setValue(row) def set_visible_column(self, column): self.widget.horizontalScrollBar().setValue(column) def set_model(self, model): if isinstance(model, QAbstractAtomItemModel): model.setDeclaration(self.declaration) self.widget.setModel(model) self.model = self.widget.model() def set_items(self, items): """Defer until later so the view is only updated after all items are added. """ self.model.beginResetModel() self._pending_view_refreshes += 1 timed_call(self._pending_timeout, self._refresh_layout) self.model.endResetModel() def set_selection(self, items): pass def set_horizontal_sizes(self, sizes): header = self.widget.horizontalHeader() for i, s in enumerate(sizes): if s is not None and s >= 0: header.resizeSection(i, s) def set_vertical_sizes(self, sizes): header = self.widget.verticalHeader() for i, s in enumerate(sizes): if s is not None and s >= 0: header.resizeSection(i, s) # ------------------------------------------------------------------------- # Widget Events # ------------------------------------------------------------------------- def on_horizontal_scrollbar_moved(self, value): """When the scrollbar moves, queue a refresh of the visible columns. This makes it only update the view when needed making scrolling much smoother. """ self._pending_column_refreshes += 1 timed_call(0, self._refresh_visible_column, value) def on_vertical_scrollbar_moved(self, value): """When the scrollbar moves, queue a refresh of the visible rows. This makes it only update the view when needed making scrolling much smoother. """ self._pending_row_refreshes += 1 timed_call(0, self._refresh_visible_row, value) def on_item_activated(self, index): item = self.item_at(index) if not item: return parent = item.parent() if parent != self: parent.declaration.activated() item.declaration.activated() def on_item_clicked(self, index): item = self.item_at(index) if not item: return parent = item.parent() if parent != self: parent.declaration.clicked() item.declaration.clicked() def on_item_double_clicked(self, index): item = self.item_at(index) if not item: return parent = item.parent() if parent != self: parent.declaration.double_clicked() item.declaration.double_clicked() def on_item_pressed(self, index): item = self.item_at(index) if not item: return parent = item.parent() if parent != self: parent.declaration.pressed() item.declaration.pressed() def on_item_entered(self, index): item = self.item_at(index) if not item: return parent = item.parent() if parent != self: parent.declaration.entered() item.declaration.entered() def on_selection_changed(self, selected, deselected): selection = self.declaration.selection[:] for index in selected.indexes(): item = self.item_at(index) if not item: continue d = item.declaration selection.append(d) if not d.selected: d.selected = True d.selection_changed(d.selected) for index in deselected.indexes(): item = self.item_at(index) if not item: continue d = item.declaration try: selection.remove(d) except ValueError: pass if d.selected: d.selected = False d.selection_changed(d.selected) self.declaration.selection = selection def on_custom_context_menu_requested(self, pos): item = self.item_at(self.widget.indexAt(pos)) if not item: return if item.menu: item.menu.popup() return parent = item.parent() if parent and hasattr(parent, "menu") and parent.menu: parent.menu.popup() def on_layout_refreshed(self): pass # ------------------------------------------------------------------------- # View refresh handlers # ------------------------------------------------------------------------- def _refresh_layout(self): """This queues and batches model changes so that the layout is only refreshed after the `_pending_timeout` expires. This prevents the UI from refreshing when inserting or removing a large number of items until the operation is complete. """ self._pending_view_refreshes -= 1 if self._pending_view_refreshes == 0: try: self.model.layoutChanged.emit() self.on_layout_refreshed() except RuntimeError: # View can be destroyed before we get here return self._refresh_sizes() def _refresh_sizes(self): """Refresh column sizes when the data changes.""" pass # header = self.widget.horizontalHeader() # # for i,item in enumerate(self.items[0]): # header.setResizeMode(i,RESIZE_MODES[item.declaration.resize_mode]) # if item.declaration.width: # header.resizeSection(i,item.declaration.width) def _refresh_visible_row(self, value): """Subclasses must implement this method to refresh the content in the row with the content at the given index. Parameters ---------- value: int Index of the row that needs to be refreshed """ raise NotImplementedError def _refresh_visible_column(self, value): """Subclasses must implement this method to refresh the content in the column with the content at the given index. Parameters ---------- value: int Index of the column that needs to be refreshed """ raise NotImplementedError enamlx-0.6.4/enamlx/qt/qt_double_spin_box.py000066400000000000000000000025461456613731400211670ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 29, 2015 """ from atom.api import Typed from enaml.qt.qt_spin_box import QtSpinBox from qtpy.QtWidgets import QDoubleSpinBox from enamlx.widgets.double_spin_box import ProxyDoubleSpinBox class QtDoubleSpinBox(QtSpinBox, ProxyDoubleSpinBox): """A Qt implementation of an Enaml SpinBox.""" #: A reference to the widget created by the proxy. widget = Typed(QDoubleSpinBox) # ------------------------------------------------------------------------- # Initialization API # ------------------------------------------------------------------------- def create_widget(self): """Create the underlying QDoubleSpinBox widget.""" widget = QDoubleSpinBox(self.parent_widget()) widget.setKeyboardTracking(False) self.widget = widget def init_widget(self): self.set_decimals(self.declaration.decimals) super(QtDoubleSpinBox, self).init_widget() # ------------------------------------------------------------------------- # ProxyDoubleSpinBox API # ------------------------------------------------------------------------- def set_decimals(self, prec): self.widget.setDecimals(prec) enamlx-0.6.4/enamlx/qt/qt_factories.py000066400000000000000000000131741456613731400177720ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 23, 2015 """ from enaml.qt import qt_factories def double_spin_box_factory(): from .qt_double_spin_box import QtDoubleSpinBox return QtDoubleSpinBox def graphics_view_factory(): from .qt_graphics_view import QtGraphicsView return QtGraphicsView def graphics_item_factory(): from .qt_graphics_view import QtGraphicsItem return QtGraphicsItem def graphics_item_group_factory(): from .qt_graphics_view import QtGraphicsItemGroup return QtGraphicsItemGroup def graphics_ellipse_item_factory(): from .qt_graphics_view import QtGraphicsEllipseItem return QtGraphicsEllipseItem def graphics_image_item_factory(): from .qt_graphics_view import QtGraphicsImageItem return QtGraphicsImageItem def graphics_line_item_factory(): from .qt_graphics_view import QtGraphicsLineItem return QtGraphicsLineItem def graphics_path_item_factory(): from .qt_graphics_view import QtGraphicsPathItem return QtGraphicsPathItem def graphics_polygon_item_factory(): from .qt_graphics_view import QtGraphicsPolygonItem return QtGraphicsPolygonItem def graphics_rect_item_factory(): from .qt_graphics_view import QtGraphicsRectItem return QtGraphicsRectItem def graphics_text_item_factory(): from .qt_graphics_view import QtGraphicsTextItem return QtGraphicsTextItem def graphics_widget_factory(): from .qt_graphics_view import QtGraphicsWidget return QtGraphicsWidget def key_event_factory(): from .qt_key_event import QtKeyEvent return QtKeyEvent def plot_area_factory(): from .qt_plot_area import QtPlotArea return QtPlotArea def plot_item_2d_factory(): from .qt_plot_area import QtPlotItem2D return QtPlotItem2D def plot_item_3d_factory(): from .qt_plot_area import QtPlotItem3D return QtPlotItem3D def plot_item_array_factory(): from .qt_plot_area import QtPlotItemArray return QtPlotItemArray def plot_item_array_3d_factory(): from .qt_plot_area import QtPlotItemArray3D return QtPlotItemArray3D def plot_item_list_factory(): from .qt_plot_area import QtPlotItemList return QtPlotItemList def plot_item_dict_factory(): from .qt_plot_area import QtPlotItemDict return QtPlotItemDict def table_view_factory(): from .qt_table_view import QtTableView return QtTableView def table_view_item_factory(): from .qt_table_view import QtTableViewItem return QtTableViewItem def table_view_row_factory(): from .qt_table_view import QtTableViewRow return QtTableViewRow def table_view_col_factory(): from .qt_table_view import QtTableViewColumn return QtTableViewColumn def table_widget_factory(): from .qt_table_widget import QtTableWidget return QtTableWidget def table_widget_item_factory(): from .qt_table_widget import QtTableWidgetItem return QtTableWidgetItem def table_widget_row_factory(): from .qt_table_widget import QtTableWidgetRow return QtTableWidgetRow def table_widget_col_factory(): from .qt_table_widget import QtTableWidgetColumn return QtTableWidgetColumn def tree_view_factory(): from .qt_tree_view import QtTreeView return QtTreeView def tree_view_item_factory(): from .qt_tree_view import QtTreeViewItem return QtTreeViewItem def tree_view_col_factory(): from .qt_tree_view import QtTreeViewColumn return QtTreeViewColumn def tree_widget_factory(): from .qt_tree_widget import QtTreeWidget return QtTreeWidget def tree_widget_item_factory(): from .qt_tree_widget import QtTreeWidgetItem return QtTreeWidgetItem def tree_widget_col_factory(): from .qt_tree_widget import QtTreeWidgetColumn return QtTreeWidgetColumn # Inject the factory qt_factories.QT_FACTORIES.update( { "DoubleSpinBox": double_spin_box_factory, "GraphicsView": graphics_view_factory, "GraphicsItem": graphics_item_factory, "GraphicsItemGroup": graphics_item_group_factory, "GraphicsEllipseItem": graphics_ellipse_item_factory, "GraphicsLineItem": graphics_line_item_factory, "GraphicsPathItem": graphics_path_item_factory, "GraphicsPolygonItem": graphics_polygon_item_factory, "GraphicsRectItem": graphics_rect_item_factory, "GraphicsTextItem": graphics_text_item_factory, "GraphicsImageItem": graphics_image_item_factory, "GraphicsWidget": graphics_widget_factory, "KeyEvent": key_event_factory, "PlotArea": plot_area_factory, "PlotItem2D": plot_item_2d_factory, "PlotItem3D": plot_item_3d_factory, "PlotItemArray": plot_item_array_factory, "PlotItemArray3D": plot_item_array_3d_factory, "PlotItemList": plot_item_list_factory, "PlotItemDict": plot_item_dict_factory, "TableView": table_view_factory, "TableViewItem": table_view_item_factory, "TableViewRow": table_view_row_factory, "TableViewColumn": table_view_col_factory, "TableWidget": table_widget_factory, "TableWidgetItem": table_widget_item_factory, "TableWidgetRow": table_widget_row_factory, "TableWidgetColumn": table_widget_col_factory, "TreeView": tree_view_factory, "TreeViewItem": tree_view_item_factory, "TreeViewColumn": tree_view_col_factory, "TreeWidget": tree_widget_factory, "TreeWidgetItem": tree_widget_item_factory, "TreeWidgetColumn": tree_widget_col_factory, } ) enamlx-0.6.4/enamlx/qt/qt_graphics_view.py000066400000000000000000001126261456613731400206470ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2018, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Sept 4, 2018 """ import warnings from atom.api import Atom, Coerced, Int, Typed, atomref from enaml.drag_drop import DropAction from enaml.qt.q_resource_helpers import ( get_cached_qcolor, get_cached_qfont, get_cached_qimage, ) from enaml.qt.qt_control import QtControl from enaml.qt.qt_drag_drop import QtDropEvent from enaml.qt.qt_toolkit_object import QtToolkitObject from enaml.qt.qt_widget import QtWidget, focus_registry from enaml.qt.QtCore import QPoint, QPointF, QRectF, Qt from enaml.qt.QtGui import ( QBrush, QColor, QCursor, QDrag, QPainter, QPen, QPixmap, QPolygonF, ) from enaml.qt.QtWidgets import ( QFrame, QGraphicsEllipseItem, QGraphicsItem, QGraphicsItemGroup, QGraphicsLineItem, QGraphicsObject, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsPolygonItem, QGraphicsProxyWidget, QGraphicsRectItem, QGraphicsScene, QGraphicsSimpleTextItem, QGraphicsView, QWidgetAction, ) from enaml.widgets.widget import Feature from enamlx.widgets.graphics_view import ( GraphicFeature, Point, ProxyAbstractGraphicsShapeItem, ProxyGraphicsEllipseItem, ProxyGraphicsImageItem, ProxyGraphicsItem, ProxyGraphicsItemGroup, ProxyGraphicsLineItem, ProxyGraphicsPathItem, ProxyGraphicsPolygonItem, ProxyGraphicsRectItem, ProxyGraphicsTextItem, ProxyGraphicsView, ProxyGraphicsWidget, ) PEN_STYLES = { "none": Qt.NoPen, "solid": Qt.SolidLine, "dash": Qt.DashLine, "dot": Qt.DotLine, "dash_dot": Qt.DashDotLine, "dash_dot_dot": Qt.DashDotDotLine, "custom": Qt.CustomDashLine, } CAP_STYLES = { "square": Qt.SquareCap, "flat": Qt.FlatCap, "round": Qt.RoundCap, } JOIN_STYLES = { "bevel": Qt.BevelJoin, "miter": Qt.MiterJoin, "round": Qt.RoundJoin, } BRUSH_STYLES = { "solid": Qt.SolidPattern, "dense1": Qt.Dense1Pattern, "dense2": Qt.Dense2Pattern, "dense3": Qt.Dense3Pattern, "dense4": Qt.Dense4Pattern, "dense5": Qt.Dense5Pattern, "dense6": Qt.Dense6Pattern, "dense7": Qt.Dense7Pattern, "horizontal": Qt.HorPattern, "vertical": Qt.VerPattern, "cross": Qt.CrossPattern, "bdiag": Qt.BDiagPattern, "fdiag": Qt.FDiagPattern, "diag": Qt.DiagCrossPattern, "linear": Qt.LinearGradientPattern, "radial": Qt.RadialGradientPattern, "conical": Qt.ConicalGradientPattern, "texture": Qt.TexturePattern, "none": Qt.NoBrush, } DRAG_MODES = { "none": QGraphicsView.NoDrag, "scroll": QGraphicsView.ScrollHandDrag, "selection": QGraphicsView.RubberBandDrag, } # -------------------------------------------------------------------------- # Qt Resource Helpers # -------------------------------------------------------------------------- def QPen_from_Pen(pen): qpen = QPen() if pen.color: qpen.setColor(get_cached_qcolor(pen.color)) qpen.setWidth(int(pen.width)) qpen.setStyle(PEN_STYLES.get(pen.line_style)) qpen.setCapStyle(CAP_STYLES.get(pen.cap_style)) qpen.setJoinStyle(JOIN_STYLES.get(pen.join_style)) if pen.line_style == "custom": qpen.setDashPattern(*pen.dash_pattern) return qpen def QBrush_from_Brush(brush): qbrush = QBrush() if brush.color: qbrush.setColor(get_cached_qcolor(brush.color)) if brush.image: qbrush.setTextureImage(get_cached_qimage(brush.image)) qbrush.setStyle(Qt.TexturePattern) else: qbrush.setStyle(BRUSH_STYLES.get(brush.style)) return qbrush def get_cached_qpen(pen): qpen = pen._tkdata if not isinstance(qpen, QPen): qpen = pen._tkdata = QPen_from_Pen(pen) return qpen def get_cached_qbrush(brush): qbrush = brush._tkdata if not isinstance(qbrush, QBrush): qbrush = brush._tkdata = QBrush_from_Brush(brush) return qbrush # -------------------------------------------------------------------------- # Mixin classes # -------------------------------------------------------------------------- class FeatureMixin(Atom): """A mixin that provides focus and mouse features.""" #: 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, (0,)) _extra_features = Coerced(GraphicFeature, (0,)) #: Internal storage for the shared widget action. _widget_action = Typed(QWidgetAction) #: Internal storage for the drag origin position. _drag_origin = Typed(QPointF) # -------------------------------------------------------------------------- # 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() features = self._extra_features if features & GraphicFeature.WheelEvent: self.hook_wheel() if features & GraphicFeature.DrawEvent: self.hook_draw() 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() features = self._extra_features if features & GraphicFeature.WheelEvent: self.unhook_wheel() if features & GraphicFeature.DrawEvent: self.unhook_draw() # -------------------------------------------------------------------------- # Protected API # -------------------------------------------------------------------------- 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(event.widget()) # self._drag_origin = None # return # Don't returns 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, widget): """Perform the drag operation for the widget. Parameters ---------- widget: QWidget A reference to the viewport 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(self.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)) def hook_wheel(self): """Install the hooks for wheel events.""" widget = self.widget widget.wheelEvent = self.wheelEvent def unhook_wheel(self): """Removes the hooks for wheel events.""" widget = self.widget del widget.wheelEvent def wheelEvent(self, event): """Handle the mouse wheel event for the widget.""" self.declaration.wheel_event(event) def hook_draw(self): """Remove the hooks for the draw (paint) event. This method may be overridden by subclasses as needed. """ widget = self.widget widget.paint = self.draw def unhook_draw(self): """Remove the hooks for the draw (paint) event. This method may be overridden by subclasses as needed. """ widget = self.widget del widget.paint def draw(self, painter, options, widget): """Handle the draw event for the widget.""" self.declaration.draw(painter, options, widget) # -------------------------------------------------------------------------- # 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 # -------------------------------------------------------------------------- # Toolkit implementations # -------------------------------------------------------------------------- class QtGraphicsView(QtControl, ProxyGraphicsView): #: Internal widget widget = Typed(QGraphicsView) #: Internal scene scene = Typed(QGraphicsScene) #: View Range view_range = Typed(QRectF, (0, 0, 1, 1)) #: Custom features _extra_features = Coerced(GraphicFeature, (0,)) #: Cyclic notification guard. This a bitfield of multiple guards. _guards = Int(0) def create_widget(self): self.scene = QGraphicsScene() self.widget = QGraphicsView(self.scene, self.parent_widget()) def init_widget(self): d = self.declaration self._extra_features = d.extra_features super(QtGraphicsView, self).init_widget() widget = self.widget widget.setCacheMode(QGraphicsView.CacheBackground) widget.setFocusPolicy(Qt.StrongFocus) widget.setFrameShape(QFrame.NoFrame) widget.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) widget.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) widget.setResizeAnchor(QGraphicsView.AnchorViewCenter) widget.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) widget.setMouseTracking(True) self.set_drag_mode(d.drag_mode) self.set_renderer(d.renderer) self.set_antialiasing(d.antialiasing) self.scene.selectionChanged.connect(self.on_selection_changed) def init_layout(self): super(QtGraphicsView, self).init_layout() scene = self.scene for item in self.scene_items(): scene.addItem(item) self.set_view_range(self.view_range) def child_added(self, child): if isinstance(child, QtGraphicsItem): self.scene.addItem(child.widget) else: super(QtGraphicsView, self).child_added(child) def child_removed(self, child): if isinstance(child, QtGraphicsItem): self.scene.removeItem(child.widget) else: super(QtGraphicsView, self).child_removed(child) def scene_items(self): for w in self.children(): if isinstance(w, QtGraphicsItem): yield w.widget # -------------------------------------------------------------------------- # GraphicFeature API # -------------------------------------------------------------------------- def _setup_features(self): super(QtGraphicsView, self)._setup_features() features = self._extra_features if features & GraphicFeature.MouseEvent: self.hook_drag() if features & GraphicFeature.WheelEvent: self.hook_wheel() self.hook_resize() def _teardown_features(self): super(QtGraphicsView, self)._teardown_features() features = self._extra_features if features & GraphicFeature.MouseEvent: self.unhook_drag() if features & GraphicFeature.WheelEvent: self.unhook_wheel() self.unhook_resize() def hook_wheel(self): """Install the hooks for wheel events.""" widget = self.widget widget.wheelEvent = self.wheelEvent def unhook_wheel(self): """Removes the hooks for wheel events.""" widget = self.widget del widget.wheelEvent def hook_draw(self): """Install the hooks for background draw events.""" widget = self.widget widget.drawBackground = self.drawBackground def unhook_draw(self): """Removes the hooks for background draw events.""" widget = self.widget del widget.drawBackground def hook_resize(self): """Install the hooks for resize events.""" widget = self.widget widget.resizeEvent = self.resizeEvent def unhook_resize(self): """Removes the hooks for resize events.""" widget = self.widget del widget.resizeEvent # -------------------------------------------------------------------------- # QGraphicView API # -------------------------------------------------------------------------- def wheelEvent(self, event): """Handle the wheel event for the widget.""" self.declaration.wheel_event(event) def drawBackground(self, painter, rect): """Handle the drawBackground request""" self.declaration.draw_background(painter, rect) def mousePressEvent(self, event): """Handle the mouse press event for a drag operation.""" self.declaration.mouse_press_event(event) super(QtGraphicsView, self).mousePressEvent(event) def mouseMoveEvent(self, event): """Handle the mouse move event for a drag operation.""" self.declaration.mouse_move_event(event) super(QtGraphicsView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): """Handle the mouse release event for the drag operation.""" self.declaration.mouse_release_event(event) super(QtGraphicsView, self).mouseReleaseEvent(event) def resizeEvent(self, event): """Resize the view range to match the widget size""" d = self.declaration widget = self.widget if d.auto_range: size = self.widget.size() view_range = QRectF(0, 0, size.width(), size.height()) # view_range = self.scene.itemsBoundingRect() else: # Return the boundaries of the view in scene coordinates r = QRectF(widget.rect()) view_range = widget.viewportTransform().inverted()[0].mapRect(r) self.set_view_range(view_range) # -------------------------------------------------------------------------- # ProxyGraphicsView API # -------------------------------------------------------------------------- def set_view_range(self, view_range): """Set the visible scene rect to override the default behavior of limiting panning and viewing to the graphics items view. Based on Luke Campagnola's updateMatrix of pyqtgraph's GraphicsView """ d = self.declaration self.view_range = view_range widget = self.widget widget.setSceneRect(view_range) if d.auto_range: widget.resetTransform() else: if d.lock_aspect_ratio: flags = Qt.KeepAspectRatio else: flags = Qt.IgnoreAspectRatio widget.fitInView(view_range, flags) def set_drag_mode(self, mode): self.widget.setDragMode(DRAG_MODES.get(mode)) def set_auto_range(self, enabled): self.set_view_range(self.view_range) def set_lock_aspect_ratio(self, locked): self.set_view_range(self.view_range) def set_antialiasing(self, enabled): flags = self.widget.renderHints() if enabled: flags |= QPainter.Antialiasing else: flags &= ~QPainter.Antialiasing self.widget.setRenderHints(flags) def set_renderer(self, renderer): """Set the viewport widget.""" viewport = None if renderer == "opengl": from enaml.qt.QtWidgets import QOpenGLWidget viewport = QOpenGLWidget() elif renderer == "default": try: from enaml.qt.QtWidgets import QOpenGLWidget viewport = QOpenGLWidget() except ImportError as e: warnings.warn("QOpenGLWidget could not be imported: {}".format(e)) self.widget.setViewport(viewport) def set_selected_items(self, items): if self._guards & 0x01: return self.scene.clearSelection() for item in items: item.selected = True def on_selection_changed(self): """Callback invoked one the selection has changed.""" d = self.declaration selection = self.scene.selectedItems() self._guards |= 0x01 try: d.selected_items = [ item.ref().declaration for item in selection if item.ref() ] finally: self._guards &= ~0x01 def set_background(self, background): """Set the background color of the widget.""" scene = self.scene scene.setBackgroundBrush(QColor.fromRgba(background.argb)) def get_item_at(self, point): item = self.scene.getItemAt(point.x, point.y) if item and item.ref(): return item.ref().declaration def get_items_at(self, point): items = self.scene.items(point.x, point.y) return [item.ref().declaration for item in items if item.ref()] def get_items_in(self, top_left, bottom_right): qrect = QRectF( QPointF(top_left.x, top_left.y), QPointF(bottom_right.x, bottom_right.y) ) items = self.scene.items(qrect) return [item.ref().declaration for item in items if item.ref()] def fit_in_view(self, item): self.widget.fitInView(item.proxy.widget) def center_on(self, item): if isinstance(item, Point): self.widget.centerOn(item.x, item.y) else: self.widget.centerOn(item.proxy.widget) def reset_view(self): self.widget.resetTransform() def translate_view(self, x, y): view_range = self.view_range.adjusted(x, y, x, y) self.set_view_range(view_range) return view_range def scale_view(self, x, y): """Scale the zoom but keep in in the min and max zoom bounds.""" d = self.declaration sx, sy = float(x), float(y) if d.lock_aspect_ratio: sy = sx view_range = self.view_range center = view_range.center() w, h = view_range.width() / sx, view_range.height() / sy cx, cy = center.x(), center.y() x = cx - (cx - view_range.left()) / sx y = cy - (cy - view_range.top()) / sy view_range = QRectF(x, y, w, h) self.set_view_range(view_range) return view_range def rotate_view(self, angle): self.widget.rotate(angle) def map_from_scene(self, point): qpoint = self.widget.mapFromScene(point.x, point.y) return Point(qpoint.x(), qpoint.y()) def map_to_scene(self, point): qpoint = self.widget.mapToScene(point.x, point.y) return Point(qpoint.x(), qpoint.y()) def pixel_density(self): tr = self.widget.transform().inverted()[0] p = tr.map(QPoint(1, 1)) - tr.map(QPoint(0, 0)) return Point(p.x(), p.y()) class QtGraphicsItem(QtToolkitObject, ProxyGraphicsItem, FeatureMixin): """QtGraphicsItem is essentially a copy of QtWidget except that it uses `self.widget`, `create_widget` and `init_widget` instead of widget. """ #: Internal item widget = Typed(QGraphicsObject) #: Cyclic notification guard. This a bitfield of multiple guards. #: 0x01 is position _guards = Int(0) # -------------------------------------------------------------------------- # Initialization API # -------------------------------------------------------------------------- def create_widget(self): self.widget = QGraphicsObject(self.parent_widget()) def init_widget(self): widget = self.widget # Save a reference so we can retrieve the QtGraphicsItem # from the QGraphicsItem widget.ref = atomref(self) focus_registry.register(widget, self) d = self.declaration self._extra_features = d.extra_features self._setup_features() if d.selectable: self.set_selectable(d.selectable) if d.movable: self.set_movable(d.movable) 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) if not d.visible: self.set_visible(d.visible) if d.opacity != 1: self.set_opacity(d.opacity) if d.rotation: self.set_rotation(d.rotation) if d.scale != 1: self.set_scale(d.scale) self.set_position(d.position) self.hook_item_change() def init_layout(self): pass # -------------------------------------------------------------------------- # ProxyToolkitObject API # -------------------------------------------------------------------------- def destroy(self): """Destroy the underlying QWidget object.""" self._teardown_features() focus_registry.unregister(self.widget) widget = self.widget if widget is not None: del self.widget super(QtGraphicsItem, 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 def parent_widget(self): """Reimplemented to only return GraphicsItems""" parent = self.parent() if parent is not None and isinstance(parent, QtGraphicsItem): return parent.widget def _teardown_features(self): super(QtGraphicsItem, self)._teardown_features() self.unhook_item_change() def hook_item_change(self): widget = self.widget widget.itemChange = self.itemChange def unhook_item_change(self): del self.widget.itemChange # -------------------------------------------------------------------------- # Signals # -------------------------------------------------------------------------- def itemChange(self, change, value): widget = self.widget if change == QGraphicsItem.ItemPositionHasChanged: pos = widget.pos() pos = Point(pos.x(), pos.y(), widget.zValue()) self._guards |= 0x01 try: self.declaration.position = pos finally: self._guards &= ~0x01 elif change == QGraphicsItem.ItemSelectedChange: self.declaration.selected = widget.isSelected() return type(widget).itemChange(widget, change, value) # -------------------------------------------------------------------------- # ProxyGraphicsItem API # -------------------------------------------------------------------------- def set_visible(self, visible): self.widget.setVisible(visible) def set_enabled(self, enabled): self.widget.setEnabled(enabled) def set_selectable(self, enabled): self.widget.setFlag(QGraphicsItem.ItemIsSelectable, enabled) def set_movable(self, enabled): self.widget.setFlag(QGraphicsItem.ItemIsMovable, enabled) def set_x(self, x): if self._guards & 0x01: return self.widget.setX(x) def set_y(self, y): if self._guards & 0x01: return self.widget.setY(y) def set_z(self, z): if self._guards & 0x01: return self.widget.setZValue(z) def set_position(self, position): if self._guards & 0x01: return pos = self.declaration.position w = self.widget w.setPos(pos.x, pos.y) w.setZValue(pos.z) def set_rotation(self, rotation): self.widget.setRotation(rotation) def set_scale(self, scale): self.widget.setScale(scale) def set_opacity(self, opacity): self.widget.setOpacity(opacity) def set_selected(self, selected): self.widget.setSelected(selected) def set_tool_tip(self, tool_tip): self.widget.setToolTip(tool_tip) def set_status_tip(self, status_tip): self.widget.setToolTip(status_tip) class QtGraphicsItemGroup(QtGraphicsItem, ProxyGraphicsItemGroup): #: Internal widget widget = Typed(QGraphicsItemGroup) def create_widget(self): self.widget = QGraphicsItemGroup(self.parent_widget()) def init_layout(self): super(QtGraphicsItemGroup, self).init_layout() widget = self.widget for item in self.child_widgets(): widget.addToGroup(item) def child_added(self, child): super(QtGraphicsItemGroup, self).child_added(child) if isinstance(child, QtGraphicsItem): self.widget.addToGroup(child.widget) def child_removed(self, child): super(QtGraphicsItemGroup, self).child_removed(child) if isinstance(child, QtGraphicsItem): self.widget.removeFromGroup(child.widget) class QtGraphicsWidget(QtGraphicsItem, ProxyGraphicsWidget): #: Internal widget widget = Typed(QGraphicsProxyWidget) def create_widget(self): """Deferred to the layout pass""" pass def init_widget(self): pass def init_layout(self): """Create the widget in the layout pass after the child widget has been created and intialized. We do this so the child widget does not attempt to use this proxy widget as its parent and because repositioning must be done after the widget is set. """ self.widget = QGraphicsProxyWidget(self.parent_widget()) widget = self.widget for item in self.child_widgets(): widget.setWidget(item) break super(QtGraphicsWidget, self).init_widget() super(QtGraphicsWidget, self).init_layout() def child_added(self, child): super(QtGraphicsItemGroup, self).child_added(child) if isinstance(child, QtWidget): self.widget.setWidget(child.widget) class QtAbstractGraphicsShapeItem(QtGraphicsItem, ProxyAbstractGraphicsShapeItem): def init_widget(self): super(QtAbstractGraphicsShapeItem, self).init_widget() d = self.declaration if d.pen: self.set_pen(d.pen) if d.brush: self.set_brush(d.brush) def set_pen(self, pen): if pen: self.widget.setPen(get_cached_qpen(pen)) else: self.widget.setPen(QPen()) def set_brush(self, brush): if brush: self.widget.setBrush(get_cached_qbrush(brush)) else: self.widget.setBrush(QBrush()) class QtGraphicsLineItem(QtAbstractGraphicsShapeItem, ProxyGraphicsLineItem): #: Internal widget widget = Typed(QGraphicsLineItem) def create_widget(self): self.widget = QGraphicsLineItem(self.parent_widget()) def set_position(self, position): self.set_point(self.declaration.point) def set_point(self, point): pos = self.declaration.position self.widget.setLine(pos.x, pos.y, *point[:2]) class QtGraphicsEllipseItem(QtAbstractGraphicsShapeItem, ProxyGraphicsEllipseItem): #: Internal widget widget = Typed(QGraphicsEllipseItem) def create_widget(self): self.widget = QGraphicsEllipseItem(self.parent_widget()) def init_widget(self): super(QtGraphicsEllipseItem, self).init_widget() d = self.declaration self.set_start_angle(d.start_angle) self.set_span_angle(d.span_angle) def set_position(self, position): self.update_rect() def set_width(self, width): self.update_rect() def set_height(self, height): self.update_rect() def update_rect(self): d = self.declaration pos = d.position self.widget.setRect(pos.x, pos.y, d.width, d.height) def set_span_angle(self, angle): self.widget.setSpanAngle(int(angle * 16)) def set_start_angle(self, angle): self.widget.setStartAngle(int(angle * 16)) class QtGraphicsRectItem(QtAbstractGraphicsShapeItem, ProxyGraphicsRectItem): #: Internal widget widget = Typed(QGraphicsRectItem) def create_widget(self): self.widget = QGraphicsRectItem(self.parent_widget()) def set_position(self, position): self.update_rect() def set_width(self, width): self.update_rect() def set_height(self, height): self.update_rect() def update_rect(self): d = self.declaration pos = d.position self.widget.setRect(pos.x, pos.y, d.width, d.height) class QtGraphicsTextItem(QtAbstractGraphicsShapeItem, ProxyGraphicsTextItem): #: Internal widget widget = Typed(QGraphicsSimpleTextItem) def create_widget(self): self.widget = QGraphicsSimpleTextItem(self.parent_widget()) def init_widget(self): super(QtGraphicsTextItem, self).init_widget() d = self.declaration if d.text: self.set_text(d.text) if d.font: self.set_font(d.font) def set_text(self, text): self.widget.setText(text) def set_font(self, font): self.widget.setFont(get_cached_qfont(font)) class QtGraphicsPolygonItem(QtAbstractGraphicsShapeItem, ProxyGraphicsPolygonItem): #: Internal widget widget = Typed(QGraphicsPolygonItem) def create_widget(self): self.widget = QGraphicsPolygonItem(self.parent_widget()) def init_widget(self): super(QtGraphicsPolygonItem, self).init_widget() d = self.declaration self.set_points(d.points) def set_points(self, points): polygon = QPolygonF([QPointF(*p[:2]) for p in points]) self.widget.setPolygon(polygon) class QtGraphicsPathItem(QtAbstractGraphicsShapeItem, ProxyGraphicsPathItem): #: Internal widget widget = Typed(QGraphicsPathItem) def create_widget(self): self.widget = QGraphicsPathItem(self.parent_widget()) def init_widget(self): super(QtGraphicsPathItem, self).init_widget() d = self.declaration if d.path: self.set_path(d.path) def set_path(self, path): #: TODO: Convert path self.widget.setPath(path) class QtGraphicsImageItem(QtGraphicsItem, ProxyGraphicsImageItem): #: Internal widget widget = Typed(QGraphicsPixmapItem) def create_widget(self): self.widget = QGraphicsPixmapItem(self.parent_widget()) def init_widget(self): super(QtGraphicsImageItem, self).init_widget() d = self.declaration if d.image: self.set_image(d.image) def set_image(self, image): self.widget.setPixmap(QPixmap.fromImage(get_cached_qimage(image))) enamlx-0.6.4/enamlx/qt/qt_key_event.py000066400000000000000000000110111456613731400177700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015-2022, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 29, 2015 """ from atom.api import Callable, Dict, Instance from enaml.qt.qt_control import QtControl from qtpy import QtCore from enamlx.widgets.key_event import ProxyKeyEvent Qt = QtCore.Qt MODIFIERS = { "": Qt.NoModifier, "shift": Qt.ShiftModifier, "ctrl": Qt.ControlModifier, "alt": Qt.AltModifier, "meta": Qt.MetaModifier, "keypad": Qt.KeypadModifier, "group": Qt.GroupSwitchModifier, } KEYS = { k.split("Key_")[-1].lower(): getattr(Qt, k) for k in Qt.__dict__ if k.startswith("Key_") } class QtKeyEvent(QtControl, ProxyKeyEvent): # Reference to the original handler _keyPressEvent = Callable() # Reference to the original handler _keyReleaseEvent = Callable() #: Widget that this key press handler is overriding widget = Instance(QtCore.QObject) #: Key codes to match codes = Dict() def create_widget(self): """The KeyEvent uses the parent_widget as it's widget""" self.widget = self.parent_widget() def init_widget(self): """The KeyEvent uses the parent_widget as it's widget""" super(QtKeyEvent, self).init_widget() d = self.declaration widget = self.widget self._keyPressEvent = widget.keyPressEvent self._keyReleaseEvent = widget.keyReleaseEvent self.set_enabled(d.enabled) self.set_keys(d.keys) # ------------------------------------------------------------------------- # ProxyKeyEvent API # ------------------------------------------------------------------------- def set_enabled(self, enabled): widget = self.widget if enabled: widget.keyPressEvent = lambda event: self.on_key_press(event) widget.keyReleaseEvent = lambda event: self.on_key_release(event) else: # Restore original widget.keyPressEvent = self._keyPressEvent widget.keyReleaseEvent = self._keyReleaseEvent def set_keys(self, keys): """Parse all the key codes and save them""" codes = {} for key in keys: parts = [k.strip().lower() for k in key.split("+")] code = KEYS.get(parts[-1]) modifier = Qt.KeyboardModifier(0) if code is None: raise KeyError("Invalid key code '{}'".format(key)) if len(parts) > 1: for mod in parts[:-1]: mod_code = MODIFIERS.get(mod) if mod_code is None: raise KeyError("Invalid key modifier '{}'".format(mod_code)) modifier |= mod_code if code not in codes: codes[code] = [] codes[code].append(modifier) self.codes = codes # ------------------------------------------------------------------------- # QWidget Keys API # ------------------------------------------------------------------------- def is_matching_key(self, code, mods): codes = self.codes.get(code, None) if codes is None: return False return mods in codes def on_key_press(self, event): d = self.declaration try: code = event.key() mods = event.modifiers() is_repeat = event.isAutoRepeat() if not self.codes or self.is_matching_key(code, mods): if not d.repeats and is_repeat: return d.pressed( { "code": code, "modifiers": mods, "key": event.text(), "repeated": is_repeat, } ) finally: self._keyPressEvent(event) def on_key_release(self, event): d = self.declaration try: code = event.key() mods = event.modifiers() is_repeat = event.isAutoRepeat() if not self.codes or self.is_matching_key(code, mods): if not d.repeats and is_repeat: return d.released( { "code": code, "key": event.text(), "modifiers": mods, "repeated": is_repeat, } ) finally: self._keyReleaseEvent(event) enamlx-0.6.4/enamlx/qt/qt_plot_area.py000066400000000000000000000366321456613731400177650ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 31, 2015 """ import types import pyqtgraph as pg from atom.api import Bool, ForwardInstance, Instance, Int, Typed from enaml.application import timed_call from enaml.qt.q_resource_helpers import get_cached_qcolor from enaml.qt.qt_control import QtControl from pyqtgraph.widgets.GraphicsLayoutWidget import GraphicsLayoutWidget from enamlx.widgets.plot_area import ProxyPlotArea def gl_view_widget(): from pyqtgraph.opengl import GLViewWidget return GLViewWidget class QtPlotArea(QtControl, ProxyPlotArea): """PyQtGraph Plot Widget""" __weakref__ = None widget = Typed(GraphicsLayoutWidget) def create_widget(self): self.widget = GraphicsLayoutWidget(self.parent_widget()) def init_layout(self): for child in self.children(): self.child_added(child) def child_added(self, child): # TODO support layouts if isinstance(child, AbstractQtPlotItem): d = child.declaration kwargs = dict(row=d.row, col=d.column) if d.row or d.column else {} self.widget.addItem(child.widget, **kwargs) def child_removed(self, child): if isinstance(child, AbstractQtPlotItem): self.widget.removeItem(child.widget) class AbstractQtPlotItem(QtControl): #: So we can receive signals __weakref__ = None #: Plot item or parent plot item if nested widget = Instance(pg.PlotItem) #: Actual plot plot = Instance(pg.GraphicsObject) #: Root or nested graph is_root = Bool() #: View box viewbox = Instance(pg.ViewBox) #: Axis item axis = Instance(pg.AxisItem) _pending_refreshes = Int(0) def create_widget(self): if isinstance(self.parent(), AbstractQtPlotItem): self.widget = self.parent_widget() self.is_root = False else: self.is_root = True self.widget = pg.PlotItem() def init_widget(self): # super(AbstractQtPlotItem, self).init_widget() d = self.declaration if self.is_root: if d.grid: self.set_grid(d.grid) if d.title: self.set_title(d.title) if d.label_left: self.set_label_left(d.label_left) if d.label_right: self.set_label_right(d.label_right) if d.label_top: self.set_label_top(d.label_top) if d.label_bottom: self.set_label_bottom(d.label_bottom) if d.show_legend: self.set_show_legend(d.show_legend) if d.multi_axis: self.set_multi_axis(d.multi_axis) self.set_antialias(d.antialias) self.set_aspect_locked(d.aspect_locked) if d.background: self.set_background(d.background) if d.axis_left_ticks: self.set_axis_left_ticks(d.axis_left_ticks) if d.axis_bottom_ticks: self.set_axis_bottom_ticks(d.axis_bottom_ticks) self._refresh_plot() self.set_auto_range(d.auto_range) self.init_signals() def init_signals(self): self.widget.sigRangeChanged.connect(self.on_range_changed) self.widget.vb.sigResized.connect(self.on_resized) def _format_data(self): raise NotImplementedError def _format_style(self): data = {} d = self.declaration if d.line_pen: data["pen"] = d.line_pen if d.shadow_pen: data["shadowPen"] = d.shadow_pen if d.fill_level: data["fillLevel"] = d.fill_level if d.fill_brush: data["fillBrush"] = d.fill_brush if d.step_mode: data["stepMode"] = d.step_mode # if d.background: # data['background'] = d.background if d.symbol: data["symbol"] = d.symbol if d.symbol_pen: data["symbolPen"] = d.symbol_pen if d.symbol_brush: data["symbolBrush"] = d.symbol_brush if d.symbol_size: data["symbolSize"] = d.symbol_size if d.antialias: data["antialias"] = d.antialias return data def _refresh_plot(self): """Defer drawing until all changes are done so we don't draw during initialization or when many values change at once. """ self._pending_refreshes += 1 refresh_time = self.declaration.refresh_time timed_call(refresh_time, self._redraw_plot) def _redraw_plot(self): self._pending_refreshes -= 1 if self._pending_refreshes != 0: return # Another change occurred if self.plot: self.plot.clear() if self.viewbox: self.viewbox.close() d = self.declaration data = self._format_data() style = self._format_style() if not self.is_root and d.parent.multi_axis: self._refresh_multi_axis() self.plot = self.viewbox.addItem(pg.PlotDataItem(*data, **style)) else: self.plot = self.widget.plot(*data, **style) def _refresh_multi_axis(self): """If linked axis' are used, setup and link them""" d = self.declaration #: Create a separate viewbox self.viewbox = pg.ViewBox() #: If this is the first nested plot, use the parent right axis _plots = [ c for c in self.parent().children() if isinstance(c, AbstractQtPlotItem) ] i = _plots.index(self) if i == 0: self.axis = self.widget.getAxis("right") self.widget.showAxis("right") else: self.axis = pg.AxisItem("right") self.axis.setZValue(-10000) #: Add new axis to scene self.widget.layout.addItem(self.axis, 2, i + 2) #: Link x axis to the parent axis self.viewbox.setXLink(self.widget.vb) #: Link y axis to the view self.axis.linkToView(self.viewbox) #: Set axis label self.axis.setLabel(d.label_right) #: Add Viewbox to parent scene self.parent().parent_widget().scene().addItem(self.viewbox) def set_row(self, row): self._refresh_plot() def set_column(self, column): self._refresh_plot() def set_aspect_locked(self, locked): return # self.widget.setAspectLocked(locked) def set_background(self, background): color = get_cached_qcolor(background) if background else None if isinstance(self.parent(), AbstractQtPlotItem): self.parent().parent_widget().setBackground(color) else: self.parent_widget().setBackground(color) def set_line_pen(self, pen): self.widget.setPen(pen) def set_shadow_pen(self, pen): self.widget.setShadowPen(pen) def set_axis_left_ticks(self, callback): self.set_axis_ticks("left", callback) def set_axis_bottom_ticks(self, callback): self.set_axis_ticks("bottom", callback) def set_axis_ticks(self, axis_name, callback): axis = self.widget.getAxis(axis_name) # Save ref if not hasattr(axis, "_tickStrings"): axis._tickStrings = axis.tickStrings axis.tickStrings = ( types.MethodType(callback, axis, axis.__class__) if callback else axis._tickStrings ) def set_grid(self, grid): self.widget.showGrid(grid[0], grid[1], self.declaration.grid_alpha) def set_grid_alpha(self, alpha): self.set_grid(self.declaration.grid) def set_show_legend(self, show): if show: if not self.widget.legend: self.widget.addLegend() else: self.widget.legend.hide() def set_title(self, title): self.widget.setTitle(title) def set_label_top(self, text): self.widget.setLabels("top", text) def set_label_left(self, text): self.widget.setLabel("left", text) def set_label_right(self, text): if not self.is_root and self.declaration.parent.multi_axis: return # don't override multi axis label self.widget.setLabel("right", text) def set_label_bottom(self, text): self.widget.setLabel("bottom", text) def set_auto_downsampling(self, enabled): self.widget.setDownsampling(auto=enabled) def set_antialias(self, enabled): self._refresh_plot() def set_auto_range(self, auto_range): d = self.declaration if not isinstance(auto_range, tuple): auto_range = (auto_range, auto_range) self.declaration.auto_range = auto_range if not auto_range[0]: self.set_range_x(d.range_x) if not auto_range[1]: self.set_range_y(d.range_y) # # Setters that require a full refresh # def set_symbol(self, arg): self._refresh_plot() def set_symbol_size(self, arg): self._refresh_plot() def set_symbol_brush(self, arg): self._refresh_plot() def set_fill_brush(self, arg): self._refresh_plot() def set_fill_level(self, arg): self._refresh_plot() def set_multi_axis(self, arg): self._refresh_plot() def set_log_mode(self, arg): self._refresh_plot() def set_clip_to_view(self, arg): self._refresh_plot() def set_step_mode(self, arg): self._refresh_plot() def set_range_x(self, val): """Set visible range of x data. Note: Padding must be 0 or it will create an infinite loop """ d = self.declaration if d.auto_range[0]: return self.widget.setXRange(*val, padding=0) def set_range_y(self, val): """Set visible range of y data. Note: Padding must be 0 or it will create an infinite loop """ d = self.declaration if d.auto_range[1]: return self.widget.setYRange(*val, padding=0) # # Widget events # def on_range_changed(self, vb, rect): d = self.declaration d.range_x = rect[0] d.range_y = rect[1] def on_scale_changed(self, view_scale): pass def on_resized(self): """Update linked views""" d = self.declaration if not self.is_root and d.parent.multi_axis: if self.viewbox: self.viewbox.setGeometry(self.widget.vb.sceneBoundingRect()) self.viewbox.linkedViewChanged(self.widget.vb, self.viewbox.XAxis) # # # # Child events # # # # def child_added(self, child): # # TODO support layouts # if isinstance(child,AbstractQtPlotItem): # self.addItem(child.widget) # # def child_removed(self, child): # if isinstance(child,AbstractQtPlotItem): # self.removeItem(child.widget) class QtPlotItem2D(AbstractQtPlotItem): def set_x(self, x): # Only refresh when they are equal # because one can be set at a time if len(self.declaration.y) == len(x): self._refresh_plot() def set_y(self, y): # Only refresh when they are equal # because one can be set at a time if len(self.declaration.x) == len(y): self._refresh_plot() def _format_data(self): data = [self.declaration.y] if self.declaration.x is not None: data.insert(0, self.declaration.x) return data class QtPlotItemDict(QtPlotItem2D): def set_data(self, data): self._refresh_plot() def _format_data(self): return self.declaration.data class QtPlotItemList(QtPlotItemDict): pass class QtPlotItemArray(QtPlotItem2D): pass class QtPlotItem3D(QtPlotItem2D): """Use forward instance to not cause import issues if not installed.""" widget = ForwardInstance(gl_view_widget) def create_widget(self): from pyqtgraph.opengl import GLViewWidget if isinstance(self.parent(), AbstractQtPlotItem): self.widget = self.parent_widget() else: self.widget = GLViewWidget(parent=self.parent_widget()) self.widget.opts["distance"] = 40 self.widget.raise_() def init_signals(self): pass def _create_grid(self): from pyqtgraph.opengl import GLGridItem gx = GLGridItem() gx.rotate(90, 0, 1, 0) gx.translate(-10, 0, 0) self.widget.addItem(gx) gy = GLGridItem() gy.rotate(90, 1, 0, 0) gy.translate(0, -10, 0) self.widget.addItem(gy) gz = GLGridItem() gz.translate(0, 0, -10) self.widget.addItem(gz) def set_z(self, z): self._refresh_plot() def _refresh_plot(self): import numpy as np # import pyqtgraph as pg from pyqtgraph import opengl as gl self._create_grid() pts = np.vstack( [self.declaration.x, self.declaration.y, self.declaration.z] ).transpose() plt = gl.GLLinePlotItem( pos=pts ) # , color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True) self.widget.addItem(plt) def set_grid(self, grid): pass class QtPlotItemArray3D(QtPlotItem3D): def _refresh_plot(self): import numpy as np import pyqtgraph as pg from pyqtgraph import opengl as gl self._create_grid() n = 51 x = self.declaration.x y = self.declaration.y for i in range(n): yi = np.array([y[i]] * 100) d = (x**2 + yi**2) ** 0.5 z = 10 * np.cos(d) / (d + 1) pts = np.vstack([x, yi, z]).transpose() plt = gl.GLLinePlotItem( pos=pts, color=pg.glColor((i, n * 1.3)), width=(i + 1) / 10.0, antialias=True, ) self.widget.addItem(plt) # def set_data(self,data): # self.widget.plotItem.clear() # if self._views: # for view in self._views: # view.clear() # # views = [] # i = 0 # if self.declaration.multi_axis: # for i,plot in enumerate(data): # if i>3: # break # if 'pen' not in plot: # plot['pen'] = self._colors[i] # if i>0: # view = ViewBox() # views.append(view) # self.widget.plotItem.scene().addItem(view) # if i==1: # axis = self.widget.plotItem.getAxis('right') # elif i>1: # axis = AxisItem('right') # axis.setZValue(-10000) # self.widget.plotItem.layout.addItem(axis,2,3) # axis.linkToView(view) # view.setXLink(self.widget.plotItem) # view.addItem(PlotCurveItem(**plot)) # else: #view.setYLink(self.widget.plotItem) # self.widget.plot(**plot) # if i>0: # def syncViews(): # for v in views: # v.setGeometry(self.widget.plotItem.vb.sceneBoundingRect()) # v.linkedViewChanged(self.widget.plotItem.vb,v.XAxis) # syncViews() # self.widget.plotItem.vb.sigResized.connect(syncViews) # self._views = views enamlx-0.6.4/enamlx/qt/qt_table_view.py000066400000000000000000000152641456613731400201360ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 28, 2015 """ from atom.api import Int, Typed from enaml.application import timed_call from qtpy.QtCore import QAbstractTableModel from qtpy.QtWidgets import QTableView from enamlx.qt.qt_abstract_item import ( RESIZE_MODES, AbstractQtWidgetItem, AbstractQtWidgetItemGroup, ) from enamlx.qt.qt_abstract_item_view import QAbstractAtomItemModel, QtAbstractItemView from enamlx.widgets.table_view import ( ProxyTableView, ProxyTableViewColumn, ProxyTableViewItem, ProxyTableViewRow, ) class QAtomTableModel(QAbstractAtomItemModel, QAbstractTableModel): """Model that pulls it's data from the TableViewItems declaration. """ def rowCount(self, parent=None): d = self.declaration return len(d.vertical_headers if d.vertical_headers else d.items) def columnCount(self, parent=None): d = self.declaration return len(d.horizontal_headers if d.horizontal_headers else d.items) def itemAt(self, index): if not index.isValid(): return None d = self.declaration try: d.current_row = index.row() d.current_column = index.column() r = d.current_row - d.visible_row c = d.current_column - d.visible_column return d._items[r]._items[c].proxy except IndexError: return None class QtTableView(QtAbstractItemView, ProxyTableView): #: Proxy widget widget = Typed(QTableView) def create_widget(self): self.widget = QTableView(self.parent_widget()) def init_widget(self): super(QtTableView, self).init_widget() d = self.declaration self.set_show_grid(d.show_grid) def init_model(self): self.set_model(QAtomTableModel(parent=self.widget)) # ------------------------------------------------------------------------- # Widget settters # ------------------------------------------------------------------------- def set_show_grid(self, show): self.widget.setShowGrid(show) def set_cell_padding(self, padding): self.widget.setStyleSheet("QTableView::item { padding: %ipx }" % padding) def set_vertical_minimum_section_size(self, size): self.widget.verticalHeader().setMinimumSectionSize(size) def set_horizontal_minimum_section_size(self, size): self.widget.horizontalHeader().setMinimumSectionSize(size) def set_horizontal_stretch(self, stretch): self.widget.horizontalHeader().setStretchLastSection(stretch) def set_vertical_stretch(self, stretch): self.widget.verticalHeader().setStretchLastSection(stretch) def set_resize_mode(self, mode): header = self.widget.horizontalHeader() # Custom is obsolete, use fixed instead. mode = "fixed" if mode == "custom" else mode header.setSectionResizeMode(RESIZE_MODES[mode]) def set_show_horizontal_header(self, show): header = self.widget.horizontalHeader() header.show() if show else header.hide() def set_show_vertical_header(self, show): header = self.widget.verticalHeader() header.show() if show else header.hide() # ------------------------------------------------------------------------- # View refresh handlers # ------------------------------------------------------------------------- def _refresh_visible_column(self, value): self._pending_column_refreshes -= 1 if self._pending_column_refreshes == 0: d = self.declaration cols = self.model.columnCount() - d.visible_columns d.visible_column = max(0, min(value, cols)) def _refresh_visible_row(self, value): self._pending_row_refreshes -= 1 if self._pending_row_refreshes == 0 and (self.declaration is not None): d = self.declaration rows = self.model.rowCount() - d.visible_rows d.visible_row = max(0, min(value, rows)) def _refresh_visible_rows(self): return top = self.widget.rowAt(self.widget.rect().top()) bottom = self.widget.rowAt(self.widget.rect().bottom()) self.declaration.visible_rows = max(1, (bottom - top)) * 2 # 2x for safety def _refresh_visible_columns(self): return left = self.widget.rowAt(self.widget.rect().left()) right = self.widget.rowAt(self.widget.rect().right()) self.declaration.visible_columns = max(1, (right - left)) * 2 class AbstractQtTableViewItemGroup(AbstractQtWidgetItemGroup): def create_widget(self): pass @property def widget(self): return self.parent_widget() class QtTableViewItem(AbstractQtWidgetItem, ProxyTableViewItem): #: Pending refreshes when loading widgets _refresh_count = Int(0) #: Time to wait before loading widget in ms _loading_interval = Int(100) def _default_view(self): return self.parent().parent() def set_row(self, row): self._update_index() def set_column(self, column): self._update_index() def _update_index(self): """Update the reference to the index within the table""" d = self.declaration self.index = self.view.model.index(d.row, d.column) if self.delegate: self._refresh_count += 1 timed_call(self._loading_interval, self._update_delegate) def _update_delegate(self): """Update the delegate cell widget. This is deferred so it does not get called until the user is done scrolling. """ self._refresh_count -= 1 if self._refresh_count != 0: return try: delegate = self.delegate if not self.is_visible(): return # The table destroys when it goes out of view # so we always have to make a new one delegate.create_widget() delegate.init_widget() # Set the index widget self.view.widget.setIndexWidget(self.index, delegate.widget) except RuntimeError: pass # Since this is deferred, the table could be deleted already def is_visible(self): """Check if this index is currently visible""" return True def data_changed(self, change): """Notify the model that data has changed in this cell!""" index = self.index if index: self.view.model.dataChanged.emit(index, index) class QtTableViewRow(AbstractQtTableViewItemGroup, ProxyTableViewRow): pass class QtTableViewColumn(AbstractQtTableViewItemGroup, ProxyTableViewColumn): pass enamlx-0.6.4/enamlx/qt/qt_tree_view.py000066400000000000000000000177101456613731400200040ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 28, 2015 """ from atom.api import Instance, Int, Typed from enaml.application import timed_call from enaml.core.pattern import Pattern from enaml.qt.qt_widget import QtWidget from qtpy.QtCore import QAbstractItemModel, QModelIndex from qtpy.QtWidgets import QTreeView from enamlx.qt.qt_abstract_item import RESIZE_MODES, AbstractQtWidgetItem from enamlx.qt.qt_abstract_item_view import QAbstractAtomItemModel, QtAbstractItemView from enamlx.widgets.tree_view import ( ProxyTreeView, ProxyTreeViewColumn, ProxyTreeViewItem, ) class QAtomTreeModel(QAbstractAtomItemModel, QAbstractItemModel): def rowCount(self, parent): d = self.declaration if d.vertical_headers: return len(d.vertical_headers) elif parent.isValid(): item = parent.internalPointer() d = item.declaration return len(d.items) if d and not d.is_destroyed else 0 def columnCount(self, parent): d = self.declaration if d.horizontal_headers: return len(d.horizontal_headers) elif parent.isValid(): item = parent.internalPointer() d = item.declaration return len(d._columns) if d and not d.is_destroyed else 0 def index(self, row, column, parent): """The index should point to the corresponding QtControl in the enaml object hierarchy. """ item = parent.internalPointer() #: If the parent is None d = self.declaration if item is None else item.declaration if row < len(d._items): proxy = d._items[row].proxy assert isinstance(proxy, QtTreeViewItem), "Invalid item {}".format(proxy) else: proxy = d.proxy return self.createIndex(row, column, proxy) def parent(self, index): if not index.isValid(): return QModelIndex() item = index.internalPointer() if not isinstance(item, QtTreeViewItem) or item.is_destroyed: return QModelIndex() parent = item.parent() if not isinstance(parent, QtTreeViewItem) or parent.is_destroyed: return QModelIndex() d = parent.declaration return self.createIndex(d.row, 0, parent) def itemAt(self, index=None): if not index or not index.isValid(): return item = index.internalPointer() assert isinstance( item, QtTreeViewItem ), "Invalid index: {} at ({},{}) {}".format( index, index.row(), index.column(), item ) d = item.declaration try: c = index.column() # - d.visible_column return d._columns[c].proxy except IndexError: return class QtTreeView(QtAbstractItemView, ProxyTreeView): #: Tree widget widget = Typed(QTreeView) #: Root index index = Instance(QModelIndex, ()) def create_widget(self): self.widget = QTreeView(self.parent_widget()) def init_widget(self): super(QtTreeView, self).init_widget() d = self.declaration self.set_show_root(d.show_root) def init_model(self): self.set_model(QAtomTreeModel(parent=self.widget)) # ------------------------------------------------------------------------- # Widget Setters # ------------------------------------------------------------------------- def set_show_root(self, show): self.widget.setRootIsDecorated(show) def set_cell_padding(self, padding): self.widget.setStyleSheet("QTreeView::item { padding: %ipx }" % padding) def set_horizontal_minimum_section_size(self, size): self.widget.header().setMinimumSectionSize(size) def set_horizontal_stretch(self, stretch): self.widget.header().setStretchLastSection(stretch) def set_horizontal_headers(self, headers): self.widget.header().model().layoutChanged.emit() def set_resize_mode(self, mode): self.widget.header().setSectionResizeMode(RESIZE_MODES[mode]) def set_show_horizontal_header(self, show): header = self.widget.header() header.show() if show else header.hide() # ------------------------------------------------------------------------- # View refresh handlers # ------------------------------------------------------------------------- def _refresh_visible_column(self, value): self._pending_column_refreshes -= 1 if self._pending_column_refreshes == 0: d = self.declaration # TODO: What about parents??? try: cols = self.model.columnCount(self.index) - d.visible_columns d.visible_column = max(0, min(value, cols)) except RuntimeError: #: Since refreshing is deferred several ms later pass def _refresh_visible_row(self, value): self._pending_row_refreshes -= 1 if self._pending_row_refreshes == 0: d = self.declaration try: rows = self.model.rowCount(self.index) - d.visible_rows d.visible_row = max(0, min(value, rows)) except RuntimeError: pass class AbstractQtTreeViewItem(AbstractQtWidgetItem): """Base TreeViewItem class""" #: Pending refreshes when loading widgets _refresh_count = Int(0) #: Time to wait before loading widget _loading_interval = Int(100) def create_widget(self): if self.declaration: for child in self.children(): if isinstance(child, (Pattern, QtWidget)): self.delegate = child def set_row(self, row): self._update_index() def set_column(self, column): self._update_index() def _default_index(self): d = self.declaration return self.view.model.index(d.row, d.column, self.parent().index) def _update_index(self): self.index = self._default_index() if self.delegate: self._refresh_count += 1 timed_call(self._loading_interval, self._update_delegate) def _update_delegate(self): """Update the delegate cell widget. This is deferred so it does not get called until the user is done scrolling. """ self._refresh_count -= 1 if self._refresh_count != 0: return try: delegate = self.delegate if not self._is_visible(): return # The table destroys when it goes out of view # so we always have to make a new one delegate.create_widget() delegate.init_widget() # Set the index widget self.view.widget.setIndexWidget(self.index, delegate.widget) except RuntimeError: # Since this is deferred, the table could be deleted already # and a RuntimeError is possible pass def _is_visible(self): return self.index.isValid() def data_changed(self, change): """Notify the model that data has changed in this cell!""" self.view.model.dataChanged.emit(self.index, self.index) class QtTreeViewItem(AbstractQtTreeViewItem, ProxyTreeViewItem): def _default_view(self): """If this is the root item, return the parent which must be a TreeView, otherwise return the parent Item's view. """ parent = self.parent() if isinstance(parent, QtTreeView): return parent return parent.view class QtTreeViewColumn(AbstractQtTreeViewItem, ProxyTreeViewColumn): def _default_view(self): """Since the TreeViewColumn must be a child of a TreeViewItem, simply return the Item's view. """ return self.parent().view def _default_index(self): d = self.declaration return self.view.model.index(d.row, d.column, self.parent().index) enamlx-0.6.4/enamlx/widgets/000077500000000000000000000000001456613731400157515ustar00rootroot00000000000000enamlx-0.6.4/enamlx/widgets/__init__.py000066400000000000000000000000001456613731400200500ustar00rootroot00000000000000enamlx-0.6.4/enamlx/widgets/abstract_item.py000066400000000000000000000103341456613731400211450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 24, 2015 """ from atom.api import ( Bool, Coerced, Enum, Event, ForwardInstance, Int, Property, Str, Typed, observe, ) from enaml.core.declarative import d_ from enaml.icon import Icon from enaml.layout.geometry import Size from enaml.widgets.control import Control, ProxyControl class ProxyAbstractWidgetItemGroup(ProxyControl): #: Reference to the declaration declaration = ForwardInstance(lambda: AbstractWidgetItemGroup) def set_selectable(self, selectable): pass class ProxyAbstractWidgetItem(ProxyControl): #: Reference to the declaration declaration = ForwardInstance(lambda: AbstractWidgetItem) def set_row(self, row): pass def set_column(self, column): pass def set_text(self, text): pass def set_text_alignment(self, text_alignment): pass def set_icon(self, icon): pass def set_icon_size(self, size): pass def set_editable(self, editable): pass def set_checkable(self, checkable): pass class AbstractWidgetItemGroup(Control): #: Triggered when clicked clicked = d_(Event(), writable=False) #: Triggered when double clicked double_clicked = d_(Event(), writable=False) #: Triggered when the row, column, or item is entered entered = d_(Event(), writable=False) #: Triggered when the row, column, or item is pressed pressed = d_(Event(), writable=False) #: Triggered when the row, column, or item's selection changes selection_changed = d_(Event(bool), writable=False) def _get_items(self): return [c for c in self.children if isinstance(c, AbstractWidgetItem)] #: Internal item reference _items = Property(lambda self: self._get_items(), cached=True) def child_added(self, child): """Reset the item cache when a child is added""" super(AbstractWidgetItemGroup, self).child_added(child) self.get_member("_items").reset(self) def child_removed(self, child): """Reset the item cache when a child is removed""" super(AbstractWidgetItemGroup, self).child_removed(child) self.get_member("_items").reset(self) class AbstractWidgetItem(AbstractWidgetItemGroup): """Item to be shared between table views and tree views""" #: Model index or row within the view row = d_(Int(), writable=False) #: Column within the view column = d_(Int(), writable=False) #: Text to display within the cell text = d_(Str()) #: Text alignment within the cell text_alignment = d_( Enum( *[ (h, v) for h in ("left", "right", "center", "justify") for v in ("center", "top", "bottom") ] ) ) #: Icon to display in the cell icon = d_(Typed(Icon)) #: The size to use for the icon. The default is an invalid size #: and indicates that an appropriate default should be used. icon_size = d_(Coerced(Size, (-1, -1))) #: Whether the item or group can be selected selectable = d_(Bool(True)) #: Selection state of the item or group selected = d_(Bool()) #: Whether the item or group can be checked checkable = d_(Bool()) #: Checked state of the item or group checked = d_(Bool()) #: Whether the item or group can be edited editable = d_(Bool()) #: Triggered when the item's contents change changed = d_(Event(), writable=False) #: Triggered when the checkbox state changes toggled = d_(Event(bool), writable=False) @observe( "row", "column", "text", "text_alignment", "icon", "icon_size", "selectable", "selected", "checkable", "checked", "editable", ) def _update_proxy(self, change): """An observer which sends state change to the proxy.""" if change["name"] in ["row", "column"]: super(AbstractWidgetItem, self)._update_proxy(change) else: self.proxy.data_changed(change) enamlx-0.6.4/enamlx/widgets/abstract_item_view.py000066400000000000000000000127601456613731400222040ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 23, 2015 """ from atom.api import ( Bool, ContainerList, Enum, ForwardInstance, Int, Property, observe, set_default, ) from enaml.core.declarative import d_ from enaml.widgets.control import Control, ProxyControl from .abstract_item import AbstractWidgetItemGroup class ProxyAbstractItemView(ProxyControl): #: Reference to the declaration declaration = ForwardInstance(lambda: AbstractItemView) def set_items(self, items): pass def set_selection_mode(self, mode): pass def set_selection_behavior(self, behavior): pass def set_selection(self, items): pass def set_scroll_to_bottom(self, scroll_to_bottom): pass def set_alternating_row_colors(self, alternate): pass def set_cell_padding(self, padding): pass def set_auto_resize(self, enabled): pass def set_resize_mode(self, mode): pass def set_word_wrap(self, enabled): pass def set_show_vertical_header(self, visible): pass def set_vertical_headers(self, headers): pass def set_vertical_stretch(self, stretch): pass def set_vertical_sizes(self, sizes): pass def set_vertical_minimum_section_size(self, size): pass def set_show_horizontal_header(self, visible): pass def set_horizontal_headers(self, headers): pass def set_horizontal_sizes(self, sizes): pass def set_horizontal_stretch(self, stretch): pass def set_horizontal_minimum_section_size(self, size): pass def set_sortable(self, sortable): pass def set_visible_row(self, row): pass def set_visible_column(self, column): pass class AbstractItemView(Control): #: Table should expand by default hug_width = set_default("ignore") #: Table should expand by default hug_height = set_default("ignore") #: The items to display in the view items = d_(ContainerList(default=[])) #: Selection mode of the view selection_mode = d_(Enum("extended", "none", "multi", "single", "contiguous")) #: Selection behavior of the view selection_behavior = d_(Enum("items", "rows", "columns")) #: Selection selection = d_(ContainerList(default=[])) #: Automatically scroll to bottm when new items are added scroll_to_bottom = d_(Bool(False)) #: Set alternating row colors alternating_row_colors = d_(Bool(False)) #: Cell padding cell_padding = d_(Int(0)) #: Automatically resize columns to fit contents auto_resize = d_(Bool(True)) #: Resize mode of columns and rows resize_mode = d_( Enum("interactive", "fixed", "stretch", "resize_to_contents", "custom") ) #: Word wrap word_wrap = d_(Bool(False)) #: Show vertical header bar show_vertical_header = d_(Bool(True)) #: Row headers vertical_headers = d_(ContainerList()) #: Stretch last row vertical_stretch = d_(Bool(False)) #: Sizes for vertical headers (list of int) vertical_sizes = d_(ContainerList()) #: Minimum row size vertical_minimum_section_size = d_(Int(0)) #: Show horizontal hearder bar show_horizontal_header = d_(Bool(True)) #: Column headers horizontal_headers = d_(ContainerList()) #: Stretch last column horizontal_stretch = d_(Bool(False)) #: Minimum column size horizontal_minimum_section_size = d_(Int(0)) #: Sizes for horizontal headers (list of int) horizontal_sizes = d_(ContainerList()) #: Table is sortable sortable = d_(Bool(True)) #: Current row index current_row = d_(Int(0)) #: Current column index current_column = d_(Int(0)) #: First visible row visible_row = d_(Int(0)) #: Number of rows visible visible_rows = d_(Int(100)) #: First visible column visible_column = d_(Int(0)) #: Number of columns visible visible_columns = d_(Int(1)) def _get_items(self): return [c for c in self.children if isinstance(c, AbstractWidgetItemGroup)] #: Cached property listing the row or columns of the table _items = Property(_get_items, cached=True) @observe( "items", "scroll_to_bottom", "alternating_row_colors", "selection_mode", "selection_behavior", "selection", "cell_padding", "auto_resize", "resize_mode", "word_wrap", "show_horizontal_header", "horizontal_headers", "horizontal_sizes", "horizontal_stretch", "show_vertical_header", "vertical_headers", "vertical_stretch", "vertical_sizes", "visible_row", "visible_column", ) def _update_proxy(self, change): """An observer which sends state change to the proxy.""" # The superclass handler implementation is sufficient. super(AbstractItemView, self)._update_proxy(change) def child_added(self, child): """Reset the item cache when a child is added""" super(AbstractItemView, self).child_added(child) self.get_member("_items").reset(self) def child_removed(self, child): """Reset the item cache when a child is removed""" super(AbstractItemView, self).child_removed(child) self.get_member("_items").reset(self) enamlx-0.6.4/enamlx/widgets/api.py000066400000000000000000000017761456613731400171070ustar00rootroot00000000000000""" Copyright (c) 2015-2018, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Jun 3, 2015 """ from .double_spin_box import DoubleSpinBox # noqa: F401 from .graphics_view import ( # noqa: F401 Brush, GraphicsEllipseItem, GraphicsImageItem, GraphicsItem, GraphicsItemGroup, GraphicsLineItem, GraphicsPathItem, GraphicsPolygonItem, GraphicsRectItem, GraphicsTextItem, GraphicsView, GraphicsWidget, Pen, Point, Rect, ) from .key_event import KeyEvent # noqa: F401 from .plot_area import ( # noqa: F401 PlotArea, PlotItem2D, PlotItem3D, PlotItemArray, PlotItemArray3D, PlotItemDict, PlotItemList, ) from .table_view import ( # noqa: F401 TableView, TableViewColumn, TableViewItem, TableViewRow, ) from .tree_view import TreeView, TreeViewColumn, TreeViewItem # noqa: F401 enamlx-0.6.4/enamlx/widgets/double_spin_box.py000066400000000000000000000024451456613731400215030ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015-2018, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 29, 2015 """ from atom.api import Float, ForwardTyped, Int, observe from enaml.core.declarative import d_ from enaml.widgets.spin_box import ProxySpinBox, SpinBox class ProxyDoubleSpinBox(ProxySpinBox): declaration = ForwardTyped(lambda: DoubleSpinBox) def set_decimals(self, prec): raise NotImplementedError() class DoubleSpinBox(SpinBox): """A spin box widget which manipulates float values.""" #: The number of decmial places to be shown. Defaults to 2. decimals = d_(Int(2)) #: The minimum value for the spin box. Defaults to 0. minimum = d_(Float(0, strict=False)) #: The maximum value for the spin box. Defaults to 100. maximum = d_(Float(100, strict=False)) #: The maximum value for the spin box. Defaults to 100. single_step = d_(Float(1.0, strict=False)) #: The position value of the spin box. The value will be clipped to #: always fall between the minimum and maximum. value = d_(Float(0, strict=False)) @observe("decimals") def _update_proxy(self, change): super(DoubleSpinBox, self)._update_proxy(change) enamlx-0.6.4/enamlx/widgets/graphics_view.py000066400000000000000000000714611456613731400211660ustar00rootroot00000000000000""" Copyright (c) 2018, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Sept 5, 2018 """ import sys from enum import IntFlag from atom.api import ( Atom, Bool, Coerced, Enum, Event, Float, ForwardTyped, Instance, List, Str, Typed, Value, observe, set_default, ) from enaml.colors import ColorMember from enaml.core.declarative import d_, d_func from enaml.fonts import FontMember from enaml.image import Image from enaml.layout.constrainable import ConstrainableMixin from enaml.widgets.control import Control, ProxyControl from enaml.widgets.toolkit_object import ProxyToolkitObject, ToolkitObject from enaml.widgets.widget import Feature NUMERIC = ( (int, float, long) if sys.version_info.major < 3 else (int, float) # noqa: F821 ) class GraphicFeature(IntFlag): #: Enables support for mouse events. MouseEvent = 0x08 #: Enables support for wheel events. WheelEvent = 0x16 #: Enables support for draw or paint events. DrawEvent = 0x32 #: Enables support for backgound draw events. BackgroundDrawEvent = 0x64 class Point(Atom): #: x position x = d_(Float(0, strict=False)) #: y position y = d_(Float(0, strict=False)) #: z position z = d_(Float(0, strict=False)) def __init__(self, x=0, y=0, z=0): super(Point, self).__init__(x=x, y=y, z=z) def __iter__(self): yield self.x yield self.y yield self.z def __len__(self): return 3 def __eq__(self, other): pos = (self.x, self.y, self.z) if isinstance(other, Point): return pos == (other.x, other.y, other.z) return pos == other def __add__(self, other): return Point( self.x + other[0], self.y + other[1], self.z + other[2] if len(other) > 2 else self.z, ) __radd__ = __add__ def __sub__(self, other): return Point( self.x - other[0], self.y - other[1], self.z - other[2] if len(other) > 2 else self.z, ) def __rsub__(self, other): return Point( other[0] - self.x, other[1] - self.y, other[2] - self.z if len(other) > 2 else self.z, ) def __mul__(self, other): if isinstance(other, NUMERIC): return Point(self.x * other, self.y * other, self.z * other) return Point( other[0] * self.x, other[1] * self.y, other[2] * self.z if len(other) > 2 else self.z, ) __rmul__ = __mul__ def __div__(self, other): if isinstance(other, NUMERIC): return Point(self.x / other, self.y / other, self.z / other) return Point( self.x / other[0], self.y / other[1], self.z / other[2] if len(other) > 2 else self.z, ) def __rdiv__(self, other): if isinstance(other, NUMERIC): return Point(other / self.x, other / self.y, other / self.z) return Point( other[0] / self.x, other[1] / self.y, other[2] / self.z if len(other) > 2 else self.z, ) def __neg__(self): return Point(-self.x, -self.y, -self.z) def __hash__(self): return id(self) def __getitem__(self, key): return (self.x, self.y, self.z)[key] def __repr__(self): return "Point(%d, %d, %d)" % (self.x, self.y, self.z) class Rect(Atom): x = d_(Float(0, strict=False)) y = d_(Float(0, strict=False)) width = d_(Float(0, strict=False)) height = d_(Float(0, strict=False)) def coerce_point(p): return p if isinstance(p, Point) else Point(*p) class PointMember(Coerced): def __init__(self, args=None, kwargs=None, factory=None, coercer=coerce_point): super(PointMember, self).__init__( Point, args, kwargs, factory=factory, coercer=coercer ) class Pen(Atom): #: Color color = ColorMember() #: Width width = Float(1.0, strict=False) #: Line Style line_style = Enum( "solid", "dash", "dot", "dash_dot", "dash_dot_dot", "custom", "none" ) #: Cap Style cap_style = Enum("square", "flat", "round") #: Join Style join_style = Enum("bevel", "miter", "round") #: Dash pattern used when line_style is 'custom' dash_pattern = List(Float(strict=False)) #: Internal data _tkdata = Value() class Brush(Atom): """Defines the fill pattern""" #: Color color = ColorMember() #: Image image = Instance(Image) #: Style style = Enum( "solid", "dense1", "dense2", "dense3", "dense4", "dense5", "dense6", "dense7", "horizontal", "vertical", "cross", "diag", "bdiag", "fdiag", "linear", "radial", "conical", "texture", "none", ) #: Internal data _tkdata = Value() class ProxyGraphicsView(ProxyControl): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsView) def set_auto_range(self, enabled): raise NotImplementedError def set_antialiasing(self, enabled): raise NotImplementedError def set_drag_mode(self, mode): raise NotImplementedError def set_renderer(self, renderer): raise NotImplementedError def get_item_at(self, point): raise NotImplementedError def set_lock_aspect_ratio(self, locked): raise NotImplementedError def set_selected_items(self, items): raise NotImplementedError def fit_in_view(self, item): raise NotImplementedError def center_on(self, item): raise NotImplementedError def reset_view(self): raise NotImplementedError def translate_view(self, x, y): raise NotImplementedError def scale_view(self, x, y): raise NotImplementedError def rotate_view(self, angle): raise NotImplementedError def map_from_scene(self, point): raise NotImplementedError def map_to_scene(self, point): raise NotImplementedError def pixel_density(self): raise NotImplementedError class ProxyGraphicsItem(ProxyToolkitObject): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsItem) def set_x(self, x): raise NotImplementedError def set_y(self, y): raise NotImplementedError def set_z(self, z): raise NotImplementedError def set_position(self, position): raise NotImplementedError def set_rotation(self, rotation): raise NotImplementedError def set_scale(self, scale): raise NotImplementedError def set_opacity(self, opacity): raise NotImplementedError def set_selected(self, selected): raise NotImplementedError def set_enabled(self, enabled): raise NotImplementedError def set_selectable(self, enabled): raise NotImplementedError def set_movable(self, enabled): raise NotImplementedError def set_visible(self, visible): raise NotImplementedError def set_tool_tip(self, tool_tip): raise NotImplementedError def set_status_tip(self, status_tip): raise NotImplementedError def request_update(self): raise NotImplementedError def ensure_visible(self): raise NotImplementedError def ensure_hidden(self): raise NotImplementedError def set_focus(self): raise NotImplementedError def clear_focus(self): raise NotImplementedError def has_focus(self): raise NotImplementedError class ProxyGraphicsItemGroup(ProxyGraphicsItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsItemGroup) class ProxyGraphicsWidget(ProxyGraphicsItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsWidget) class ProxyAbstractGraphicsShapeItem(ProxyGraphicsItem): #: Reference to the declaration declaration = ForwardTyped(lambda: AbstractGraphicsShapeItem) def set_pen(self, pen): raise NotImplementedError def set_brush(self, brush): raise NotImplementedError class ProxyGraphicsRectItem(ProxyAbstractGraphicsShapeItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsRectItem) def set_width(self, width): raise NotImplementedError def set_height(self, height): raise NotImplementedError class ProxyGraphicsEllipseItem(ProxyAbstractGraphicsShapeItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsEllipseItem) def set_width(self, width): raise NotImplementedError def set_height(self, height): raise NotImplementedError def set_span_angle(self, angle): raise NotImplementedError def set_start_angle(self, angle): raise NotImplementedError class ProxyGraphicsLineItem(ProxyAbstractGraphicsShapeItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsLineItem) def set_point(self, point): raise NotImplementedError class ProxyGraphicsTextItem(ProxyAbstractGraphicsShapeItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsTextItem) def set_text(self, text): raise NotImplementedError def set_font(self, font): raise NotImplementedError class ProxyGraphicsPolygonItem(ProxyAbstractGraphicsShapeItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsPolygonItem) def set_points(self, points): raise NotImplementedError class ProxyGraphicsPathItem(ProxyAbstractGraphicsShapeItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsPathItem) def set_path(self, path): raise NotImplementedError class ProxyGraphicsImageItem(ProxyGraphicsItem): #: Reference to the declaration declaration = ForwardTyped(lambda: GraphicsImageItem) def set_image(self, image): raise NotImplementedError class GraphicsItem(ToolkitObject, ConstrainableMixin): #: Proxy reference proxy = Typed(ProxyGraphicsItem) # -------------------------------------------------------------------------- # Item orentation # -------------------------------------------------------------------------- #: Position position = d_(PointMember()) #: Item rotation rotation = d_(Float(strict=False)) #: Item scale scale = d_(Float(1.0, strict=False)) # -------------------------------------------------------------------------- # Item display # -------------------------------------------------------------------------- #: Item opacity opacity = d_(Float(1.0, strict=False)) # Item selected selected = d_(Bool()) # Item is enabled enabled = d_(Bool(True)) # Item is visible visible = d_(Bool(True)) #: Tool tip tool_tip = d_(Str()) #: Status tip status_tip = d_(Str()) # -------------------------------------------------------------------------- # Item interaction # -------------------------------------------------------------------------- #: Set the extra features to enable for this widget. This value must #: be provided when the widget is instantiated. Runtime changes to #: this value are ignored. features = d_(Coerced(Feature, (0,))) extra_features = d_(Coerced(GraphicFeature, (0,))) #: Update request_update = d_(Event()) #: Set whether this item can be selected. selectable = d_(Bool()) #: Set whether this item can be moved. movable = d_(Bool()) @observe( "position", "position.x", "position.y", "position.z", "scale", "rotation", "opacity", "selected", "enabled", "visible", "tool_tip", "status_tip", "request_update", "selectable", "movable", ) def _update_proxy(self, change): super(GraphicsItem, self)._update_proxy(change) # -------------------------------------------------------------------------- # Widget API # -------------------------------------------------------------------------- def show(self): """Ensure the widget is shown. Calling this method will also set the widget visibility to True. """ self.visible = True if self.proxy_is_active: self.proxy.ensure_visible() def hide(self): """Ensure the widget is hidden. Calling this method will also set the widget visibility to False. """ self.visible = False if self.proxy_is_active: self.proxy.ensure_hidden() def set_focus(self): """Set the keyboard input focus to this widget. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.set_focus() def clear_focus(self): """Clear the keyboard input focus from this widget. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! """ if self.proxy_is_active: self.proxy.clear_focus() def has_focus(self): """Test whether this widget has input focus. FOR ADVANCED USE CASES ONLY: DO NOT ABUSE THIS! Returns ------- result : bool True if this widget has input focus, False otherwise. """ if self.proxy_is_active: return self.proxy.has_focus() return False @d_func def focus_gained(self): """A method invoked when the widget gains input focus. ** The FocusEvents feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def focus_lost(self): """A method invoked when the widget loses input focus. ** The FocusEvents feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def drag_start(self): """A method called at the start of a drag-drop operation. This method is called when the user starts a drag operation by dragging the widget with the left mouse button. It returns the drag data for the drag operation. ** The DragEnabled feature must be enabled for the widget in order for this method to be called. ** Returns ------- result : DragData An Enaml DragData object which holds the drag data. If this is not provided, no drag operation will occur. """ return None @d_func def drag_end(self, drag_data, result): """A method called at the end of a drag-drop operation. This method is called after the user has completed the drop operation by releasing the left mouse button. It is passed the original drag data object along with the resulting drop action of the operation. ** The DragEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- data : DragData The drag data created by the `drag_start` method. result : DropAction The requested drop action when the drop completed. """ pass @d_func def drag_enter(self, event): """A method invoked when a drag operation enters the widget. The widget should inspect the mime data of the event and accept the event if it can handle the drop action. The event must be accepted in order to receive further drag-drop events. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def drag_move(self, event): """A method invoked when a drag operation moves in the widget. This method will not normally be implemented, but it can be useful for supporting advanced drag-drop interactions. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def drag_leave(self): """A method invoked when a drag operation leaves the widget. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** """ pass @d_func def drop(self, event): """A method invoked when the user drops the data on the widget. The widget should either accept the proposed action, or set the drop action to an appropriate action before accepting the event, or set the drop action to DropAction.Ignore and then ignore the event. ** The DropEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : DropEvent The event representing the drag-drop operation. """ pass @d_func def mouse_press_event(self, event): """A method invoked when a mouse press event occurs in the widget. ** The MouseEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : MouseEvent The event representing the press operation. """ pass @d_func def mouse_move_event(self, event): """A method invoked when a mouse move event occurs in the widget. ** The MouseEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : MouseEvent The event representing the press operation. """ pass @d_func def mouse_release_event(self, event): """A method invoked when a mouse release event occurs in the widget. ** The MouseEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : MouseEvent The event representing the press operation. """ pass @d_func def wheel_event(self, event): """A method invoked when a wheel event occurs in the widget. This method will not normally be implemented, but it can be useful for supporting zooming and other scolling interactions. ** The WheelEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : WheelEvent The event representing the wheel operation. """ pass @d_func def draw(self, painter, options, widget): """A method invoked when this widget needs to be drawn. This method will not normally be implemented, but it can be useful for creating custom graphics. ** The DrawEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- painter: Object The toolkit dependent painter object. options: Object The toolkit dependent options object. widget: Widget The underlying widget. """ pass class GraphicsView(Control): #: Proxy reference proxy = Typed(ProxyGraphicsView) #: An graphicsview widget expands freely in height and width by default. hug_width = set_default("ignore") hug_height = set_default("ignore") #: Select backend for rendering. OpenGL is used by default if available. renderer = d_(Enum("default", "opengl", "qwidget")) #: Antialiasing is enabled by default. If performance is an issue, disable #: this. antialiasing = d_(Bool(True)) #: Items currently selected selected_items = d_(List(GraphicsItem)) #: Defines the behavior when dragging. By default nothing is done. Pan #: will pan the scene around and selection will draw a box to select items. drag_mode = d_(Enum("none", "scroll", "selection")) #: Range of allowed zoom factors. This is used to prevent scaling way out #: or way in by accident. min_zoom = d_(Float(0.007, strict=False)) max_zoom = d_(Float(100.0, strict=False)) #: Automatically resize view to fit the scene contents auto_range = d_(Bool(False)) # TODO: Broken #: Keep the aspect ratio locked when resizing the view range lock_aspect_ratio = d_(Bool(True)) #: Set the extra features to enable for this widget. This value must #: be provided when the widget is instantiated. Runtime changes to #: this value are ignored. extra_features = d_(Coerced(GraphicFeature, (0,))) def _default_extra_features(self): return GraphicFeature.WheelEvent | GraphicFeature.MouseEvent @observe( "selected_items", "renderer", "antialiasing", "drag_mode", "auto_range", "lock_aspect_ratio", ) def _update_proxy(self, change): super(GraphicsView, self)._update_proxy(change) # -------------------------------------------------------------------------- # Widget API # -------------------------------------------------------------------------- @d_func def wheel_event(self, event): """A method invoked when a wheel event occurs in the widget. This method will not normally be implemented, but it can be useful for supporting zooming and other scolling interactions. ** The WheelEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : WheelEvent The event representing the wheel operation. """ pass @d_func def mouse_press_event(self, event): """A method invoked when a mouse press event occurs in the widget. ** The MouseEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : MouseEvent The event representing the press operation. """ pass @d_func def mouse_move_event(self, event): """A method invoked when a mouse move event occurs in the widget. ** The MouseEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : MouseEvent The event representing the press operation. """ pass @d_func def mouse_release_event(self, event): """A method invoked when a mouse release event occurs in the widget. ** The MouseEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- event : MouseEvent The event representing the press operation. """ pass @d_func def draw_background(self, painter, rect): """A method invoked when a background draw is requested. This method will not normally be implemented, but it can be useful for implementing custom backgrounds. This drawing is cached. ** The DrawBackgroundEnabled feature must be enabled for the widget in order for this method to be called. ** Parameters ---------- painter : Painter A the toolkit dependent handle drawing. rect : Rect A rect showing the area of interest. """ pass # -------------------------------------------------------------------------- # Graphics Scene API # -------------------------------------------------------------------------- def get_item_at(self, *args, **kwargs): """Return the items at the given position""" return self.proxy.get_item_at(coerce_point(*args, **kwargs)) def fit_in_view(self, item): """Fit this item into the view""" self.proxy.fit_in_view(item) def center_on(self, item): """Center on the given item or point.""" if not isinstance(item, GraphicsItem): item = coerce_point(item) self.proxy.center_on(item) def translate_view(self, x=0, y=0): """Translate the view by the given x and y pixels.""" return self.proxy.translate_view(x, y) def scale_view(self, x=1, y=1): """Scale the view by the given x and y factors.""" return self.proxy.scale_view(x, y) def rotate_view(self, angle=0): """Roteate the view by the given x and y factors.""" self.proxy.rotate_view(angle) def reset_view(self): """Reset all view transformations.""" self.proxy.reset_view() def map_from_scene(self, point): """Returns the scene coordinate point mapped to viewport coordinates.""" return self.proxy.map_from_scene(point) def map_to_scene(self, point): """Returns the viewport coordinate point mapped to scene coordinates.""" return self.proxy.map_to_scene(point) def pixel_density(self): """Returns the size of a pixel in sceen coordinates.""" return self.proxy.pixel_density() class GraphicsItemGroup(GraphicsItem): #: Proxy reference proxy = Typed(ProxyGraphicsItemGroup) class AbstractGraphicsShapeItem(GraphicsItem): """A common base for all path items.""" #: Proxy reference proxy = Typed(ProxyAbstractGraphicsShapeItem) #: Set the pen or "line" style. pen = d_(Instance(Pen)) #: Set the brush or "fill" style. brush = d_(Instance(Brush)) @observe("pen", "brush") def _update_proxy(self, change): super(AbstractGraphicsShapeItem, self)._update_proxy(change) class GraphicsRectItem(AbstractGraphicsShapeItem): #: Proxy reference proxy = Typed(ProxyGraphicsRectItem) #: Width width = d_(Float(10.0, strict=False)) #: Height height = d_(Float(10.0, strict=False)) @observe("width", "height") def _update_proxy(self, change): super(GraphicsRectItem, self)._update_proxy(change) class GraphicsEllipseItem(AbstractGraphicsShapeItem): #: Proxy reference proxy = Typed(ProxyGraphicsEllipseItem) #: Width width = d_(Float(10.0, strict=False)) #: Height height = d_(Float(10.0, strict=False)) #: Sets the span angle for an ellipse segment to angle. #: This is rounded to the nearest 16ths of a degree. span_angle = d_(Float(360.0, strict=False)) #: Sets the start angle for an ellipse segment to angle. #: This is rounded to the nearest 16ths of a degree. start_angle = d_(Float(0.0, strict=False)) @observe("width", "height", "span_angle", "start_angle") def _update_proxy(self, change): super(GraphicsEllipseItem, self)._update_proxy(change) class GraphicsTextItem(AbstractGraphicsShapeItem): #: Proxy reference proxy = Typed(ProxyGraphicsTextItem) #: Text text = d_(Str()) #: Font font = d_(FontMember()) @observe("text", "font") def _update_proxy(self, change): super(GraphicsTextItem, self)._update_proxy(change) class GraphicsLineItem(AbstractGraphicsShapeItem): """Creates a line from the position x,y to the given point""" #: Proxy reference proxy = Typed(ProxyGraphicsLineItem) #: An x,y or x,y,z point point = d_(PointMember()) @observe("point") def _update_proxy(self, change): super(GraphicsLineItem, self)._update_proxy(change) class GraphicsPolygonItem(AbstractGraphicsShapeItem): """Creates a line from the position x,y to the given point""" #: Proxy reference proxy = Typed(ProxyGraphicsPolygonItem) #: A list of (x,y) or (x,y,z) points #: TODO: Support np array points = d_(List(PointMember())) @observe("points") def _update_proxy(self, change): super(GraphicsPolygonItem, self)._update_proxy(change) class GraphicsPathItem(AbstractGraphicsShapeItem): #: Proxy reference proxy = Typed(ProxyGraphicsPathItem) #: Path. For now you must pass a QPainterPath until some "abstract" #: path value and format is accepted. path = d_(Value()) @observe("path") def _update_proxy(self, change): super(GraphicsPathItem, self)._update_proxy(change) class GraphicsImageItem(GraphicsItem): #: Proxy reference proxy = Typed(ProxyGraphicsImageItem) #: Image image = d_(Instance(Image)) @observe("image") def _update_proxy(self, change): super(GraphicsImageItem, self)._update_proxy(change) class GraphicsWidget(GraphicsItem): """Use this to embed a widget within a graphics scene""" #: Proxy reference proxy = Typed(ProxyGraphicsWidget) enamlx-0.6.4/enamlx/widgets/key_event.py000066400000000000000000000026501456613731400203170ustar00rootroot00000000000000""" Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Aug 29, 2015 """ import sys from atom.api import Bool, Event, ForwardTyped, List, Typed, observe from enaml.core.declarative import d_ from enaml.widgets.control import Control, ProxyControl if sys.version_info.major < 3: str = basestring # noqa: F821 class ProxyKeyEvent(ProxyControl): #: Reference to the declaration declaration = ForwardTyped(lambda: KeyEvent) def set_enabled(self, enabled): raise NotImplementedError def set_keys(self, keys): raise NotImplementedError class KeyEvent(Control): #: Proxy reference proxy = Typed(ProxyKeyEvent) #: List of keys that or codes to filter #: Can be a key letter or code and including modifiers #: Ex. Ctrl + r, up, esc, etc.. #: If empty will fire for any key combination keys = d_(List(str)) #: Listen for events enabled = d_(Bool(True)) #: Fire multiple times when the key is held repeats = d_(Bool(True)) #: Pressed event pressed = d_(Event(dict), writable=False) #: Released event released = d_(Event(dict), writable=False) @observe("enabled", "keys") def _update_proxy(self, change): """An observer which sends state change to the proxy.""" super(KeyEvent, self)._update_proxy(change) enamlx-0.6.4/enamlx/widgets/plot_area.py000066400000000000000000000143551456613731400203010ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Jun 11, 2015 """ import sys from atom.api import ( Bool, Callable, ContainerList, Dict, Enum, Float, FloatRange, ForwardInstance, ForwardTyped, Instance, Int, Str, Tuple, Typed, Value, observe, ) from atom.atom import set_default from enaml.core.declarative import d_ from enaml.widgets.api import Container from enaml.widgets.control import Control, ProxyControl if sys.version_info.major < 3: str = basestring # noqa: F821 def numpy_ndarray(): import numpy return numpy.ndarray class ProxyPlotArea(ProxyControl): declaration = ForwardTyped(lambda: PlotArea) class PlotArea(Container): hug_width = set_default("ignore") hug_height = set_default("ignore") proxy = Typed(ProxyPlotArea) setup = d_(Callable(lambda graph: None)) PEN_ARGTYPES = (tuple, list, str, dict) BRUSH_ARGTYPES = (tuple, list, str, dict, int, float) class PlotItem(Control): #: Title of data series title = d_(Str()) #: Name name = d_(Str()) #: Row in plot area row = d_(Int(0)) #: Column in plot area column = d_(Int(0)) #: Pen type to use for line line_pen = d_(Instance(PEN_ARGTYPES)) #: Pen type to use for shadow shadow_pen = d_(Instance(PEN_ARGTYPES)) #: Fill level fill_level = d_(Float(strict=False)) # ‘c’ one of: r, g, b, c, m, y, k, w # R, G, B, [A] integers 0-255 # (R, G, B, [A]) tuple of integers 0-255 # float greyscale, 0.0-1.0 # int see intColor() # (int, hues) see intColor() # “RGB” hexadecimal strings; may begin with ‘#’ # “RGBA” # “RRGGBB” # “RRGGBBAA” #: Brush fill type fill_brush = d_(Instance(BRUSH_ARGTYPES)) #: Symbol to use for points symbol = d_(Enum(None, "o", "s", "t", "d", "+")) #: Symbol sizes for points symbol_size = d_(Float(10, strict=False)) #: Symbol pen to use symbol_pen = d_(Instance(PEN_ARGTYPES)) #: Symbol brush symbol_brush = d_(Instance(BRUSH_ARGTYPES)) #: Show legend show_legend = d_(Bool(False)) label_left = d_(Str()) label_right = d_(Str()) label_top = d_(Str()) label_bottom = d_(Str()) # H, V grid = d_(Tuple(bool, default=(False, False))) grid_alpha = d_(FloatRange(low=0.0, high=1.0, value=0.5)) #: Display a separate axis for each nested plot multi_axis = d_(Bool(True)) axis_left_ticks = d_(Callable()) axis_bottom_ticks = d_(Callable()) #: Display the axis on log scale log_mode = d_(Tuple(bool, default=(False, False))) # x,y #: Enable antialiasing antialias = d_(Bool(False)) #: Set auto range for each axis auto_range = d_( Enum(True, False, (True, True), (True, False), (False, True), (False, False)) ) # x-range to use if auto_range is disabled range_x = d_(ContainerList(default=[0, 100])) #: y-range to use if auto_range is disabled range_y = d_(ContainerList(default=[0, 100])) #: Automatically downsaple auto_downsample = d_(Bool(False)) #: Clip data points to view clip_to_view = d_(Bool(False)) #: Step mode to use step_mode = d_(Bool(False)) #: Keep aspect ratio locked when resizing aspect_locked = d_(Bool(False)) #: Time between updates refresh_time = d_(Int(100)) @observe( "line_pen", "symbol", "symbol_size", "symbol_pen", "symbol_brush", "fill_brush", "fill_level", "multi_axis", "title", "label_left", "label_right", "label_top", "label_bottom", "grid", "grid_alpha", "log_mode", "antialias", "auto_range", "auto_downsample", "clip_to_view", "step_mode", "aspect_locked", "axis_left_ticks", "axis_bottom_ticks", "show_legend", "row", "column", ) def _update_proxy(self, change): """An observer which sends state change to the proxy.""" # The superclass handler implementation is sufficient. super(PlotItem, self)._update_proxy(change) @observe("range_x", "range_y") def _update_range(self, change): """Handle updates and changes""" getattr(self.proxy, "set_%s" % change["name"])(change["value"]) class PlotItem2D(PlotItem): #: x-axis values, as a list x = d_(ContainerList()) #: y-axis values, as a list y = d_(ContainerList()) @observe("x", "y") def _update_proxy(self, change): """An observer which sends state change to the proxy.""" # The superclass handler implementation is sufficient. super(PlotItem2D, self)._update_proxy(change) class PlotItem3D(PlotItem2D): #: z-axis values, as a list z = d_(ContainerList()) @observe("z") def _update_proxy(self, change): """An observer which sends state change to the proxy.""" # The superclass handler implementation is sufficient. super(PlotItem3D, self)._update_proxy(change) class PlotItemArray(PlotItem2D): """Numpy array item""" #: x-axis values, as a numpy array x = d_(ForwardInstance(numpy_ndarray)) #: y-axis values, as a numpy array y = d_(ForwardInstance(numpy_ndarray)) class PlotItemArray3D(PlotItem3D): """Numpy array item""" #: Plot type type = Enum("line") #: x-axis values, as a numpy array x = d_(ForwardInstance(numpy_ndarray)) #: y-axis values, as a numpy array y = d_(ForwardInstance(numpy_ndarray)) #: z-axis values, as a numpy array z = d_(ForwardInstance(numpy_ndarray)) class AbstractDataPlotItem(PlotItem): data = d_(Value()) @observe("data") def _update_proxy(self, change): """An observer which sends state change to the proxy.""" # The superclass handler implementation is sufficient. super(AbstractDataPlotItem, self)._update_proxy(change) class PlotItemList(AbstractDataPlotItem): data = d_(ContainerList()) class PlotItemDict(AbstractDataPlotItem): data = d_(Dict(default={"x": [], "y": []})) enamlx-0.6.4/enamlx/widgets/table_view.py000066400000000000000000000054621456613731400204530ustar00rootroot00000000000000""" Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Jun 3, 2015 """ from atom.api import Bool, ForwardTyped, Int, Typed, observe from enaml.core.declarative import d_ from enamlx.widgets.abstract_item import ( AbstractWidgetItem, AbstractWidgetItemGroup, ProxyAbstractWidgetItem, ProxyAbstractWidgetItemGroup, ) from enamlx.widgets.abstract_item_view import AbstractItemView, ProxyAbstractItemView class ProxyTableView(ProxyAbstractItemView): declaration = ForwardTyped(lambda: TableView) def get_row_count(self): raise NotImplementedError def get_column_count(self): raise NotImplementedError def set_show_grid(self, show): pass class ProxyTableViewRow(ProxyAbstractWidgetItemGroup): declaration = ForwardTyped(lambda: TableViewRow) def set_row(self, row): raise NotImplementedError class ProxyTableViewColumn(ProxyAbstractWidgetItemGroup): declaration = ForwardTyped(lambda: TableViewColumn) def set_column(self, column): raise NotImplementedError class ProxyTableViewItem(ProxyAbstractWidgetItem): declaration = ForwardTyped(lambda: TableViewItem) def data_changed(self, change): raise NotImplementedError class TableView(AbstractItemView): #: Proxy reference proxy = Typed(ProxyTableView) #: Show grid of cells show_grid = d_(Bool(True)) @observe("show_grid") def _update_proxy(self, change): """An observer which sends state change to the proxy.""" if change["name"] == "items": self._update_visible_area() super(TableView, self)._update_proxy(change) def _update_visible_area(self): self.visible_rows = min(100, len(self.items)) self.visible_columns = min(100, len(self.items)) class TableViewItem(AbstractWidgetItem): """The base class implementation is sufficient.""" #: Proxy reference proxy = Typed(ProxyTableViewItem) class TableViewRow(AbstractWidgetItemGroup): """Use this to build a table by defining the rows.""" #: Proxy reference proxy = Typed(ProxyTableViewRow) #: Row within the table row = d_(Int()) @observe("row") def _update_index(self, change): for column, item in enumerate(self._items): item.row = self.row item.column = column class TableViewColumn(AbstractWidgetItemGroup): """Use this to build a table by defining the columns.""" #: Proxy reference proxy = Typed(ProxyTableViewColumn) #: Column within the table column = d_(Int()) @observe("column") def _update_index(self, change): for row, item in enumerate(self._item): item.row = row item.column = self.column enamlx-0.6.4/enamlx/widgets/tree_view.py000066400000000000000000000066361456613731400203270ustar00rootroot00000000000000""" Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Jun 3, 2015 """ from atom.api import Bool, ContainerList, ForwardTyped, Int, Property, Typed, observe from enaml.core.declarative import d_ from enamlx.widgets.abstract_item import ( AbstractWidgetItem, ProxyAbstractWidgetItem, ProxyAbstractWidgetItemGroup, ) from enamlx.widgets.abstract_item_view import AbstractItemView, ProxyAbstractItemView class ProxyTreeView(ProxyAbstractItemView): declaration = ForwardTyped(lambda: TreeView) class ProxyTreeViewColumn(ProxyAbstractWidgetItemGroup): declaration = ForwardTyped(lambda: TreeViewColumn) def set_column(self, column): raise NotImplementedError class ProxyTreeViewItem(ProxyAbstractWidgetItem): declaration = ForwardTyped(lambda: TreeViewItem) def refresh_model(self, schange): raise NotImplementedError class TreeView(AbstractItemView): #: Proxy widget proxy = Typed(ProxyTreeView) #: Show root node show_root = d_(Bool(True)) @observe("show_root") def _update_proxy(self, change): """An observer which sends state change to the proxy.""" # The superclass handler implementation is sufficient. super(TreeView, self)._update_proxy(change) def child_added(self, child): super(TreeView, self).child_added(child) self._update_rows() def child_removed(self, child): super(TreeView, self).child_removed(child) self._update_rows() def _update_rows(self): for r, item in enumerate(self._items): item.row = r class TreeViewItem(AbstractWidgetItem): #: Proxy reference proxy = Typed(ProxyTreeViewItem) #: The child items items = d_(ContainerList(default=[])) #: First visible row visible_row = d_(Int(0)) #: Number of rows visible visible_rows = d_(Int(100)) #: First visible column visible_column = d_(Int(0)) #: Number of columns visible visible_columns = d_(Int(1)) def _get_items(self): """Items should be a list of child TreeViewItems excluding columns. """ return [c for c in self.children if isinstance(c, TreeViewItem)] def _get_columns(self): """List of child TreeViewColumns including this item as the first column """ return [self] + [c for c in self.children if isinstance(c, TreeViewColumn)] #: Columns _columns = Property(lambda self: self._get_columns(), cached=True) def child_added(self, child): super(TreeViewItem, self).child_added(child) self.get_member("_columns").reset(self) self._update_rows() def child_removed(self, child): super(TreeViewItem, self).child_removed(child) self.get_member("_columns").reset(self) self._update_rows() def _update_rows(self): """Update the row and column numbers of child items.""" for row, item in enumerate(self._items): item.row = row # Row is the Parent item item.column = 0 for column, item in enumerate(self._columns): item.row = self.row # Row is the Parent item item.column = column class TreeViewColumn(AbstractWidgetItem): """Use this to build a table by defining the columns.""" #: Proxy reference proxy = Typed(ProxyTreeViewColumn) enamlx-0.6.4/examples/000077500000000000000000000000001456613731400146355ustar00rootroot00000000000000enamlx-0.6.4/examples/double_spin_box.enaml000066400000000000000000000010231456613731400210220ustar00rootroot00000000000000import enamlx enamlx.install() from enaml.widgets.api import Window, Form, Label, Field, SpinBox from enaml.layout.api import hbox, vbox from enamlx.widgets.api import DoubleSpinBox enamldef Main(Window): title = 'DoubleSpinBox Example' Form: Label: lbl: text = 'Value' DoubleSpinBox: sbox: maximum = 100 minimum = 0 decimals = 9 value = 1/25.4 Field: fld: text << u'Value: {}'.format(sbox.value) read_only = True enamlx-0.6.4/examples/graphics_view/000077500000000000000000000000001456613731400174675ustar00rootroot00000000000000enamlx-0.6.4/examples/graphics_view/graphics_interact.enaml000066400000000000000000000064421456613731400242040ustar00rootroot00000000000000import math import enamlx enamlx.install() from enaml.core.api import Looper from enamlx.widgets.api import ( GraphicsView, GraphicsItem, GraphicsTextItem, GraphicsRectItem, GraphicsPolygonItem, GraphicsEllipseItem, GraphicsLineItem, GraphicsPathItem, GraphicsItemGroup, Pen, Brush, Point ) from enaml.widgets.api import ( MainWindow, Container, PushButton, CheckBox, RadioButton, SpinBox, Menu, Action, MenuBar, Feature, ObjectCombo, Form, Label ) from enaml.drag_drop import DragData, DropAction from enaml.qt.QtGui import QPainterPath from enaml.qt.QtCore import Qt def create_drag_data(data): drag = DragData() drag.supported_actions = DropAction.Copy drag.mime_data.set_data('text/plain', data) return drag enamldef Main(MainWindow): window: attr blue_pen = Pen(color='blue') attr red_pen = Pen(color='red') attr green_brush = Brush(color='green') MenuBar: Menu: title = "&File" Action: text = "Quit\tCtrl+Q" triggered :: raise SystemExit(0) Container: Form: Label: text = "Drag mode:" ObjectCombo: items = list(canvas.get_member('drag_mode').items) selected := canvas.drag_mode GraphicsView: canvas: attr size = (960, 960) background = "#fff" drag_mode = 'selection' minimum_size = size attr last_point = None Menu: context_menu = True Action: text = 'Reset' triggered :: canvas.reset_view() mouse_press_event => (evt): self.last_point = evt.pos() mouse_move_event => (evt): if self.last_point is None: self.last_point = evt.pos() if self.selected_items or self.drag_mode == 'selection': return dp = evt.pos()-self.last_point delta = Point(dp.x(), dp.y()) self.last_point = evt.pos() if evt.buttons() in (Qt.MidButton, Qt.LeftButton): px = self.pixel_density() tr = -delta*px self.translate_view(tr.x, tr.y) wheel_event => (evt): if hasattr(evt, 'delta'): d = evt.delta() else: d = evt.angleDelta().y() s = 1.001 ** d self.scale_view(s, s) selected_items :: old = change['oldvalue'] new = change['value'] for item in old: item.pen = blue_pen for item in new: item.pen = red_pen Looper: iterable = range(100) GraphicsTextItem: position = (0, loop_index*20) pen = blue_pen movable = True selectable = True text << "{}".format(position) features = Feature.DragEnabled drag_start => (): return create_drag_data(b"Hello") drag_end => (drag_data, result): print(drag_data, result) enamlx-0.6.4/examples/graphics_view/graphics_items.enaml000066400000000000000000000100351456613731400235050ustar00rootroot00000000000000import math import enamlx enamlx.install() from enaml.core.api import Looper from enamlx.widgets.api import ( GraphicsView, GraphicsItem, GraphicsTextItem, GraphicsRectItem, GraphicsPolygonItem, GraphicsEllipseItem, GraphicsLineItem, GraphicsPathItem, GraphicsItemGroup, GraphicsWidget, Pen, Brush ) from enaml.widgets.api import ( MainWindow, Container, PushButton, CheckBox, RadioButton, SpinBox, Menu, Action, MenuBar ) from enaml.qt.QtGui import QPainterPath from enaml.application import timed_call from random import randint enamldef Main(MainWindow): window: attr blue_pen = Pen(color='blue') attr red_pen = Pen(color='red') attr green_brush = Brush(color='green') initial_size = canvas.size MenuBar: Menu: title = "&File" Action: text = "Quit\tCtrl+Q" triggered :: raise SystemExit(0) Container: padding = 0 GraphicsView: canvas: attr size = (960, 960) background = "#fff" Looper: iterable = range(500) GraphicsTextItem: position = (0, loop_index*20) rotation = loop_index%30 pen = blue_pen if loop_index & 1 else red_pen text << "{}".format(position) GraphicsTextItem: attr delay = 100+10*loop_index attr fade_dir = -0.1 activated :: timed_call(delay, fade_in_and_out) position = (300, loop_index*20, 0) text << "{} {} {}".format(position.x, position.y, position.z) func fade_in_and_out(): if self.opacity <= 0 or self.opacity >= 1: self.fade_dir *= -1 self.opacity -= self.fade_dir timed_call(delay, fade_in_and_out) GraphicsEllipseItem: attr delay = randint(500, 1000) activated :: timed_call(delay, move_around) func move_around(): self.position += (randint(-10, 10), randint(-10, 10)) timed_call(delay, move_around) position = (randint(0, 240), randint(500, 640), 0) opacity = 0.3 pen = blue_pen if loop_index & 1 else red_pen width = 20 height = 30 GraphicsRectItem: brush = green_brush position = (randint(0, 640), randint(0, 640), 0) opacity = 0.3 width = 10 GraphicsWidget: position = (500, 100) rotation = 90.0 PushButton: text = "Click me!" clicked :: parent.position += (0, 10) # Grid GraphicsItemGroup: opacity = 0.05 Looper: iterable = range(100) GraphicsLineItem: position = (10*loop_index, 0) point = (10*loop_index, canvas.size[1], 0) GraphicsLineItem: position = (0, 10*loop_index) point = (canvas.size[0], 10*loop_index, 0) GraphicsPolygonItem: scale = 3 points = [(i, 100+10*math.sin(i)) for i in range(100)] GraphicsPathItem: pen = Pen(color="purple", width=2) scale = 3 movable = True path << redraw(self.position) func redraw(position): x, y, z = position p = QPainterPath() p.moveTo(x, y) p.lineTo(x+10, y) w, h = 10, 10 p.addEllipse(x+10, y-w/2, w, h) p.lineTo(x+10+w+10, y) return p enamlx-0.6.4/examples/graphics_view/main.py000066400000000000000000000004341456613731400207660ustar00rootroot00000000000000import enaml from enaml.qt.qt_application import QtApplication import enamlx if __name__ == "__main__": enamlx.install(allow_def=True) with enaml.imports(): from graphics_items import Main app = QtApplication() view = Main() view.show() app.start() enamlx-0.6.4/examples/images/000077500000000000000000000000001456613731400161025ustar00rootroot00000000000000enamlx-0.6.4/examples/images/icons/000077500000000000000000000000001456613731400172155ustar00rootroot00000000000000enamlx-0.6.4/examples/images/icons/README.md000066400000000000000000000001411456613731400204700ustar00rootroot00000000000000Icons from [http://www.famfamfam.com/lab/icons/silk/](http://www.famfamfam.com/lab/icons/silk/) enamlx-0.6.4/examples/images/icons/bug.png000066400000000000000000000014061456613731400205010ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˝OSQ\^[تtz% l$***2: iRp c0ąKw&&`44(eKɽwwrOX\HiscUQz@;քIdTaˀ)jC'يKT8=ʯ9ނ^zΘ1OFZ[W-Gz?&%*MGnN!aO>Nc[ɨX·0Nqg*1Sub|{g|fz)̾&\ 5\ 0 3i D;`|0>A?Tx4^`oqs`>ʦ`fCv@mX[r\At.)G[ Ì`N1)BWs+:NdsVa*DX.pB&B]H@T3@Pڏڠ wVP63yp-4 Ǽ $H'9{m@U$ZjCX:TgL::?[#{1P=.2F\iA-D 77qXIפb4kaAj% ͼj&Q˫H&s. `jKLE3*ΫX w6_l=@hߊv ,qqIENDB`enamlx-0.6.4/examples/plot_area/000077500000000000000000000000001456613731400166035ustar00rootroot00000000000000enamlx-0.6.4/examples/plot_area/main.py000066400000000000000000000005521456613731400201030ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Aug 23, 2015 @author: jrm """ import enaml from enaml.qt.qt_application import QtApplication import enamlx def main(): enamlx.install() with enaml.imports(): from plot_area import Main app = QtApplication() view = Main() view.show() app.start() if __name__ == "__main__": main() enamlx-0.6.4/examples/plot_area/plot_area.enaml000066400000000000000000000120441456613731400215700ustar00rootroot00000000000000""" Demonstrating the examples from pyqtgraph """ from atom.api import (Atom, Str, Range, List, Bool) from enaml.widgets.api import ( Window, Container, DockArea,DockItem,PushButton, CheckBox, RadioButton ) from enaml.core.looper import Looper from enaml.widgets.spin_box import SpinBox import numpy as np from enamlx.widgets.api import PlotArea,PlotItemArray,PlotItemArray3D from enaml.layout.dock_layout import VSplitLayout, HSplitLayout from enamlx.widgets.double_spin_box import DoubleSpinBox from enaml.widgets.dual_slider import DualSlider enamldef Main(Window): minimum_size = (960,640) Container: padding = 0 DockArea: layout = HSplitLayout( VSplitLayout( 'basic', 'multi', 'range', ), VSplitLayout( 'multi2', 'multi3', 'multi4' ) #'scatter', ) DockItem: name = 'basic' PlotArea: PlotItemArray: title = 'Basic array plotting' y << np.random.normal(size=100) label_left = 'Value' DockItem: name = 'multi' title = 'Multiple curves' PlotArea: PlotItemArray: multi_axis = False label_left = 'A' line_pen = 'g' y << np.random.normal(size=100) PlotItemArray: line_pen = 'b' label_right = 'B' y << np.random.normal(size=100)+10 PlotItemArray: line_pen = 'r' label_right = 'C' y << np.random.normal(size=100)+20 DockItem: name = 'multi2' title = 'Multiple curves with axis' PlotArea: PlotItemArray: multi_axis = False label_left = 'A' line_pen = 'g' y << np.random.normal(size=100) PlotItemArray: line_pen = 'b' label_right = 'B' y << np.random.normal(size=100)+10 PlotItemArray: line_pen = 'r' label_right = 'C' y << np.random.normal(size=100)+20 DockItem: name = 'multi3' title = 'Multiple plot' PlotArea: PlotItemArray: row = 1 label_left = 'A' line_pen = 'g' y << np.random.normal(size=100) PlotItemArray: row = 2 # leave row out to stack however it likes line_pen = 'b' label_left = 'B' y << np.random.normal(size=100)+10 DockItem: name = 'multi4' title = 'Multiple plot' PlotArea: PlotItemArray: label_left = 'A' line_pen = 'g' y << np.random.normal(size=100) PlotItemArray: line_pen = 'b' label_left = 'B' y << np.random.normal(size=100)+10 DockItem: name = 'range' title = 'View range' Container: padding = 0 PlotArea: PlotItemArray: range_plot: auto_range = (False,True) background = 'white' #range_x << (range_slider.low_value,range_slider.high_value) #range_x :: range_slider.low_value,range_slider.high_value = map(int,change['value']) y << np.random.normal(size=100) DualSlider: range_slider: minimum = 0 maximum = 100 low_value << int(range_plot.range_x[0]) low_value :: range_plot.range_x[0] = change['value'] high_value << int(range_plot.range_x[1]) high_value :: range_plot.range_x[1] = change['value'] # DockItem: # name = 'scatter' # Container: # PlotItemArray3D: # TODO, does not work # minimum_size = (360,360) # x = np.linspace(-10,10,100) # y = np.linspace(-10,10,51) # z = np.random.normal(size=100)enamlx-0.6.4/examples/table_view/000077500000000000000000000000001456613731400167565ustar00rootroot00000000000000enamlx-0.6.4/examples/table_view/main.py000066400000000000000000000006331456613731400202560ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Aug 23, 2015 @author: jrm """ import faulthandler import enaml from enaml.qt.qt_application import QtApplication import enamlx def main(): enamlx.install() faulthandler.enable() with enaml.imports(): from table_view import Main app = QtApplication() view = Main() view.show() app.start() if __name__ == "__main__": main() enamlx-0.6.4/examples/table_view/table_view.enaml000066400000000000000000000116601456613731400221210ustar00rootroot00000000000000import os import time import random from threading import Thread from atom.api import (Atom, Str, Range, ContainerList, Bool) from enamlx.widgets.table_view import ( TableView, TableViewRow, TableViewItem ) from enaml.widgets.api import ( Window, Container, PushButton, CheckBox, RadioButton ) from enaml.core.looper import Looper from enaml.widgets.spin_box import SpinBox from enaml.image import Image from enaml.icon import Icon,IconImage from enaml.application import deferred_call from enaml.widgets.menu import Menu from enaml.widgets.action import Action from enamlx.core.looper import ListLooper def icon_path(name): path = os.getcwd() return os.path.join(path,'../','images','icons','%s.png'%name) def load_image(name): with open(icon_path(name),'rb') as f: data = f.read() return Image(data=data) def load_icon(name): img = load_image(name) icg = IconImage(image=img) return Icon(images=[icg]) class Person(Atom): """ A simple class representing a person object. """ last_name = Str() first_name = Str() age = Range(low=0) debug = Bool(False) def __repr__(self, *args, **kwargs): return "Person(first_name={p.first_name},last_name={p.last_name})".format(p=self) class TableModel(Atom): people = ContainerList(Person) def add_person(self): #people = self.people[:] for i in range(100): age = len(self.people) person = Person(last_name='Doe-{}'.format(age),first_name='John-{}'.format(age),age=age) #people.append(person) self.people.insert(0,person) def remove_person(self): #people = self.people[:] #people.pop() self.people.pop() data_model = TableModel(people=[ Person(last_name='Barker-%i'%i, first_name='Bob%i'%i, age=i, debug=bool(i&1)) for i in range(1000000) # 10000 ]) def update_data(): """ Test what happens when we update text!""" while True: time.sleep(1) def update(): person = data_model.people[random.randint(0,10)] print("Updating %s"%person) # Always update in the ui thread! person.debug = not person.debug deferred_call(update) # It flipping works! t = Thread(target=update_data) t.daemon=True t.start() enamldef Main(Window): attr model = data_model Container: PushButton: text = 'Add person' clicked :: model.add_person() PushButton: text = 'Remove person' clicked :: model.remove_person() TableView: table: minimum_size = (640,360) horizontal_headers << ['#','First','Last','Age','Color']#,str(table.visible_rows)] horizontal_stretch = True horizontal_sizes = [10, 100, 100, 20, 30] items << model.people Looper: #: Max number of visible rows #: If this number is too small, stuff will get jacked iterable << range(table.visible_rows) TableViewRow: row << table.visible_row+loop_index attr person << table.items[self.row] clicked :: print("Row %s clicked!"%self.row) Menu: Action: text << 'Row {} menu'.format(table.visible_row+loop_index) triggered :: 'Clicked row' TableViewItem: checkable = True checked := person.debug toggled :: print("toggled %s"%self.row) icon << person.debug and load_icon('bug') or None TableViewItem: text := person.first_name clicked :: print("Clicked %s"%person.first_name) Menu: Action: text << 'Edit {} menu'.format(person.first_name) triggered :: 'Clicked edit' TableViewItem: text << person.last_name double_clicked :: print("Double clicked %s"%person.last_name) selected :: print("Selection %s is now %s"%(person.last_name,change['value'])) TableViewItem: text = 'Loading...' editable = True # TODO: Delegate widgets Not yet implemented SpinBox: value := person.age TableViewItem: text << str(parent.row) foreground << parent.row&1 and 'red' or 'blue' background = 'green' text_alignment = ('center','top') enamlx-0.6.4/examples/test.py000066400000000000000000000027171456613731400161750ustar00rootroot00000000000000from PyQt5.QtGui import QBrush, QColor from PyQt5.QtWidgets import ( QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView, QMainWindow, ) class ChangeableItem(QGraphicsRectItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setBrush(QBrush(QColor("green"))) self.setFlags( QGraphicsRectItem.ItemSendsGeometryChanges | QGraphicsRectItem.ItemIsMovable ) def itemChange(self, change, value): if change == QGraphicsRectItem.ItemPositionHasChanged: print("ChangeableItem", self.pos(), self.scenePos()) return QGraphicsRectItem.itemChange(self, change, value) app = QApplication([]) window = QMainWindow() scene = QGraphicsScene(window) view = QGraphicsView(scene) view.setGeometry(0, 0, 500, 500) scene.setSceneRect(0, 0, 500, 500) rect_good = ChangeableItem(0, 0, 100, 100) rect_bad = QGraphicsRectItem(100, 0, 100, 100) rect_bad.setBrush(QBrush(QColor("blue"))) def patchItemChange(self, change, value): if change == QGraphicsRectItem.ItemPositionHasChanged: print("PatchedChangeableItem", self.pos(), self.scenePos()) # rect_bad.itemChange = Bull.patchItemChange # rect_bad.setFlags(QGraphicsRectItem.ItemSendsGeometryChanges|QGraphicsRectItem.ItemIsMovable) # print(rect_bad.brush().isOpaque()) scene.addItem(rect_good) scene.addItem(rect_bad) window.setCentralWidget(view) window.resize(800, 800) window.show() app.exec_() enamlx-0.6.4/examples/tree_view/000077500000000000000000000000001456613731400166265ustar00rootroot00000000000000enamlx-0.6.4/examples/tree_view/main.py000066400000000000000000000005171456613731400201270ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Aug 23, 2015 @author: jrm """ import enaml from enaml.qt.qt_application import QtApplication import enamlx if __name__ == "__main__": enamlx.install() with enaml.imports(): from tree_view import Main app = QtApplication() view = Main() view.show() app.start() enamlx-0.6.4/examples/tree_view/tree_view.enaml000066400000000000000000000200571456613731400216410ustar00rootroot00000000000000import os from atom.api import (Atom, Str, Range, List, Bool, ForwardInstance) from enamlx.widgets.api import ( TreeView, TreeViewItem, TreeViewColumn, ) from enaml.widgets.api import ( Window, Container, PushButton, CheckBox, RadioButton, SpinBox ) from enaml.core.looper import Looper from enaml.stdlib.mapped_view import MappedView from enaml.image import Image from enaml.icon import Icon,IconImage from enaml.core.dynamic_template import DynamicTemplate def icon_path(name): path = os.getcwd() return os.path.join(path,'../','images','icons','%s.png'%name) def load_image(name): with open(icon_path(name),'rb') as f: data = f.read() return Image(data=data) def load_icon(name): img = load_image(name) icg = IconImage(image=img) return Icon(images=[icg]) class Person(Atom): """ A simple class representing a person object. """ last_name = Str() first_name = Str() children = List(ForwardInstance(lambda:Person)) age = Range(low=0) debug = Bool(False) def __repr__(self): return "Person(first_name={},last_name={})".format(self.first_name,self.last_name) class Model(Atom): people = List(Person) def add_person(self): people = self.people[:] person = Person(last_name='Doe',first_name='John',age=len(self.people)) people.append(person) self.people = people def remove_person(self): people = self.people[:] people.pop() self.people = people template TreeItemLoop(items): """ A templated loop which maps a template over a sequence.""" TreeItemLoop(tuple(items[:-1])): pass AutoTreeItem: item = items[-1] template TreeItemLoop(items: ()): """ The terminating condition for the templated loop.""" pass template AutoTreeItemNode(item): """ Template for tree item nodes. This defines the columns and how the tree will be walked. """ TreeViewColumn: checkable = True checked := item.debug icon << item.debug and load_icon('bug') or None TreeViewColumn: text << item.first_name TreeViewColumn: text << item.last_name TreeViewColumn: SpinBox: value << item.age TreeViewColumn: text << 'parent {}'.format(item.children) foreground << '#CCCCCC' #if parent_index &1 else '#333333' TreeItemLoop(tuple(item.children)): pass enamldef AutoTreeItem(TreeViewItem): attr item text = str(item) items << item.children DynamicTemplate: base = AutoTreeItemNode args = (item,) enamldef Main(Window): attr model = Model(people=[ Person(last_name='Jones-%i'%i, first_name='Bob-%i'%i, age=i, debug=bool(i&1), children=[ Person(last_name='Lill-%i-%i'%(i,j),first_name='Jill-%i-%i'%(i,j),age=i) #Person(last_name='Wack-%i-%i'%(i,j),first_name='Jack-%i-%i'%(i,j),age=i), for j in range(2)] ) for i in range(5)]) Container: PushButton: text = 'Add person' clicked :: model.add_person() PushButton: text = 'Remove person' clicked :: model.remove_person() TreeView: tree: horizontal_headers << ['#','Debug','First','Last','Age','Relationship'] items << model.people Looper: iterable << tree.items AutoTreeItem: item = loop_item # TreeViewItem: # text = '{}'.format(loop_item) # items << loop_item.children # Looper: # iterable << parent.items # TreeViewItem: # text << '{}'.format(loop_item) # Looper: # iterable << range(tree.visible_rows)#min(tree.visible_rows,len(tree.items))) # TreeViewItem: # attr parent_index << tree.visible_row+loop_index # attr person << tree.items[parent_index] # text << '{}'.format(person) # items << person.children # # TreeViewColumn: # checkable = True # checked := person.debug # icon << person.debug and load_icon('bug') or None # TreeViewColumn: # text << person.first_name # TreeViewColumn: # text << person.last_name # TreeViewColumn: # SpinBox: # value << person.age # TreeViewColumn: # text << 'parent {}'.format(person.children) # foreground << '#CCCCCC' if parent_index &1 else '#333333' # # TODO: This should only be run when expanded # Looper: # iterable << range(min(tree.visible_rows,len(parent.items))) # TreeViewItem: # attr child << parent.items[loop_index] # text << '{}'.format(child) # TreeViewColumn: # checkable = True # checked := child.debug # TreeViewColumn: # text << child.first_name # TreeViewColumn: # text << child.last_name # TreeViewColumn: # SpinBox: # value << child.age # TreeViewColumn: # text = 'child' # # TODO: How to go arbitrarily deep? # Looper: # iterable << parent.items if parent.items else [] # TreeViewItem: # attr person << parent.items[loop_index] # text << '{}'.format(person) # items << person.children # # TreeViewColumn: # checkable = True # checked := person.debug # TreeViewColumn: # text << person.first_name # TreeViewColumn: # text << person.last_name # TreeViewColumn: # text = 'Child?' # pass #MappedView: #SpinBox: # value << person.age # TODO: # Looper: # iterable << person.children # TreeViewItem: # Child item # text << 'child %s.%s'%(parent_index,loop_index) # TreeViewColumn: # checkable = True # checked := loop_item.debug # TreeViewColumn: # text << loop_item.first_name # TreeViewColumn: # text << loop_item.last_name # TreeViewColumn: # #text << str(loop_item.age) # SpinBox: # value << loop_item.age # Going arbitrarily deep? enamlx-0.6.4/makefile000066400000000000000000000004621456613731400145210ustar00rootroot00000000000000docs: cd docs make html isort: isort enamlx examples typecheck: mypy enamlx examples --ignore-missing-imports lintcheck: flake8 --ignore=E501 enamlx examples reformat: black enamlx examples test: pytest -v tests --cov enamlx --cov-report xml --asyncio-mode auto precommit: isort reformat lintcheck enamlx-0.6.4/setup.py000066400000000000000000000021361456613731400145330ustar00rootroot00000000000000#!/usr/bin/env python """ Copyright (c) 2015, Jairus Martin. Distributed under the terms of the MIT License. The full license is in the file COPYING.txt, distributed with this software. Created on Mar 2, 2016 """ from setuptools import setup, find_packages setup( name="enamlx", version="0.6.4", description="Additional Qt Widgets for Enaml", long_description=open("README.md").read(), long_description_content_type="text/markdown", author="frmdstryr", author_email="frmdstryr@gmail.com", url="https://github.com/frmdstryr/enamlx", packages=find_packages(), include_package_data=True, install_requires=["enaml"], keywords=["enaml", "qt", "tree", "table", "widgets"], classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Operating System :: OS Independent", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", ], )