class _ExtractSeriesWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) # {series name: {frame name: [ (Attribute, QPersistentModelIndex) ] self.seriesOptions: Dict[str, Dict[str, List[Tuple[ int, QPersistentModelIndex]]]] = dict() # {frame name: attribute model} self.models: Dict[str, CustomProxyAttributeModel] = dict() self.seriesView = CustomSignalView(parent=self) self.seriesModel = CustomStringListModel(self) self.seriesModel.setHeaderLabel('Series name') self.addSeriesButton = QPushButton('Add', self) self.removeSeriesButton = QPushButton('Remove', self) self.addSeriesButton.clicked.connect(self.addSeries) self.removeSeriesButton.clicked.connect(self.removeSeries) self.seriesView.setModel(self.seriesModel) self.seriesView.setDragDropMode(QTableView.InternalMove) self.seriesView.setDragDropOverwriteMode(False) self.seriesView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.seriesView.verticalHeader().hide() # Connect selection to change self.seriesView.selectedRowChanged[str, str].connect( self.onSeriesSelectionChanged) # When a series is added it should be immediately edited self.seriesModel.rowAppended.connect(self.editSeriesName) self.seriesModel.rowsInserted.connect(self.checkNoSeries) self.seriesModel.rowsRemoved.connect(self.checkNoSeries) self.workbenchView = WorkbenchView(self, editable=False) self.workbench: WorkbenchModel = None self.workbenchView.selectedRowChanged[str, str].connect( self.onFrameSelectionChanged) self.attributesView = SearchableAttributeTableWidget( self, True, False, False, [Types.Numeric, Types.Ordinal]) firstRowLayout = QHBoxLayout() firstRowLayout.setSpacing(5) selectionGroup = QGroupBox( title= 'Select a time series. Then select the columns to add from the current ' 'datasets', parent=self) firstRowLayout.addWidget(self.seriesView) buttonLayout = QVBoxLayout() buttonLayout.addWidget(self.addSeriesButton) buttonLayout.addWidget(self.removeSeriesButton) firstRowLayout.addLayout(buttonLayout) firstRowLayout.addSpacing(30) firstRowLayout.addWidget(self.workbenchView) firstRowLayout.addSpacing(30) firstRowLayout.addWidget(self.attributesView) selectionGroup.setLayout(firstRowLayout) # Time axis labels model with add/remove buttons self.timeAxisModel = CustomStringListModel(self) self.timeAxisModel.setHeaderLabel('Time labels') self.timeAxisView = CustomSignalView(self) self.timeAxisView.setModel(self.timeAxisModel) self.addTimeButton = QPushButton('Add', self) self.removeTimeButton = QPushButton('Remove', self) self.addTimeButton.clicked.connect(self.addTimeLabel) self.removeTimeButton.clicked.connect(self.removeTimeLabel) self.timeAxisView.setDragDropMode(QTableView.InternalMove) self.timeAxisView.setDragDropOverwriteMode(False) self.timeAxisView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.timeAxisView.verticalHeader().hide() self.timeAxisModel.rowAppended.connect(self.editTimeLabelName) # Concatenation model self.timeSeriesDataModel = ConcatenatedModel(self) self.timeSeriesDataView = QTableView(self) self.timeSeriesDataView.setSelectionMode(QTableView.NoSelection) self.timeSeriesDataView.setItemDelegateForColumn( 1, ComboBoxDelegate(self.timeAxisModel, self.timeSeriesDataView)) self.timeSeriesDataView.setEditTriggers(QTableView.CurrentChanged | QTableView.DoubleClicked) self.timeSeriesDataView.verticalHeader().hide() # Update the label column when some label changes in the label table self.timeAxisModel.dataChanged.connect( self.timeSeriesDataModel.timeAxisLabelChanged) groupTime = QGroupBox( title= 'Add the time points (ordered) and set the correspondence to every selected column', parent=self) secondRowLayout = QHBoxLayout() secondRowLayout.setSpacing(5) # labelLayout = QVBoxLayout() # lab = QLabel('Here you should define every time point, in the correct order. After adding ' # 'double-click a row to edit the point name and drag rows to reorder them', self) # lab.setWordWrap(True) # labelLayout.addWidget(lab) # labelLayout.addWidget(self.timeAxisView) secondRowLayout.addWidget(self.timeAxisView) timeButtonLayout = QVBoxLayout() timeButtonLayout.addWidget(self.addTimeButton) timeButtonLayout.addWidget(self.removeTimeButton) secondRowLayout.addLayout(timeButtonLayout) secondRowLayout.addSpacing(30) # labelLayout = QVBoxLayout() # lab = QLabel('Every selected column for the current series will be listed here. Click the right ' # 'column of the table to set the time label associated with every original column', # self) # lab.setWordWrap(True) # labelLayout.addWidget(lab) secondRowLayout.addWidget(self.timeSeriesDataView) # secondRowLayout.addLayout(labelLayout) groupTime.setLayout(secondRowLayout) self.outputName = QLineEdit(self) self.warningLabel = MessageLabel(text='', color='orange', icon=QMessageBox.Warning, parent=self) lastRowLayout = QFormLayout() lastRowLayout.addRow('Output variable name:', self.outputName) self.outputName.setPlaceholderText('Output name') lastRowLayout.setVerticalSpacing(0) lastRowLayout.addRow('', self.warningLabel) lastRowLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) self.warningLabel.hide() self.outputName.textChanged.connect(self.checkOutputName) layout = QVBoxLayout(self) layout.addWidget(selectionGroup) layout.addWidget(groupTime) layout.addLayout(lastRowLayout) self.checkNoSeries() def setWorkbench(self, w: WorkbenchModel) -> None: """ Sets the workbench and initialises every attribute model (one for each frame) """ self.workbench = w self.workbenchView.setModel(w) # Set a default name for output if self.workbench: name = 'time_series_{:d}' n = 1 name_n = name.format(n) while name_n in self.workbench.names: n += 1 name_n = name.format(n) self.outputName.setText(name_n) def addSourceFrameModel(self, frameName: str) -> None: if self.workbench: dfModel = self.workbench.getDataframeModelByName(frameName) # Create an attribute model with checkboxes standardModel = AttributeTableModel(self, checkable=True, editable=False, showTypes=True) standardModel.setFrameModel(dfModel) # Create a proxy to filter data in the concatenation customProxy = CustomProxyAttributeModel(self) customProxy.setSourceModel(standardModel) # Add proxy to the list of models self.models[frameName] = customProxy # Add proxy as source model self.timeSeriesDataModel.addSourceModel(customProxy) @Slot() def checkNoSeries(self) -> None: if not self.seriesModel.rowCount(): self.workbenchView.setEnabled(False) self.attributesView.setEnabled(False) self.timeAxisView.setEnabled(False) self.addTimeButton.setEnabled(False) self.removeTimeButton.setEnabled(False) self.timeSeriesDataView.setEnabled(False) else: self.workbenchView.setEnabled(True) self.attributesView.setEnabled(True) self.timeAxisView.setEnabled(True) self.addTimeButton.setEnabled(True) self.removeTimeButton.setEnabled(True) self.timeSeriesDataView.setEnabled(True) def persistOptionsSetForSeries(self, seriesName: str) -> None: if seriesName: seriesValues: Dict[str, List[Tuple[int, QPersistentModelIndex]]] = dict() for r in range(self.timeSeriesDataModel.rowCount()): column0Index: QModelIndex = self.timeSeriesDataModel.index( r, 0, QModelIndex()) column1Index: QModelIndex = self.timeSeriesDataModel.index( r, 1, QModelIndex()) sourceIndex: QModelIndex = self.timeSeriesDataModel.mapToSource( column0Index) proxy: CustomProxyAttributeModel = sourceIndex.model() frameName: str = proxy.sourceModel().frameModel().name attrIndexInFrame: int = proxy.mapToSource(sourceIndex).row() timeLabelIndex: QPersistentModelIndex = column1Index.data( Qt.DisplayRole) if seriesValues.get(frameName, None): seriesValues[frameName].append( (attrIndexInFrame, timeLabelIndex)) else: seriesValues[frameName] = [(attrIndexInFrame, timeLabelIndex)] self.seriesOptions[seriesName] = seriesValues @Slot(str, str) def onSeriesSelectionChanged(self, new: str, old: str) -> None: # Save current set options self.persistOptionsSetForSeries(old) if new: # Get options of new selection newOptions: Dict[str, List[Tuple[int, QPersistentModelIndex]]] = \ self.seriesOptions.get(new, dict()) for frameName, proxyModel in self.models.items(): frameOptions = newOptions.get(frameName, None) self.setOptionsForFrame(frameName, frameOptions) # Update proxy view on the time label columns proxyModel.dataChanged.emit( proxyModel.index(0, 1, QModelIndex()), proxyModel.index(proxyModel.rowCount() - 1, 1, QModelIndex()), [Qt.DisplayRole, Qt.EditRole]) # Every time series change clear frame selection in workbench self.workbenchView.clearSelection() @Slot(str, str) def onFrameSelectionChanged(self, newFrame: str, _: str) -> None: if not newFrame: # Nothing is selected return self.attributesView.setAttributeModel( AttributeTableModel(self)) # Check if frame is already in the source models if newFrame not in self.models.keys(): # Create a new proxy and add it to source models self.addSourceFrameModel(newFrame) if len(self.models) == 1: # If it is the first model added then set up the view self.timeSeriesDataView.setModel(self.timeSeriesDataModel) self.timeSeriesDataView.horizontalHeader( ).setSectionResizeMode(0, QHeaderView.Stretch) self.timeSeriesDataView.horizontalHeader( ).setSectionResizeMode(1, QHeaderView.Stretch) # Update the attribute table self.attributesView.setAttributeModel( self.models[newFrame].sourceModel()) def setOptionsForFrame( self, frameName: str, options: Optional[List[Tuple[int, QPersistentModelIndex]]]) -> None: customProxyModel = self.models[frameName] attributeTableModel = customProxyModel.sourceModel() attributeTableModel.setAllChecked(False) if options: proxySelection: Dict[int, QPersistentModelIndex] = { i: pmi for i, pmi in options } customProxyModel.attributes = proxySelection attributeTableModel.setChecked(list(proxySelection.keys()), value=True) else: customProxyModel.attributes = dict() @Slot() def addSeries(self) -> None: # Append new row self.seriesModel.appendEmptyRow() # In oder to avoid copying previous options for frameName, proxyModel in self.models.items(): self.setOptionsForFrame(frameName, None) @Slot() def removeSeries(self) -> None: selected: List[QModelIndex] = self.seriesView.selectedIndexes() if selected: seriesName: str = selected[0].data(Qt.DisplayRole) # Remove row self.seriesModel.removeRow(selected[0].row()) # Remove options for series if they exists self.seriesOptions.pop(seriesName, None) @Slot() def addTimeLabel(self) -> None: self.timeAxisModel.appendEmptyRow() @Slot() def removeTimeLabel(self) -> None: selected: List[QModelIndex] = self.timeAxisView.selectedIndexes() if selected: self.timeAxisModel.removeRow(selected[0].row()) # Update model self.timeSeriesDataModel.dataChanged.emit( self.timeSeriesDataModel.index(0, 1, QModelIndex()), self.timeSeriesDataModel.index( self.timeSeriesDataModel.rowCount() - 1, 1, QModelIndex()), [Qt.DisplayRole, Qt.EditRole]) @Slot() def editSeriesName(self) -> None: index = self.seriesModel.index(self.seriesModel.rowCount() - 1, 0, QModelIndex()) self.seriesView.setCurrentIndex(index) self.seriesView.edit(index) @Slot() def editTimeLabelName(self) -> None: index = self.timeAxisModel.index(self.timeAxisModel.rowCount() - 1, 0, QModelIndex()) self.timeAxisView.setCurrentIndex(index) self.timeAxisView.edit(index) @Slot(str) def checkOutputName(self, text: str) -> None: if self.workbench and text in self.workbench.names: self.warningLabel.setText( 'Variable {:s} will be overwritten'.format(text)) self.warningLabel.show() else: self.warningLabel.hide()
class FieldNameListEditor(QWidget): """A widget to edit foreign keys' field name lists.""" data_committed = Signal(name="data_committed") def __init__(self, parent, option, index): """Initialize class.""" super().__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.model = MinimalTableModel(self) self.model.flags = self.model_flags self.view = QTableView(self) self.view.setModel(self.model) self.view.verticalHeader().hide() self.view.horizontalHeader().hide() self.view.setShowGrid(False) check_box_delegate = CheckBoxDelegate(self) self.view.setItemDelegateForColumn(0, check_box_delegate) check_box_delegate.data_committed.connect( self._handle_check_box_data_committed) self.button = QPushButton("Ok", self) self.button.setFlat(True) self.view.verticalHeader().setDefaultSectionSize(option.rect.height()) self.button.setFixedHeight(option.rect.height()) layout.addWidget(self.view) layout.addWidget(self.button) self.button.clicked.connect(self._handle_ok_button_clicked) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) x_offset = parent.parent().columnViewportPosition(index.column()) y_offset = parent.parent().rowViewportPosition(index.row()) self.position = parent.mapToGlobal(QPoint(0, 0)) + QPoint( x_offset, y_offset) def model_flags(self, index): """Return index flags.""" if not index.isValid(): return Qt.NoItemFlags if index.column() != 0: return ~Qt.ItemIsEditable return Qt.ItemIsEditable @Slot("QModelIndex", name="_handle_check_box_data_committed") def _handle_check_box_data_committed(self, index): """Called when checkbox delegate wants to edit data. Toggle the index's value.""" data = index.data(Qt.EditRole) self.model.setData(index, not data) @Slot("bool", name="_handle_ok_button_clicked") def _handle_ok_button_clicked(self, checked=False): """Called when user pressed Ok.""" self.data_committed.emit() def set_data(self, field_names, current_field_names): """Set values to show in the 'menu'. Reset model using those values and update geometry.""" data = [[name in current_field_names, name] for name in field_names] self.model.reset_model(data) self.view.resizeColumnsToContents() width = self.view.horizontalHeader().length() + qApp.style( ).pixelMetric(QStyle.PM_ScrollBarExtent) self.setFixedWidth(width + 2) height = self.view.verticalHeader().length() + self.button.height() parent_height = self.parent().height() self.setFixedHeight(min(height, parent_height / 2) + 2) self.move(self.position) def data(self): return ",".join( [name for checked, name in self.model._main_data if checked])
class AutoFilterWidget(QWidget): """A widget to show the auto filter 'menu'.""" def __init__(self, parent): """Initialize class.""" super().__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.model = MinimalTableModel(self) self.model.flags = self.model_flags self.view = QTableView(self) self.view.setModel(self.model) self.view.verticalHeader().hide() self.view.horizontalHeader().hide() self.view.setShowGrid(False) check_box_delegate = CheckBoxDelegate(self) self.view.setItemDelegateForColumn(0, check_box_delegate) check_box_delegate.data_committed.connect( self._handle_check_box_data_committed) self.button = QPushButton("Ok", self) self.button.setFlat(True) layout.addWidget(self.view) layout.addWidget(self.button) self.button.clicked.connect(self.hide) self.hide() self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) def model_flags(self, index): """Return index flags.""" if not index.isValid(): return Qt.NoItemFlags if index.column() == 1: return ~Qt.ItemIsEditable return Qt.ItemIsEditable @Slot("QModelIndex", name="_handle_check_box_data_committed") def _handle_check_box_data_committed(self, index): """Called when checkbox delegate wants to edit data. Toggle the index's value.""" data = index.data(Qt.EditRole) model_data = self.model._main_data row_count = self.model.rowCount() if index.row() == 0: # Ok row value = data in (None, False) for row in range(row_count): model_data[row][0] = value self.model.dataChanged.emit(self.model.index(0, 0), self.model.index(row_count - 1, 0)) else: # Data row self.model.setData(index, not data) self.set_ok_index_data() def set_ok_index_data(self): """Set data for ok index based on data from all other indexes.""" ok_index = self.model.index(0, 0) true_count = 0 for row_data in self.model._main_data[1:]: if row_data[0] == True: true_count += 1 if true_count == len(self.model._main_data) - 1: self.model.setData(ok_index, True) elif true_count == 0: self.model.setData(ok_index, False) else: self.model.setData(ok_index, None) def set_values(self, values): """Set values to show in the 'menu'. Reset model using those values and update geometry.""" self.model.reset_model([[None, "All"]] + values) self.set_ok_index_data() self.view.horizontalHeader().hideSection( 2) # Column 2 holds internal data (cls_id_set) self.view.resizeColumnsToContents() width = self.view.horizontalHeader().length() + qApp.style( ).pixelMetric(QStyle.PM_ScrollBarExtent) self.setFixedWidth(width + 2) height = self.view.verticalHeader().length() + self.button.height() parent_height = self.parent().height() self.setFixedHeight(min(height, parent_height / 2) + 2) def set_section_height(self, height): """Set vertical header default section size as well as button height.""" self.view.verticalHeader().setDefaultSectionSize(height) self.button.setFixedHeight(height)
class IncomeSpendingWidget(AbstractOperationDetails): def __init__(self, parent=None): AbstractOperationDetails.__init__(self, parent) self.name = "Income/Spending" self.details_model = None self.category_delegate = CategorySelectorDelegate() self.tag_delegate = TagSelectorDelegate() self.float_delegate = FloatDelegate(2) self.date_label = QLabel(self) self.details_label = QLabel(self) self.account_label = QLabel(self) self.peer_label = QLabel(self) self.main_label.setText(g_tr("IncomeSpendingWidget", "Income / Spending")) self.date_label.setText(g_tr("IncomeSpendingWidget", "Date/Time")) self.details_label.setText(g_tr("IncomeSpendingWidget", "Details")) self.account_label.setText(g_tr("IncomeSpendingWidget", "Account")) self.peer_label.setText(g_tr("IncomeSpendingWidget", "Peer")) self.timestamp_editor = QDateTimeEdit(self) self.timestamp_editor.setCalendarPopup(True) self.timestamp_editor.setTimeSpec(Qt.UTC) self.timestamp_editor.setFixedWidth(self.timestamp_editor.fontMetrics().width("00/00/0000 00:00:00") * 1.25) self.timestamp_editor.setDisplayFormat("dd/MM/yyyy hh:mm:ss") self.account_widget = AccountSelector(self) self.peer_widget = PeerSelector(self) self.a_currency = OptionalCurrencyComboBox(self) self.a_currency.setText(g_tr("IncomeSpendingWidget", "Paid in foreign currency:")) self.add_button = QPushButton(self) self.add_button.setText(" +️ ") self.add_button.setFont(self.bold_font) self.add_button.setFixedWidth(self.add_button.fontMetrics().width("XXX")) self.del_button = QPushButton(self) self.del_button.setText(" — ️") self.del_button.setFont(self.bold_font) self.del_button.setFixedWidth(self.del_button.fontMetrics().width("XXX")) self.copy_button = QPushButton(self) self.copy_button.setText(" >> ️") self.copy_button.setFont(self.bold_font) self.copy_button.setFixedWidth(self.copy_button.fontMetrics().width("XXX")) self.details_table = QTableView(self) self.details_table.horizontalHeader().setFont(self.bold_font) self.details_table.setAlternatingRowColors(True) self.details_table.verticalHeader().setVisible(False) self.details_table.verticalHeader().setMinimumSectionSize(20) self.details_table.verticalHeader().setDefaultSectionSize(20) self.layout.addWidget(self.date_label, 1, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.details_label, 2, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.timestamp_editor, 1, 1, 1, 4) self.layout.addWidget(self.add_button, 2, 1, 1, 1) self.layout.addWidget(self.copy_button, 2, 2, 1, 1) self.layout.addWidget(self.del_button, 2, 3, 1, 1) self.layout.addWidget(self.account_label, 1, 5, 1, 1, Qt.AlignRight) self.layout.addWidget(self.peer_label, 2, 5, 1, 1, Qt.AlignRight) self.layout.addWidget(self.account_widget, 1, 6, 1, 1) self.layout.addWidget(self.peer_widget, 2, 6, 1, 1) self.layout.addWidget(self.a_currency, 1, 7, 1, 1) self.layout.addWidget(self.commit_button, 0, 9, 1, 1) self.layout.addWidget(self.revert_button, 0, 10, 1, 1) self.layout.addWidget(self.details_table, 4, 0, 1, 11) self.layout.addItem(self.horizontalSpacer, 1, 8, 1, 1) self.add_button.clicked.connect(self.addChild) self.del_button.clicked.connect(self.delChild) super()._init_db("actions") self.mapper.setItemDelegate(IncomeSpendingWidgetDelegate(self.mapper)) self.details_model = DetailsModel(self.details_table, db_connection()) self.details_model.setTable("action_details") self.details_model.setEditStrategy(QSqlTableModel.OnManualSubmit) self.details_table.setModel(self.details_model) self.details_model.dataChanged.connect(self.onDataChange) self.account_widget.changed.connect(self.mapper.submit) self.peer_widget.changed.connect(self.mapper.submit) self.a_currency.changed.connect(self.mapper.submit) self.a_currency.updated.connect(self.details_model.setAltCurrency) self.mapper.addMapping(self.timestamp_editor, self.model.fieldIndex("timestamp")) self.mapper.addMapping(self.account_widget, self.model.fieldIndex("account_id")) self.mapper.addMapping(self.peer_widget, self.model.fieldIndex("peer_id")) self.mapper.addMapping(self.a_currency, self.model.fieldIndex("alt_currency_id")) self.details_table.setItemDelegateForColumn(2, self.category_delegate) self.details_table.setItemDelegateForColumn(3, self.tag_delegate) self.details_table.setItemDelegateForColumn(4, self.float_delegate) self.details_table.setItemDelegateForColumn(5, self.float_delegate) self.model.select() self.details_model.select() self.details_model.configureView() def setId(self, id): super().setId(id) self.details_model.setFilter(f"action_details.pid = {id}") @Slot() def addChild(self): new_record = self.details_model.record() new_record.setNull("tag_id") new_record.setValue("amount", 0) new_record.setValue("amount_alt", 0) if not self.details_model.insertRecord(-1, new_record): logging.fatal( g_tr('AbstractOperationDetails', "Failed to add new record: ") + self.details_model.lastError().text()) return @Slot() def delChild(self): idx = self.details_table.selectionModel().selection().indexes() selected_row = idx[0].row() self.details_model.removeRow(selected_row) self.details_table.setRowHidden(selected_row, True) @Slot() def saveChanges(self): if not self.model.submitAll(): logging.fatal( g_tr('AbstractOperationDetails', "Operation submit failed: ") + self.model.lastError().text()) return pid = self.model.data(self.model.index(0, self.model.fieldIndex("id"))) if pid is None: # we just have saved new action record and need last inserted id pid = self.model.query().lastInsertId() for row in range(self.details_model.rowCount()): self.details_model.setData(self.details_model.index(row, self.details_model.fieldIndex("pid")), pid) if not self.details_model.submitAll(): logging.fatal(g_tr('AbstractOperationDetails', "Operation details submit failed: ") + self.details_model.lastError().text()) return self.modified = False self.commit_button.setEnabled(False) self.revert_button.setEnabled(False) self.dbUpdated.emit() return def createNew(self, account_id=0): super().createNew(account_id) self.details_model.setFilter(f"action_details.pid = 0") def prepareNew(self, account_id): new_record = self.model.record() new_record.setNull("id") new_record.setValue("timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) new_record.setValue("account_id", account_id) new_record.setValue("peer_id", 0) new_record.setValue("alt_currency_id", None) return new_record def copyNew(self): old_id = self.model.record(self.mapper.currentIndex()).value(0) super().copyNew() self.details_model.setFilter(f"action_details.pid = 0") query = executeSQL("SELECT * FROM action_details WHERE pid = :pid ORDER BY id DESC", [(":pid", old_id)]) while query.next(): new_record = query.record() new_record.setNull("id") new_record.setNull("pid") assert self.details_model.insertRows(0, 1) self.details_model.setRecord(0, new_record) def copyToNew(self, row): new_record = self.model.record(row) new_record.setNull("id") new_record.setValue("timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) return new_record
return False def setModelData(self, editor, model, index): ''' The user wanted to change the old state in the opposite. ''' model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole) if __name__ == '__main__': import sys app = QApplication(sys.argv) model = QStandardItemModel(4, 3) tableView = QTableView() tableView.setModel(model) delegate = CheckBoxDelegate(None) tableView.setItemDelegateForColumn(1, delegate) for row in range(4): for column in range(3): index = model.index(row, column, QModelIndex()) model.setData(index, 1) tableView.setWindowTitle("Check Box Delegate") tableView.show() sys.exit(app.exec_())
class MainWin(QWidget): def __init__(self): super().__init__() self.dbConnector = cPl.DBConnector("timePlanner.db", "Task", ormMapping) self.taskStorage = cPl.TaskStorage(self.dbConnector, ormMapping) self.currentView = "Work" self.checkBox = QCheckBox('Minimize to Tray') self.checkBox.setChecked(True) self.createModels() self.grid = QGridLayout() self.tray_icon = QSystemTrayIcon() self.tray_icon.setToolTip("Time Planner") self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) traySignal = "activated(QSystemTrayIcon::ActivationReason)" QObject.connect(self.tray_icon, SIGNAL(traySignal), self.__icon_activated) #appendFunc = anonFuncString("Total time") appendDataFinished = [ cPl.AppendDataView(self.finishedModel, 3, self.taskStorage.getTotalWorkTime, workTimeFormat), cPl.AppendDataView(self.finishedModel, 2, anonFuncString) ] self.finishedModel.setAppendData(appendDataFinished) self.initUI() def hideEvent(self, event): if self.checkBox.isChecked(): event.ignore() self.hide() self.tray_icon.show() def __icon_activated(self, reason): if reason == QSystemTrayIcon.Trigger: self.show() def __resizeView(self): self.view.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch) if self.currentView == "Work": self.view.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.view.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Fixed) self.view.horizontalHeader().setSectionResizeMode( 3, QHeaderView.ResizeToContents) self.view.setColumnWidth(1, 140) self.view.setColumnWidth(2, 70) self.view.setColumnWidth(self.model.getHeaderLenght(), 70) self.view.setColumnWidth(self.model.getHeaderLenght() + 1, 70) self.view.setColumnWidth(self.model.getHeaderLenght() + 2, 70) self.view.setColumnWidth(self.model.getHeaderLenght() + 3, 70) else: self.view.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.view.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Fixed) self.view.horizontalHeader().setSectionResizeMode( 3, QHeaderView.ResizeToContents) self.view.setColumnWidth(1, 140) self.view.setColumnWidth(2, 140) def initUI(self): self.taskStorage.Model = self.model grid = self.grid self.setLayout(grid) buttonCurrent = QPushButton('Current Task') buttonCurrent.setCheckable(True) buttonFinished = QPushButton('Finished Task') buttonFinished.setCheckable(True) buttonCurrent.setMaximumWidth(100) buttonFinished.setMaximumWidth(100) self.move(300, 150) self.setMinimumWidth(1100) self.setMinimumHeight(400) self.setWindowTitle('Time Planner') top_Panel = self.createTop() grid.addWidget(top_Panel, 0, 0) self.view = QTableView() self.view.setModel(self.model) stylesheet = "QHeaderView::section{color: grey; border: 2px solid #6c6c6c; border-width: 0px 0px 2px 0px; " \ "border-style: dotted; border-color: black} " self.view.setStyleSheet(stylesheet) self.__resizeView() #Create here else garbage collector clear it self.buttonStart = cPl.StartButtonDelegate(self.taskStorage, "taskId") self.buttonPause = cPl.PauseButtonDelegate(self.taskStorage, "taskId") self.buttonFinish = cPl.FinishButtonDelegate(self.taskStorage, "taskId") self.buttonDelete = cPl.DeleteButtonDelegate(self.taskStorage, "taskId", self) if self.currentView == "Work": self.view.setItemDelegateForColumn(self.model.getHeaderLenght(), self.buttonStart) self.view.setItemDelegateForColumn( self.model.getHeaderLenght() + 1, self.buttonPause) self.view.setItemDelegateForColumn( self.model.getHeaderLenght() + 2, self.buttonFinish) self.view.setItemDelegateForColumn( self.model.getHeaderLenght() + 3, self.buttonDelete) buttonCurrent.setChecked(True) buttonFinished.setChecked(False) else: self.view.setItemDelegateForColumn(self.model.getHeaderLenght(), self.buttonDelete) buttonCurrent.setChecked(False) buttonFinished.setChecked(True) viewPanel = self.createTaskView(self.view) grid.addWidget(viewPanel, 1, 0) buttonPanel = self.createButtonView() grid.addWidget(buttonPanel, 1, 1) grid.addWidget(self.checkBox, 2, 0) self.show() # Top panel - LineEdit for task name + Add new task button def createTop(self): topPanel = QWidget() topPanel.setContentsMargins(0, 0, 0, 0) hBox = QHBoxLayout() vBox = QVBoxLayout() lineEdit = QLineEdit('') lineEdit.setPlaceholderText("Enter new task name") buttonAdd = QPushButton('Add Task') buttonAdd.clicked.connect(self.add_newTask(lineEdit)) hBox.addWidget(lineEdit) lineEdit.setMinimumWidth(250) hBox.addWidget(buttonAdd) hBox.addStretch(1) vBox.addLayout(hBox) topPanel.setLayout(vBox) return topPanel def createTaskView(self, view): hBox = QHBoxLayout() viewPanel = QWidget() hBox.addWidget(view) viewPanel.setLayout(hBox) return viewPanel def createButtonView(self): buttons = [] datePickers = [] buttonPanel = QWidget() buttonPanel.setContentsMargins(0, 0, 0, 0) buttonCurrent = QPushButton('Current tasks') buttonCurrent.setCheckable(True) buttonFinished = QPushButton('Finished tasks') buttonFinished.setCheckable(True) buttonFilter = QPushButton("Filter by end date") buttonApply = QPushButton("Apply") buttonFilter.setCheckable(True) buttonCurrent.setMaximumWidth(100) buttonFinished.setMaximumWidth(100) buttonFilter.setMaximumWidth(100) buttonApply.setMaximumWidth(100) buttonCurrent.setMinimumWidth(100) buttonFinished.setMinimumWidth(100) buttonFilter.setMinimumWidth(100) buttonApply.setMinimumWidth(100) buttons.append(buttonFilter) # labelStart = QLabel("Date start") datePickers.append(labelStart) dateStart = QDateEdit() dateStart.setDisplayFormat('dd/MM/yyyy') dateStart.setCalendarPopup(True) dateStart.setDate(QDate.currentDate()) datePickers.append(dateStart) labelEnd = QLabel("Date end") datePickers.append(labelEnd) dateEnd = QDateEdit() dateEnd.setDisplayFormat('dd/MM/yyyy') dateEnd.setCalendarPopup(True) dateEnd.setDate(QDate.currentDate()) datePickers.append(dateEnd) datePickers.append(buttonApply) buttonCurrent.clicked.connect( self.switchCurrentTask(buttonFinished, buttonCurrent, buttons)) buttonFinished.clicked.connect( self.switchFinishedTask(buttonCurrent, buttonFinished, buttons)) buttonFilter.clicked.connect( self.openDatePickerFilter(datePickers, buttonFilter)) buttonApply.clicked.connect( self.ApplyDateFilterArchive(dateStart, dateEnd)) buttonCurrent.setChecked(True) vBox = QVBoxLayout() vBox.addWidget(buttonCurrent) vBox.addWidget(buttonFinished) vBox.addSpacing(30) for bnt in buttons: bnt.setVisible(False) vBox.addWidget(bnt) for datePicker in datePickers: datePicker.setVisible(False) vBox.addWidget(datePicker) vBox.addStretch(1) buttonPanel.setLayout(vBox) return buttonPanel def add_newTask(self, lineEdit): def call_sql(): if lineEdit.text() != "": self.taskStorage.addTask(lineEdit.text()) self.model.refresh() return call_sql def openDatePickerFilter(self, filterElements, clickBtn): def call(): if not clickBtn.isChecked(): for filterElement in filterElements: filterElement.setVisible(False) self.taskStorage.viewAllFinishedTask() self.finishedModel.refresh() else: for filterElement in filterElements: filterElement.setVisible(True) return call def ApplyDateFilterArchive(self, dateStart, dateEnd): def call(): start = datetime.combine(dateStart.date().toPython(), datetime.min.time()) end = datetime.combine(dateEnd.date().toPython(), datetime.min.time()) startUTC = int(float(start.timestamp())) endUTC = int(float(end.timestamp())) self.finishedModel.switchToFilterData(startUTC, endUTC) self.finishedModel.refresh() return call def switchCurrentTask(self, buttonInAct, btnAct, btnsFilter): def call(): if self.currentView != "Work": self.model.setAllTaskView() self.view.setModel(self.model) self.view.setItemDelegateForColumn( self.model.getHeaderLenght(), self.buttonStart) self.view.setItemDelegateForColumn( self.model.getHeaderLenght() + 1, self.buttonPause) self.view.setItemDelegateForColumn( self.model.getHeaderLenght() + 2, self.buttonFinish) btnAct.setChecked(True) buttonInAct.setChecked(False) for btn in btnsFilter: btn.setVisible(False) self.currentView = "Work" self.__resizeView() return call def switchFinishedTask(self, buttonInAct, btnAct, btnsFilter): def call(): if self.currentView != "Archive": self.finishedModel.switchToAllDataView() self.view.setModel(self.finishedModel) self.view.setItemDelegateForColumn( self.model.getHeaderLenght(), self.buttonDelete) btnAct.setChecked(True) buttonInAct.setChecked(False) for btn in btnsFilter: btn.setVisible(True) self.currentView = "Archive" self.__resizeView() return call def createModels(self): self.model = cPl.TaskModel(self.taskStorage, ormMapping, 0, buttonData, self) # передаем хранилище задач в модель self.finishedModel = cPl.TaskModel(self.taskStorage, ormMappingFinished, 1, buttonDataFinish, self) def updateView(self): self.view.update()