class PieChart(QQuickPaintedItem): def __init__(self, parent=None): QQuickPaintedItem.__init__(self, parent) self._name = u'' def paint(self, painter): pen = QPen(self.color, 2) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) def getColor(self): return self._color def setColor(self, value): self._color = value def getName(self): return self._name def setName(self, value): self._name = value nameChanged = Signal() color = Property(QColor, getColor, setColor) name = Property(str, getName, setName, notify=nameChanged)
class PieChart(QQuickPaintedItem): def __init__(self, parent=None): QQuickPaintedItem.__init__(self, parent) self._name = u'' def paint(self, painter): pen = QPen(self.color, 2) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) def getColor(self): return self._color def setColor(self, value): self._color = value def getName(self): return self._name def setName(self, value): self._name = value color = Property(QColor, getColor, setColor) name = Property(str, getName, setName) chartCleared = Signal() @Slot() # This should be something like @Invokable def clearChart(self): self.setColor(Qt.transparent) self.update() self.chartCleared.emit()
class PieSlice(QQuickPaintedItem): def __init__(self, parent=None): QQuickPaintedItem.__init__(self, parent) self._color = QColor() self._fromAngle = 0 self._angleSpan = 0 def getColor(self): return self._color def setColor(self, value): self._color = value def getFromAngle(self): return self._angle def setFromAngle(self, value): self._fromAngle = value def getAngleSpan(self): return self._angleSpan def setAngleSpan(self, value): self._angleSpan = value color = Property(QColor, getColor, setColor) fromAngle = Property(int, getFromAngle, setFromAngle) angleSpan = Property(int, getAngleSpan, setAngleSpan) def paint(self, painter): pen = QPen(self._color, 2) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16)
class ClassFilterProxyModel(QSortFilterProxyModel, QObject): _changed_front_page_ = Signal(bool) _changed_filter_class_ = Signal(str) _front_page_ = True _filter_class_ = 'all' def __init__(self): super().__init__() def get_front_page(self): return self._front_page_ def set_front_page(self, fp): if self._front_page_ != fp: self._front_page_ = fp self._changed_front_page_.emit(self._front_page_) self.invalidateFilter() def get_filter_class(self): return self._filter_class_ def set_filter_class(self, new_filter): if self._filter_class_ != new_filter: # _logger.debug(f'class filter: set_filter_class {new_filter}') self._filter_class_ = new_filter self._changed_filter_class_.emit(self._filter_class_) self.invalidateFilter() front_page = Property(bool, get_front_page, set_front_page, notify=_changed_front_page_) filter_class = Property(str, get_filter_class, set_filter_class, notify=_changed_filter_class_) def filterAcceptsRow(self, source_row, source_parent: QModelIndex): index = self.sourceModel().index(source_row, 0, source_parent) if self.front_page: return True else: if self._filter_class_ == 'all': return True else: device_class_id = index.data(DeviceRoles.DEVICE_CLASS_ID) cb_filter = device_class_id == self._filter_class_ return cb_filter
class OptionalCurrencyComboBox(QWidget): changed = Signal() name_updated = Signal(str) def __init__(self, parent): QWidget.__init__(self, parent) self._id = 0 self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.null_flag = QCheckBox(parent) self.null_flag.setChecked(False) self.null_flag.setText(self.tr("Currency")) self.layout.addWidget(self.null_flag) self.currency = CurrencyComboBox(parent) self.currency.setEnabled(False) self.layout.addWidget(self.currency) self.setLayout(self.layout) self.setFocusProxy(self.null_flag) self.null_flag.clicked.connect(self.onClick) self.currency.changed.connect(self.onCurrencyChange) def setText(self, text): self.null_flag.setText(text) def getId(self): return self._id if self._id else None def setId(self, new_value): if self._id == new_value: return self._id = new_value self.updateView() name = JalDB().get_asset_name(self._id) self.name_updated.emit('' if name is None else name) currency_id = Property(int, getId, setId, notify=changed, user=True) def updateView(self): has_value = True if self._id else False if has_value: self.currency.selected_id = self._id self.null_flag.setChecked(has_value) self.currency.setEnabled(has_value) @Slot() def onClick(self): if self.null_flag.isChecked(): if self.currency.selected_id == 0: self.currency.selected_id = JalSettings().getValue('BaseCurrency') self.currency_id = self.currency.selected_id else: self.currency_id = 0 self.changed.emit() @Slot() def onCurrencyChange(self, _id): self.currency_id = self.currency.selected_id self.changed.emit()
def ConstProperty(classvars, typename, name): ''' This function adds a QProperty named 'name' to a class's vars() dictionary. It create the getter. *Important* a member variable named '_name' will be expected by the getter. A QProperty is exposed to QML. ''' goc_member_variable(classvars, name) classvars[f'{name}'] = Property(typename, Getter(name), constant = True)
class OrbitTransformController(QObject): def __init__(self, parent): super(OrbitTransformController, self).__init__(parent) self._target = None self._matrix = QMatrix4x4() self._radius = 1 self._angle = 0 def setTarget(self, t): self._target = t def getTarget(self): return self._target def setRadius(self, radius): if self._radius != radius: self._radius = radius self.updateMatrix() self.radiusChanged.emit() def getRadius(self): return self._radius def setAngle(self, angle): if self._angle != angle: self._angle = angle self.updateMatrix() self.angleChanged.emit() def getAngle(self): return self._angle def updateMatrix(self): self._matrix.setToIdentity() self._matrix.rotate(self._angle, QVector3D(0, 1, 0)) self._matrix.translate(self._radius, 0, 0) if self._target is not None: self._target.setMatrix(self._matrix) angleChanged = Signal() radiusChanged = Signal() angle = Property(float, getAngle, setAngle, notify=angleChanged) radius = Property(float, getRadius, setRadius, notify=radiusChanged)
def RWProperty(classvars, typename, name, callback = None): ''' This function adds a QProperty named 'name' to a class's vars() dictionary. It create the getter, setter, and signal named 'nameChanged'. *Important* a member variable named '_name' will be expected by the getter and setter. A QProperty is exposed to QML. ''' goc_member_variable(classvars, name) notify = classvars[f'{name}Changed'] = Signal() classvars[f'{name}'] = Property(typename, Getter(name), Setter(name, callback, classvars), notify = notify)
def ROProperty(classvars, typename, name, callback = None): ''' This function adds a QProperty named 'name' to a class's vars() dictionary. It creates the getter, and signal named 'nameChanged'. It also creates a set_name() setter outside of the Qt property system. *Important* a member variable named '_name' will be expected by the getter. A QProperty is exposed to QML. ''' goc_member_variable(classvars, name) notify = classvars[f'{name}Changed'] = Signal() classvars[f'{name}'] = Property(typename, Getter(name), notify = notify) classvars[f'set_{name}'] = Setter(name)
class PieChart(QQuickItem): def __init__(self, parent=None): QQuickItem.__init__(self, parent) self._name = None self._pieSlice = None def getName(self): return self._name def setName(self, value): self._name = value name = Property(str, getName, setName) def getPieSlice(self): return self._pieSlice def setPieSlice(self, value): self._pieSlice = value self._pieSlice.setParentItem(self) pieSlice = Property(PieSlice, getPieSlice, setPieSlice)
def InputProperty(classvars, typename, name, callback = None): ''' This function adds a QProperty named 'name' to a class's vars() dictionary. It create the getter, setter, and signal named 'nameChanged'. *Important* a member variable named '_name' will be expected by the getter and setter. 'callback' will be called if (and only if) a new value is set. see InputSetter for more information on 'callback' A QProperty is exposed to QML. An InputProperty is a property that turns a product dirty when needed. It can be a primitive type (e.g. int, string, bool, etc) or a Product, or a collection containing products ''' goc_member_variable(classvars, name) notify = classvars[f'{name}Changed'] = Signal() classvars[f'{name}'] = Property(typename, Getter(name), InputSetter(name, callback, classvars), notify = notify)
class CurrencyComboBox(QComboBox): changed = Signal(int) def __init__(self, parent): QComboBox.__init__(self, parent) self.p_selected_id = 0 self.model = None self.activated.connect(self.OnUserSelection) self.query = QSqlQuery(db=db_connection()) self.query.prepare(f"SELECT id, symbol FROM currencies") self.query.exec() self.model = QSqlTableModel(db=db_connection()) self.model.setQuery(self.query) self.model.select() self.setModel(self.model) self.setModelColumn(self.model.fieldIndex("symbol")) def isCustom(self): return True def getId(self): return self.p_selected_id def setId(self, new_id): if self.p_selected_id == new_id: return self.p_selected_id = new_id name = readSQL("SELECT symbol FROM currencies WHERE id=:id", [(":id", self.p_selected_id)]) if self.currentIndex() == self.findText(name): return self.setCurrentIndex(self.findText(name)) selected_id = Property(int, getId, setId, notify=changed, user=True) def setIndex(self, index): if index is not None: self.selected_id = index self.changed.emit(self.selected_id) @Slot() def OnUserSelection(self, _selected_index): self.selected_id = self.model.record(self.currentIndex()).value("id") self.changed.emit(self.selected_id)
class PieChart(QQuickItem): def __init__(self, parent=None): QQuickItem.__init__(self, parent) self._name = u'' self._slices = [] def getName(self): return self._name def setName(self, value): self._name = value name = Property(str, getName, setName) def appendSlice(self, _slice): _slice.setParentItem(self) self._slices.append(_slice) slices = ListProperty(PieSlice, appendSlice)
class PieSlice(QQuickPaintedItem): def __init__(self, parent=None): QQuickPaintedItem.__init__(self, parent) self._color = QColor() def getColor(self): return self._color def setColor(self, value): self._color = value color = Property(QColor, getColor, setColor) def paint(self, painter): pen = QPen(self._color, 2) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16)
class AccountButton(QPushButton): changed = Signal(int) def __init__(self, parent): QPushButton.__init__(self, parent) self.p_account_id = 0 self.Menu = QMenu(self) self.Menu.addAction(self.tr("Choose account"), self.ChooseAccount) self.Menu.addAction(self.tr("Any account"), self.ClearAccount) self.setMenu(self.Menu) self.dialog = AccountListDialog() self.setText(self.dialog.SelectedName) def getId(self): return self.p_account_id def setId(self, account_id): self.p_account_id = account_id if self.p_account_id: self.setText(JalDB().get_account_name(account_id)) else: self.setText(self.tr("ANY")) self.changed.emit(self.p_account_id) account_id = Property(int, getId, setId, notify=changed) def ChooseAccount(self): ref_point = self.mapToGlobal(self.geometry().bottomLeft()) self.dialog.setGeometry(ref_point.x(), ref_point.y(), self.dialog.width(), self.dialog.height()) self.dialog.setFilter() res = self.dialog.exec(enable_selection=True) if res: self.account_id = self.dialog.selected_id def ClearAccount(self): self.account_id = 0
class ClickModel(QObject): """ClickModel is the model class for the GUI. It holds the counter property and handles event generated by the click on the button.""" def __init__(self): # Initialize the parent object. If omitted, GUI will not start QObject.__init__(self) # Initialize the counter internal value. Because we propagate count as # a property to QML, getter, setter and notifier must be made self._count = 0 def get_count(self): """Getter for the count property""" return self._count def set_count(self, val): """Setter for the count property""" print("Current: {}, new: {}".format(self._count, val)) # We set new value and notify of change only if the value # is really changed. if val != self._count: # Change internal value self._count = val # Notify the GUI that the value had changed self.counter_changed.emit() # Declare a notification method counter_changed = Signal() # Add a new property to ClickModel object. It can be used as an attribute # from Python. count = Property(int, get_count, set_count, notify=counter_changed) @Slot() def increase(self): """Handler for the button click. Increases counter by one.""" print("Increasing") # Use property as an attribute. Setter is called automatically and # notifies the GUI about the changed value. self.count = self.count + 1
class DbLookupComboBox(QComboBox): def __init__(self, parent=None): QComboBox.__init__(self, parent) self._model = None self._table = '' self._key_field = '' self._field = '' self._selected_id = -1 def getKey(self): return readSQL( f"SELECT {self._key_field} FROM {self._table} WHERE {self._field}='{self.currentText()}'" ) def setKey(self, selected_id): if self._selected_id == selected_id: return self._selected_id = selected_id value = readSQL( f"SELECT {self._field} FROM {self._table} WHERE {self._key_field}={selected_id}" ) self.setCurrentIndex(self.findText(value)) key = Property(int, getKey, setKey, user=True) def setupDb(self, table, key_field, field): self._table = table self._field = field self._key_field = key_field self._model = QSqlTableModel(parent=self, db=db_connection()) self._model.setTable(table) field_idx = self._model.fieldIndex(field) self._model.setSort(field_idx, Qt.AscendingOrder) self._model.select() self.setModel(self._model) self.setModelColumn(field_idx)
def Q_ENUMS_mock(classvars, enumclass): #do not use, PySide2 workaround values = [a for a in dir(enumclass) if not a.startswith('__') and not callable(getattr(enumclass,a))] for v in values: classvars[f'{v}'] = Property(int, ConstGetter(getattr(enumclass,v)), constant = True)
class DevicesModel(QAbstractListModel, QObject): """ List model that accesses the devices for the view """ def __init__(self, args, env: (ToolEnvironmentObject, None)): super().__init__() self.args = args self.env = env self._device_count_ = self.env.devices.device_count self._category_ = 'Ultimarc Configurations' self._ui_dev_info_ = [] self.setup_info() def setup_info(self): """ setup up meta data for the devices and configurations """ for dev in self.get_devices(): tmp = UIDeviceInfo(product_name=dev.product_name, device_class=dev.class_descr, product_key=dev.dev_key) tmp.setup_icon(dev.class_id) self._ui_dev_info_.append(tmp) # Configuration for non connected devices for device_class in DeviceClassID: tmp = UIDeviceInfo(False, device_class=device_class.name) tmp.setup_icon(device_class.value) self._ui_dev_info_.append(tmp) def get_devices(self): """ Return a list of devices we should show information for. """ return self.env.devices.filter() def roleNames(self): """ Just return the DeviceRolePropertyMap dict, but convert key values to byte arrays first for QT. """ # TODO: Add device information to role dict roles = OrderedDict() for k, v in DeviceRolePropertyMap.items(): roles[k] = v.encode('utf-8') return roles def rowCount(self, parent): if parent.isValid(): return 0 return len(self._ui_dev_info_) def data(self, index: QModelIndex, role): if not index.isValid(): return None if role == DeviceRoles.CATEGORY: return 'main' if index.row() < self._device_count_ else self._category_ for x in range(len(self._ui_dev_info_)): if x == index.row(): dev_cls = self._ui_dev_info_[x] return getattr(dev_cls, DeviceRolePropertyMap[role]) return None def setData(self, index: QModelIndex, value, role: int = ...): # TODO: Implement for writing GUI -> Device return False def get_category(self): return self._category_ def get_device_count(self): return self._device_count_ if self._device_count_ < 4 else 4 device_count = Property(int, get_device_count, constant=True) category = Property(str, get_category, constant=True)
class DateRangeSelector(QWidget): changed = Signal(int, int) # emits signal when one or both dates were changed, "from" and "to" timestamps are sent def __init__(self, parent): QWidget.__init__(self, parent) self.report_ranges = { 'week': (self.tr("Week"), ManipulateDate.PreviousWeek), 'month': (self.tr("Month"), ManipulateDate.PreviousMonth), 'quarter': (self.tr("Quarter"), ManipulateDate.PreviousQuarter), 'year': (self.tr("Year"), ManipulateDate.PreviousYear), 'QTD': (self.tr("Quarter to date"), ManipulateDate.QuarterToDate), 'YTD': (self.tr("Year to date"), ManipulateDate.YearToDate), 'this_year': (self.tr("This year"), ManipulateDate.ThisYear), 'last_year': (self.tr("Previous year"), ManipulateDate.LastYear), 'all': (self.tr("All dates"), ManipulateDate.AllDates), } self._begin = 0 self._end = 0 self._items = [] self.changing_range = False self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.range_combo = QComboBox(self) self.layout.addWidget(self.range_combo) self.from_label = QLabel(self.tr("From:"), parent=self) self.layout.addWidget(self.from_label) self.from_date = QDateEdit() self.from_date.setDisplayFormat("dd/MM/yyyy") self.from_date.setCalendarPopup(True) self.from_date.setTimeSpec(Qt.UTC) self.layout.addWidget(self.from_date) self.from_label = QLabel(self.tr("To:"), parent=self) self.layout.addWidget(self.from_label) self.to_date = QDateEdit() self.to_date.setDisplayFormat("dd/MM/yyyy") self.to_date.setCalendarPopup(True) self.to_date.setTimeSpec(Qt.UTC) self.layout.addWidget(self.to_date) self.setLayout(self.layout) self.setFocusProxy(self.range_combo) self.connect_signals_and_slots() def getConfig(self): return ';'.join(self._items) def setConfig(self, items_list): try: self._items = items_list.split(';') except AttributeError: self._items = [] for item in self._items: try: item_name = self.report_ranges[item][ITEM_NAME] self.range_combo.addItem(item_name, item) except KeyError: continue ItemsList = Property(str, getConfig, setConfig) def connect_signals_and_slots(self): self.range_combo.currentIndexChanged.connect(self.onRangeChange) self.from_date.dateChanged.connect(self.onFromChange) self.to_date.dateChanged.connect(self.onToChange) @Slot() def onRangeChange(self, index): item = self.range_combo.itemData(index) self._begin, self._end = self.report_ranges[item][ITEM_METHOD]() self.changing_range = True self.from_date.setDateTime(QDateTime.fromSecsSinceEpoch(self._begin, spec=Qt.UTC)) self.to_date.setDateTime(QDateTime.fromSecsSinceEpoch(self._end, spec=Qt.UTC)) self.changing_range = False self.changed.emit(self._begin, self._end) @Slot() def onFromChange(self): self._begin = self.from_date.date().startOfDay(Qt.UTC).toSecsSinceEpoch() if not self.changing_range: self.changed.emit(self._begin, self._end) @Slot() def onToChange(self): self._end = self.to_date.date().startOfDay(Qt.UTC).toSecsSinceEpoch() if not self.changing_range: self.changed.emit(self._begin, self._end) def setCurrentIndex(self, index): if index == self.range_combo.currentIndex(): self.onRangeChange(index) else: self.range_combo.setCurrentIndex(index)
class ReferenceDataDialog(QDialog, Ui_ReferenceDataDialog): # tree_view - table will be displayed as hierarchical tree with help of 2 columns: 'id', 'pid' in sql table def __init__(self): QDialog.__init__(self) self.setupUi(self) self.model = None self._previous_row = -1 self.selected_id = 0 self.p_selected_name = '' self._filter_text = '' self.selection_enabled = False self.group_id = None self.group_key_field = None self.group_key_index = None self.group_fkey_field = None self.filter_field = None self._filter_value = '' self.toggle_state = False self.toggle_field = None self.search_field = None self.search_text = "" self.tree_view = False self.toolbar = None self.custom_editor = False self.AddChildBtn.setVisible(False) self.GroupLbl.setVisible(False) self.GroupCombo.setVisible(False) self.SearchFrame.setVisible(False) self.AddBtn.setIcon(load_icon("add.png")) self.AddChildBtn.setIcon(load_icon("add_child.png")) self.RemoveBtn.setIcon(load_icon("delete.png")) self.CommitBtn.setIcon(load_icon("accept.png")) self.RevertBtn.setIcon(load_icon("cancel.png")) self.SearchString.textChanged.connect(self.OnSearchChange) self.GroupCombo.currentIndexChanged.connect(self.OnGroupChange) self.Toggle.stateChanged.connect(self.OnToggleChange) self.AddBtn.clicked.connect(self.OnAdd) self.AddChildBtn.clicked.connect(self.OnChildAdd) self.RemoveBtn.clicked.connect(self.OnRemove) self.CommitBtn.clicked.connect(self.OnCommit) self.RevertBtn.clicked.connect(self.OnRevert) self.DataView.doubleClicked.connect(self.OnDoubleClicked) self.DataView.clicked.connect(self.OnClicked) self.TreeView.doubleClicked.connect(self.OnDoubleClicked) self.TreeView.clicked.connect(self.OnClicked) def _init_completed(self): self.DataView.setVisible(not self.tree_view) self.TreeView.setVisible(self.tree_view) if self.tree_view: self.TreeView.selectionModel().selectionChanged.connect( self.OnRowSelected) else: self.DataView.selectionModel().selectionChanged.connect( self.OnRowSelected) self.DataView.setContextMenuPolicy(Qt.CustomContextMenu) self.DataView.customContextMenuRequested.connect( self.onDataViewContextMenu) self.model.dataChanged.connect(self.OnDataChanged) self.setFilter() def onDataViewContextMenu(self, pos): if not self.group_id: return index = self.DataView.indexAt(pos) menu_title = QWidgetAction(self.DataView) title_lbl = QLabel() title_lbl.setText(self.tr("Change type to:")) menu_title.setDefaultWidget(title_lbl) contextMenu = QMenu(self.DataView) contextMenu.addAction(menu_title) contextMenu.addSeparator() combo_model = self.GroupCombo.model() for i in range(self.GroupCombo.count()): type_id = combo_model.data( combo_model.index( i, combo_model.fieldIndex(self.group_fkey_field))) contextMenu.addAction(self.GroupCombo.itemText(i), partial(self.updateItemType, index, type_id)) contextMenu.popup(self.DataView.viewport().mapToGlobal(pos)) @Slot() def updateItemType(self, index, new_type): self.model.updateItemType(index, new_type) self.CommitBtn.setEnabled(True) self.RevertBtn.setEnabled(True) @Slot() def closeEvent(self, event): if self.CommitBtn.isEnabled( ): # There are uncommitted changed in a table if QMessageBox().warning( self, self.tr("Confirmation"), self.tr( "You have uncommitted changes. Do you want to close?"), QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: event.ignore() return else: self.model.revertAll() event.accept() # Overload ancestor method to activate/deactivate filters for table view def exec(self, enable_selection=False, selected=0): self.selection_enabled = enable_selection self.setFilter() if enable_selection: self.locateItem(selected) res = super().exec() self.resetFilter() return res def getSelectedName(self): if self.selected_id == 0: return self.tr("ANY") else: return self.p_selected_name def setSelectedName(self, selected_id): pass @Signal def selected_name_changed(self): pass SelectedName = Property(str, getSelectedName, setSelectedName, notify=selected_name_changed) @Slot() def OnDataChanged(self): self.CommitBtn.setEnabled(True) self.RevertBtn.setEnabled(True) @Slot() def OnAdd(self): if self.custom_editor: editor = self.customEditor() editor.createNewRecord() editor.exec() self.model.select( ) # TODO better to make self.beginInsertRows/endInsertRows else: if self.tree_view: idx = self.TreeView.selectionModel().selection().indexes() else: idx = self.DataView.selectionModel().selection().indexes() current_index = idx[0] if idx else self.model.index(0, 0) self.model.addElement(current_index, in_group=self.group_id) self.CommitBtn.setEnabled(True) self.RevertBtn.setEnabled(True) @Slot() def OnChildAdd(self): if self.tree_view: idx = self.TreeView.selectionModel().selection().indexes() current_index = idx[0] if idx else self.model.index(0, 0) self.model.addChildElement(current_index) self.CommitBtn.setEnabled(True) self.RevertBtn.setEnabled(True) @Slot() def OnRemove(self): if self.tree_view: idx = self.TreeView.selectionModel().selection().indexes() else: idx = self.DataView.selectionModel().selection().indexes() current_index = idx[0] if idx else self.model.index(0, 0) self.model.removeElement(current_index) self.CommitBtn.setEnabled(True) self.RevertBtn.setEnabled(True) @Slot() def OnCommit(self): if not self.model.submitAll(): return self.CommitBtn.setEnabled(False) self.RevertBtn.setEnabled(False) @Slot() def OnRevert(self): self.model.revertAll() self.CommitBtn.setEnabled(False) self.RevertBtn.setEnabled(False) def setFilterValue(self, filter_value): if self.filter_field is None: return self._filter_value = filter_value self.setFilter() def resetFilter(self): self.model.setFilter("") def setFilter(self): conditions = [] if self.search_text and self.search_field is not None: search = self.search_field.split('-') if len(search) == 1: # Simple search by given text field conditions.append( f"{self.search_field} LIKE '%{self.search_text}%'") elif len(search ) == 4: # Complex search by relation from another table # Here search[0] is a field in current table that binds with search[2] field in lookup table search[1] # search[3] is a name in lookup table which is used for searching. # I.e. self.search_field has format: f_key-lookup_table_name-lookup_id-lookup_field conditions.append( f"{search[0]} IN (SELECT {search[2]} FROM {search[1]} " f"WHERE {search[3]} LIKE '%{self.search_text}%')") else: assert False, f"Unsupported format of search field: {self.search_field}" if self.group_id: conditions.append( f"{self.table}.{self.group_key_field}={self.group_id}") if self.filter_field is not None and self._filter_value: conditions.append( f"{self.table}.{self.filter_field} = {self._filter_value}") # completion model needs only this filter, others are for dialog self.model.completion_model.setFilter( f"{self.table}.{self.filter_field} = {self._filter_value}") if self.toggle_field: if not self.toggle_state: conditions.append(f"{self.table}.{self.toggle_field}=1") self._filter_text = "" for line in conditions: self._filter_text += line + " AND " self._filter_text = self._filter_text[:-len(" AND ")] self.model.setFilter(self._filter_text) @Slot() def OnSearchChange(self): self.search_text = self.SearchString.text() self.setFilter() @Slot() def OnRowSelected(self, selected, _deselected): idx = selected.indexes() if idx: self.selected_id = self.model.getId(idx[0]) self.p_selected_name = self.model.getName(idx[0]) @Slot() def OnClicked(self, index): if self.custom_editor: if self._previous_row == index.row(): editor = self.customEditor() editor.selected_id = self.selected_id editor.exec() else: self._previous_row = index.row() @Slot() def OnDoubleClicked(self, index): self.selected_id = self.model.getId(index) self.p_selected_name = self.model.getName(index) if self.selection_enabled: self.setResult(QDialog.Accepted) self.close() @Slot() def OnGroupChange(self, list_id): self.OnRevert() # Discard all possible changes model = self.GroupCombo.model() self.group_id = model.data( model.index(list_id, model.fieldIndex(self.group_fkey_field))) self.setFilter() @Slot() def OnToggleChange(self, state): if state == 0: self.toggle_state = False else: self.toggle_state = True self.setFilter() def locateItem(self, item_id): raise NotImplementedError( "locateItem() method is not defined in subclass ReferenceDataDialog" )
class AssetDialog(QDialog, Ui_AssetDialog): def __init__(self): QDialog.__init__(self) self.setupUi(self) self._asset_id = -1 # Custom model to allow common submit errors handling and error message display self._model = AssetsListModel("assets", self) self._mapper = QDataWidgetMapper(self._model) self._mapper.setModel(self._model) self._mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) self._mapper.addMapping(self.NameEdit, self._model.fieldIndex("full_name")) self._mapper.addMapping(self.isinEdit, self._model.fieldIndex("isin")) self._mapper.addMapping(self.TypeCombo, self._model.fieldIndex("type_id")) self._mapper.addMapping(self.CountryCombo, self._model.fieldIndex("country_id")) self._mapper.addMapping(self.BaseAssetSelector, self._model.fieldIndex("base_asset")) self._model.select() self._symbols_model = SymbolsListModel("asset_tickers", self.SymbolsTable) self.SymbolsTable.setModel(self._symbols_model) self._symbols_model.select() self._symbols_model.configureView() self._data_model = ExtraDataModel("asset_data", self.DataTable) self.DataTable.setModel(self._data_model) self._data_model.select() self._data_model.configureView() self.AddSymbolButton.setIcon(load_icon("add.png")) self.RemoveSymbolButton.setIcon(load_icon("delete.png")) self.AddDataButton.setIcon(load_icon("add.png")) self.RemoveDataButton.setIcon(load_icon("delete.png")) self.OkButton.setIcon(load_icon("accept.png")) self.CancelButton.setIcon(load_icon("cancel.png")) self.TypeCombo.currentIndexChanged.connect(self.onTypeUpdate) self.AddSymbolButton.clicked.connect(self.onAddSymbol) self.RemoveSymbolButton.clicked.connect(self.onRemoveSymbol) self.AddDataButton.clicked.connect(self.onAddData) self.RemoveDataButton.clicked.connect(self.onRemoveData) def getSelectedId(self): return self._asset_id def setSelectedId(self, asset_id): self._asset_id = asset_id self._model.setFilter(f"id={self._asset_id}") self._mapper.toFirst() self._symbols_model.filterBy("asset_id", self._asset_id) self._data_model.filterBy("asset_id", self._asset_id) self.onTypeUpdate( 0) # need to update manually as it isn't triggered from mapper selected_id = Property(str, getSelectedId, setSelectedId) def createNewRecord(self): self._asset_id = 0 self._model.setFilter(f"id={self._asset_id}") new_record = self._model.record() new_record.setNull("id") assert self._model.insertRows(0, 1) self._model.setRecord(0, new_record) self._mapper.toLast() self._symbols_model.filterBy("asset_id", self._asset_id) self._data_model.filterBy("asset_id", self._asset_id) def accept(self) -> None: if not self._model.submitAll(): return asset_id = self._model.data( self._model.index(0, self._model.fieldIndex("id"))) if asset_id is None: # we just have saved new asset record and need last inserted id asset_id = self._model.query().lastInsertId() for model in [self._data_model, self._symbols_model]: for row in range(model.rowCount()): model.setData(model.index(row, model.fieldIndex("asset_id")), asset_id) if not model.submitAll(): return super().accept() def reject(self) -> None: for model in [self._data_model, self._symbols_model, self._model]: model.revertAll() super().reject() def onTypeUpdate(self, _index): if self.TypeCombo.key == PredefinedAsset.Derivative: self.BaseAssetSelector.setEnabled(True) self.isinEdit.setEnabled(False) elif self.TypeCombo.key == PredefinedAsset.Money or self.TypeCombo.key == PredefinedAsset.Commodity: self.BaseAssetSelector.setEnabled(False) self.isinEdit.setEnabled(False) else: self.BaseAssetSelector.setEnabled(False) self.isinEdit.setEnabled(True) def onAddSymbol(self): idx = self.SymbolsTable.selectionModel().selection().indexes() current_index = idx[0] if idx else self._symbols_model.index(0, 0) self._symbols_model.addElement(current_index) def onRemoveSymbol(self): idx = self.SymbolsTable.selectionModel().selection().indexes() current_index = idx[0] if idx else self._symbols_model.index(0, 0) self._symbols_model.removeElement(current_index) def onAddData(self): idx = self.DataTable.selectionModel().selection().indexes() current_index = idx[0] if idx else self._data_model.index(0, 0) self._data_model.addElement(current_index) def onRemoveData(self): idx = self.DataTable.selectionModel().selection().indexes() current_index = idx[0] if idx else self._data_model.index(0, 0) self._data_model.removeElement(current_index)
class AbstractReferenceSelector(QWidget): changed = Signal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.completer = None self.p_selected_id = 0 self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.name = QLineEdit() self.name.setText("") self.layout.addWidget(self.name) self.details = QLabel() self.details.setText("") self.details.setVisible(False) self.layout.addWidget(self.details) self.button = QPushButton("...") self.button.setFixedWidth( self.button.fontMetrics().horizontalAdvance("XXXX")) self.layout.addWidget(self.button) self.setLayout(self.layout) self.setFocusProxy(self.name) self.button.clicked.connect(self.on_button_clicked) if self.details_field: self.name.setFixedWidth( self.name.fontMetrics().horizontalAdvance("X") * 15) self.details.setVisible(True) self.completer = QCompleter(self.dialog.model.completion_model) self.completer.setCompletionColumn( self.dialog.model.completion_model.fieldIndex(self.selector_field)) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.name.setCompleter(self.completer) self.completer.activated[QModelIndex].connect(self.on_completion) def getId(self): return self.p_selected_id def setId(self, selected_id): if self.p_selected_id == selected_id: return self.p_selected_id = selected_id self.name.setText( self.dialog.model.getFieldValue(selected_id, self.selector_field)) if self.details_field: self.details.setText( self.dialog.model.getFieldValue(selected_id, self.details_field)) selected_id = Property(int, getId, setId, notify=changed, user=True) def setFilterValue(self, filter_value): self.dialog.setFilterValue(filter_value) def on_button_clicked(self): ref_point = self.mapToGlobal(self.name.geometry().bottomLeft()) self.dialog.setGeometry(ref_point.x(), ref_point.y(), self.dialog.width(), self.dialog.height()) res = self.dialog.exec(enable_selection=True, selected=self.selected_id) if res: self.selected_id = self.dialog.selected_id self.changed.emit() @Slot(QModelIndex) def on_completion(self, index): model = index.model() self.selected_id = model.data(model.index(index.row(), 0), Qt.DisplayRole) self.changed.emit() def isCustom(self): return True
class WelcomePage(QWizardPage): butt_signal = Signal(str) # Make a signal, pass it def __init__(self, parent=None): QWizardPage.__init__(self, parent) self.setTitle("Select the data source(s) to analyze:") self.label = QLabel("<b>Images are not required.</b><br> However, if they"+ " are not present, then bounded metrics will be calculated from the bounding rectangle" " of the provided coordinates.") self.label.setWordWrap(True) self.label.setTextFormat(Qt.RichText) self.label.setAlignment(Qt.AlignCenter) self.file_dir_form = QGridLayout() self.coord_label = QLineEdit() self.coord_label.setReadOnly(True) self.coord_label.text() self.coord_butt = QPushButton("Select...") self._coord_path = "" self.image_label = QLineEdit() self.image_label.setReadOnly(True) #self.image_label.setMaximumWidth() self.image_butt = QPushButton("Select...") self.image_path = "" self.file_dir_form.addWidget(self.coord_label, 0, 0) self.file_dir_form.addWidget(self.coord_butt, 0, 1) self.file_dir_form.addWidget(self.image_label, 1, 0) self.file_dir_form.addWidget(self.image_butt, 1, 1) self.v_layout = QVBoxLayout() self.v_layout.setSpacing(32) self.v_layout.addWidget(self.label) self.v_layout.addLayout(self.file_dir_form) self.file_dir_form.setSpacing(4) self.setLayout(self.v_layout) self.butt_signal self.coord_butt.clicked.connect(self.select_coord_path) self.image_butt.clicked.connect(self.select_image_path) def readCoordPath(self): return self._coord_path def setCoordPath(self, val): self._coord_path = val coord_path = Property(str, readCoordPath, setCoordPath, notify=butt_signal) @Slot() def select_coord_path(self): self._coord_path = QFileDialog.getExistingDirectory(parent=self, caption="Select the folder containing the coordinates of interest.", options=QFileDialog.ShowDirsOnly) @Slot() def select_image_path(self): self.image_path = QFileDialog.getExistingDirectory(parent=self, caption="Select the folder containing the images of interest.", options=QFileDialog.ShowDirsOnly)
class Units(QObject): _changed_grid_unit = Signal(float) _changed_spacing = Signal() def __init__(self): super().__init__() self._grid_unit = -1.0 self._small_spacing = -1.0 self._large_spacing = -1.0 self._device_pixel_patio = -1.0 self.update() self.update_device_pixel_ratio() def eventFilter(self, watched, event): if watched == QCoreApplication.instance(): if event.type() == QEvent.ApplicationFontChange: self.update() return QObject.eventFilter(watched, event) def update(self): grid_unit = QFontMetrics( QGuiApplication.font()).boundingRect('M').height() if grid_unit % 2 != 0: grid_unit += 1 if grid_unit != self._grid_unit: self._grid_unit = grid_unit self._changed_grid_unit.emit(self._grid_unit) if grid_unit != self._large_spacing: self._small_spacing = max(2, int( grid_unit / 4)) # 1 / 4 of grid_unit, at least 2 self._large_spacing = self._small_spacing * 2 self._changed_spacing.emit() def update_device_pixel_ratio(self): # Using QGuiApplication::devicePixelRatio() gives too coarse values, # i.e.it directly jumps from 1.0 to 2.0. We want tighter control on # sizing, so we compute the exact ratio and use that. # TODO: make it possible to adapt to the dpi for the current screen dpi # instead of assuming that all of them use the same dpi which applies for # X11 but not for other systems. primary = QGuiApplication.primaryScreen() if primary: return dpi = primary.logicalDotsPerInchX() # Usual "default" is 96 dpi # that magic ratio follows the definition of "device independent pixel" by Microsoft self._device_pixel_patio = dpi / 96 self._changed_spacing.emit() def get_grid_unit(self): return self._grid_unit def get_small_spacing(self): return self._small_spacing def get_large_spacing(self): return self._large_spacing grid_unit = Property(float, get_grid_unit, constant=True) small_spacing = Property(int, get_small_spacing, constant=True) large_spacing = Property(int, get_large_spacing, constant=True)
class TaxWidget(MdiWidget, Ui_TaxWidget): def __init__(self, parent): MdiWidget.__init__(self) self.setupUi(self) self.Year.setValue(datetime.now().year - 1) # Set previous year by default self.XlsSelectBtn.pressed.connect(partial(self.OnFileBtn, 'XLS')) self.DlsgSelectBtn.pressed.connect(partial(self.OnFileBtn, 'DLSG')) self.SaveButton.pressed.connect(self.SaveReport) # center dialog with respect to parent window x = parent.x() + parent.width() / 2 - self.width() / 2 y = parent.y() + parent.height() / 2 - self.height() / 2 self.setGeometry(x, y, self.width(), self.height()) @Slot() def OnFileBtn(self, type): if type == 'XLS': selector = (self.tr("Save tax reports to:"), self.tr("Excel files (*.xlsx)"), '.xlsx', self.XlsFileName) elif type == 'DLSG': last_digit = self.year % 10 selector = (self.tr("Save tax form to:"), self.tr(f"Tax form (*.dc{last_digit})"), f".dc{last_digit}", self.DlsgFileName) else: raise ValueError filename = QFileDialog.getSaveFileName(self, selector[0], ".", selector[1]) if filename[0]: if filename[1] == selector[ 1] and filename[0][-len(selector[2]):] != selector[2]: selector[3].setText(filename[0] + selector[2]) else: selector[3].setText(filename[0]) def getYear(self): return self.Year.value() def getXlsFilename(self): return self.XlsFileName.text() def getAccount(self): return self.AccountWidget.selected_id def getDlsgState(self): return self.DlsgGroup.isChecked() def getDslgFilename(self): return self.DlsgFileName.text() def getBrokerAsIncomeName(self): return self.IncomeSourceBroker.isChecked() def getDividendsOnly(self): return self.DividendsOnly.isChecked() def getNoSettlement(self): return self.NoSettlement.isChecked() year = Property(int, fget=getYear) xls_filename = Property(str, fget=getXlsFilename) account = Property(int, fget=getAccount) update_dlsg = Property(bool, fget=getDlsgState) dlsg_filename = Property(str, fget=getDslgFilename) dlsg_broker_as_income = Property(bool, fget=getBrokerAsIncomeName) dlsg_dividends_only = Property(bool, fget=getDividendsOnly) no_settelement = Property(bool, fget=getNoSettlement) def SaveReport(self): taxes = TaxesRus() tax_report = taxes.prepare_tax_report( self.year, self.account, use_settlement=(not self.no_settelement)) reports_xls = XLSX(self.xls_filename) templates = { "Дивиденды": "tax_rus_dividends.json", "Акции": "tax_rus_trades.json", "Облигации": "tax_rus_bonds.json", "ПФИ": "tax_rus_derivatives.json", "Корп.события": "tax_rus_corporate_actions.json", "Комиссии": "tax_rus_fees.json", "Проценты": "tax_rus_interests.json" } parameters = { "period": f"{datetime.utcfromtimestamp(taxes.year_begin).strftime('%d.%m.%Y')}" f" - {datetime.utcfromtimestamp(taxes.year_end - 1).strftime('%d.%m.%Y')}", "account": f"{taxes.account_number} ({taxes.account_currency})", "currency": taxes.account_currency, "broker_name": taxes.broker_name, "broker_iso_country": taxes.broker_iso_cc } for section in tax_report: if section not in templates: continue reports_xls.output_data(tax_report[section], templates[section], parameters) reports_xls.save() logging.info( self.tr("Tax report saved to file ") + f"'{self.xls_filename}'") if self.update_dlsg: tax_forms = DLSG(self.year, broker_as_income=self.dlsg_broker_as_income, only_dividends=self.dlsg_dividends_only) tax_forms.update_taxes(tax_report, parameters) try: tax_forms.save(self.dlsg_filename) except: logging.error( self.tr("Can't write tax form into file ") + f"'{self.dlsg_filename}'")
class Product(QObject): ''' classdocs ''' productDirty = Signal() productClean = Signal() def __init__(self, parent=None): super(Product, self).__init__(parent) ''' Constructor ''' self._dirty = False self._dependsOn = [] self._dependencies = [] self._error = None self._producer = None self._autoUpdate = False @Property(QObject, notify = productClean) def bind(self): return self def set_dirty(self, d): if self._dirty != d: self._dirty = d self.dirtyChanged.emit() if self._dirty and self._autoUpdate: QTimer.singleShot(0, self.update) # schedule an update as soon as we go back to event loop, but not before dirtyChanged = Signal() dirty = Property(bool, Getter('dirty'), set_dirty, dirtyChanged) RWProperty(vars(), bool, 'autoUpdate') def _update(self): ''' update function to override ''' pass @Slot() def update(self): if self.dirty: self._error = None for d in self._dependencies: if not d.update(): self._error = d._error return False try: self._update() except Exception as e: self._error = e print(traceback.format_exc()) self.makeClean() return self._error is None @Slot() def makeDirty(self): if not self.dirty: self.dirty = True self.productDirty.emit() @Slot() def makeClean(self): if self.dirty: self.dirty = False self.productClean.emit() def set_dependsOn(self, v): ''' *Important" this property is meant to be used only from QML. Use add/remove_dependency() from python. ''' old = self._dependsOn if assign_input(self, "dependsOn", v): for d in old: self.remove_dependency(d) for d in self._dependsOn: self.add_dependency(d) dependsOnChanged = Signal() dependsOn = Property(list, Getter('dependsOn'), set_dependsOn, dependsOnChanged) def add_dependency(self, d): if d is not None: self._dependencies.append(d) d.productDirty.connect(self.makeDirty) self.makeDirty() def remove_dependency(self, d): if d is not None: self._dependencies.remove(d) d.productDirty.disconnect(self.makeDirty) self.makeDirty() def set_producer(self, producer): if self._producer is not None: raise RuntimeError("Error: tried to call set a set_producer() twice on " + str(self) + ".") assert(issubclass(type(producer), Product)) self._producer = producer self.add_dependency(producer) producer.productClean.connect(self.makeClean)
class QtBubbleLabel(QWidget): BackgroundColor = QColor(195, 195, 195) BorderColor = QColor(150, 150, 150) def __init__(self, *args, **kwargs): super(QtBubbleLabel, self).__init__(*args, **kwargs) self.setWindowFlags(Qt.Window | Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) self.setMinimumWidth(200) self.setMinimumHeight(48) self.setAttribute(Qt.WA_TranslucentBackground, True) layout = QVBoxLayout(self) layout.setContentsMargins(8, 8, 8, 16) self.label = QLabel(self) layout.addWidget(self.label) self.animationGroup = QParallelAnimationGroup(self) def setText(self, text): self.label.setText(text) def text(self): return self.label.text() def stop(self): self.hide() self.animationGroup.stop() self.animationGroup.clear() self.close() def show(self): super(QtBubbleLabel, self).show() x = self.parent().geometry().x() y = self.parent().geometry().y() x2 = self.parent().size().width() y2 = self.parent().size().height() startPos = QPoint(x + int(x2 / 2) - int(self.width() / 2), y + int(y2 / 2)) endPos = QPoint(x + int(x2 / 2) - int(self.width() / 2), y + int(y2 / 2) - self.height() * 3 - 5) self.move(startPos) # 初始化动画 self.initAnimation(startPos, endPos) def initAnimation(self, startPos, endPos): # 透明度动画 opacityAnimation = QPropertyAnimation(self, b"opacity") opacityAnimation.setStartValue(1.0) opacityAnimation.setEndValue(0.0) # 设置动画曲线 opacityAnimation.setEasingCurve(QEasingCurve.InQuad) opacityAnimation.setDuration(3000) # 在4秒的时间内完成 # 往上移动动画 moveAnimation = QPropertyAnimation(self, b"pos") moveAnimation.setStartValue(startPos) moveAnimation.setEndValue(endPos) moveAnimation.setEasingCurve(QEasingCurve.InQuad) moveAnimation.setDuration(4000) # 在5秒的时间内完成 # 并行动画组(目的是让上面的两个动画同时进行) self.animationGroup.addAnimation(opacityAnimation) self.animationGroup.addAnimation(moveAnimation) self.animationGroup.finished.connect(self.close) # 动画结束时关闭窗口 self.animationGroup.start() def paintEvent(self, event): super(QtBubbleLabel, self).paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿 rectPath = QPainterPath() # 圆角矩形 height = self.height() - 8 # 往上偏移8 rectPath.addRoundedRect(QRectF(0, 0, self.width(), height), 5, 5) x = self.width() / 5 * 4 # 边框画笔 painter.setPen( QPen(self.BorderColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # 背景画刷 painter.setBrush(self.BackgroundColor) # 绘制形状 painter.drawPath(rectPath) def windowOpacity(self): return super(QtBubbleLabel, self).windowOpacity() def setWindowOpacity(self, opacity): super(QtBubbleLabel, self).setWindowOpacity(opacity) opacity = Property(float, windowOpacity, setWindowOpacity) def ShowMsg(self, text): self.stop() self.setText(text) self.setStyleSheet("color:black") self.show() @staticmethod def ShowMsgEx(owner, text): data = QtBubbleLabel(owner) data.setText(text) data.setStyleSheet("color:black") data.show() def ShowError(self, text): self.stop() self.setText(text) self.setStyleSheet("color:red") self.show() @staticmethod def ShowErrorEx(owner, text): data = QtBubbleLabel(owner) data.setText(text) data.setStyleSheet("color:red") data.show()
class FigureCanvasItemQQuickAgg(QQuickItem): BUTTON_MAP = { Qt.LeftButton: MouseButton.LEFT, Qt.MiddleButton: MouseButton.MIDDLE, Qt.RightButton: MouseButton.RIGHT, Qt.XButton1: MouseButton.BACK, Qt.XButton2: MouseButton.FORWARD } def __init__(self, parent=None): self._node = None self._figure = None super().__init__(parent) self._tools = None self.setFlag(QQuickItem.ItemHasContents, True) self.setAcceptedMouseButtons(Qt.AllButtons) self.setAcceptHoverEvents(True) def updatePaintNode(self, node, data): if not self._figure: return if not self._figure.canvas: return if not self._figure.canvas.renderer: return image = QImage(self._figure.canvas.buffer_rgba(), self._figure.canvas.renderer.width, self._figure.canvas.renderer.height, QImage.Format_RGBA8888) texture = self.window().createTextureFromImage(image) if not self._node: self._node = QSGSimpleTextureNode() self._node.setFiltering(QSGTexture.Linear) self._node.setTexture(texture) self._node.setRect(0, 0, self.width(), self.height()) return self._node def get_figure(self): return self._figure def set_figure(self, value): self._figure = value self._tools = Tools(self._figure.canvas) self._tools.zoom() self._figure.canvas.mpl_connect("draw_event", lambda event: self.update()) self.figure_changed.emit() def map_from_qt_to_matplotlib(self, pos): return pos.x(), self._figure.bbox.height - pos.y() def hoverEnterEvent(self, event): self._figure.canvas.enter_notify_event( xy=self.map_from_qt_to_matplotlib(event.pos()), guiEvent=event) def hoverLeaveEvent(self, event): self._figure.canvas.leave_notify_event(guiEvent=event) def hoverMoveEvent(self, event): x, y = self.map_from_qt_to_matplotlib(event.pos()) self._figure.canvas.motion_notify_event(x, y, guiEvent=event) def mouseMoveEvent(self, event): x, y = self.map_from_qt_to_matplotlib(event.pos()) self._figure.canvas.motion_notify_event(x, y, guiEvent=event) def mousePressEvent(self, event): x, y = self.map_from_qt_to_matplotlib(event.pos()) button = self.BUTTON_MAP.get(event.button()) if not button: return self._figure.canvas.button_press_event(x, y, button, guiEvent=event) def mouseReleaseEvent(self, event): x, y = self.map_from_qt_to_matplotlib(event.pos()) button = self.BUTTON_MAP.get(event.button()) if not button: return self._figure.canvas.button_release_event(x, y, button, guiEvent=event) def mouseDoubleClickEvent(self, event): x, y = self.map_from_qt_to_matplotlib(event.pos()) button = self.BUTTON_MAP.get(event.button()) if not button: return self._figure.canvas.button_press_event(x, y, button, dblclick=True, guiEvent=event) figure_changed = Signal() figure = Property("QVariant", get_figure, set_figure, notify=figure_changed)
class DevicesFilterProxyModel(QSortFilterProxyModel, QObject): _changed_front_page_ = Signal(bool) _changed_filter_class_ = Signal(str) _changed_filter_text_ = Signal(str) _changed_selected_index_ = Signal() _front_page_ = True _filter_text_ = '' _filter_class_ = 0 _selected_index_ = -1 def __init__(self): super().__init__() def get_front_page(self): return self._front_page_ def set_front_page(self, fp): if self._front_page_ != fp: self._front_page_ = fp self._changed_front_page_.emit(self._front_page_) self.invalidateFilter() def get_filter_text(self): return self._filter_text_ def set_filter_text(self, new_filter): if self._filter_text_ != new_filter: self._filter_text_ = new_filter self._changed_filter_text_.emit(self._filter_text_) self.invalidateFilter() def get_filter_class(self): return self._filter_class_ def set_filter_class(self, new_filter): if self._filter_class_ != new_filter: self._filter_class_ = new_filter self._changed_filter_class_.emit(self._filter_class_) self.invalidateFilter() def set_selected_index(self, row): if self._selected_index_ != row: self._selected_index_ = row self._changed_selected_index_.emit() def get_selected_index(self): return self._selected_index_ @Slot(str, result=str) def get_property(self, p): index = self.sourceModel().index(self._selected_index_, 0) # _logger.debug(f'get_property slot: {p}, {DeviceRoles[p]}, {index.data(DeviceRoles[p])}') return index.data(DeviceRoles[p]) selected_index = Property(int, get_selected_index, set_selected_index, notify=_changed_selected_index_) front_page = Property(bool, get_front_page, set_front_page, notify=_changed_front_page_) filter_text = Property(str, get_filter_text, set_filter_text, notify=_changed_filter_text_) def filterAcceptsRow(self, source_row, source_parent: QModelIndex): index = self.sourceModel().index(source_row, 0, source_parent) if self.front_page: connected = index.data(DeviceRoles.CONNECTED) return True if connected and source_row < 4 else False else: if len(self._filter_text_) == 0: return True else: product_name = index.data(DeviceRoles.PRODUCT_NAME) device_class = index.data(DeviceRoles.DEVICE_CLASS) product_key = index.data(DeviceRoles.PRODUCT_KEY) re_name = re.search(self._filter_text_, product_name, re.IGNORECASE) is not None re_class = re.search(self._filter_text_, device_class, re.IGNORECASE) is not None re_key = re.search(self._filter_text_, product_key, re.IGNORECASE) is not None re_filter = re_name or re_class or re_key # _logger.debug(f're filter ({name}: {re_filter}') return re_filter