class TransferWidget(AbstractOperationDetails): def __init__(self, parent=None): AbstractOperationDetails.__init__(self, parent) self.name = "Transfer" self.operation_type = LedgerTransaction.Transfer self.from_date_label = QLabel(self) self.from_account_label = QLabel(self) self.from_amount_label = QLabel(self) self.to_date_label = QLabel(self) self.to_account_label = QLabel(self) self.to_amount_label = QLabel(self) self.fee_account_label = QLabel(self) self.fee_amount_label = QLabel(self) self.comment_label = QLabel(self) self.arrow_account = QLabel(self) self.copy_date_btn = QPushButton(self) self.copy_amount_btn = QPushButton(self) self.main_label.setText(self.tr("Transfer")) self.from_date_label.setText(self.tr("Date/Time")) self.from_account_label.setText(self.tr("From")) self.from_amount_label.setText(self.tr("Amount")) self.to_date_label.setText(self.tr("Date/Time")) self.to_account_label.setText(self.tr("To")) self.to_amount_label.setText(self.tr("Amount")) self.fee_account_label.setText(self.tr("Fee from")) self.fee_amount_label.setText(self.tr("Fee amount")) self.comment_label.setText(self.tr("Note")) self.arrow_account.setText(" ➜ ") self.copy_date_btn.setText("➜") self.copy_date_btn.setFixedWidth( self.copy_date_btn.fontMetrics().horizontalAdvance("XXXX")) self.copy_amount_btn.setText("➜") self.copy_amount_btn.setFixedWidth( self.copy_amount_btn.fontMetrics().horizontalAdvance("XXXX")) self.withdrawal_timestamp = QDateTimeEdit(self) self.withdrawal_timestamp.setCalendarPopup(True) self.withdrawal_timestamp.setTimeSpec(Qt.UTC) self.withdrawal_timestamp.setFixedWidth( self.withdrawal_timestamp.fontMetrics().horizontalAdvance( "00/00/0000 00:00:00") * 1.25) self.withdrawal_timestamp.setDisplayFormat("dd/MM/yyyy hh:mm:ss") self.deposit_timestamp = QDateTimeEdit(self) self.deposit_timestamp.setCalendarPopup(True) self.deposit_timestamp.setTimeSpec(Qt.UTC) self.deposit_timestamp.setFixedWidth( self.deposit_timestamp.fontMetrics().horizontalAdvance( "00/00/0000 00:00:00") * 1.25) self.deposit_timestamp.setDisplayFormat("dd/MM/yyyy hh:mm:ss") self.from_account_widget = AccountSelector(self) self.to_account_widget = AccountSelector(self) self.fee_account_widget = AccountSelector(self) self.withdrawal = QLineEdit(self) self.withdrawal.setAlignment(Qt.AlignRight) self.deposit = QLineEdit(self) self.deposit.setAlignment(Qt.AlignRight) self.fee = QLineEdit(self) self.fee.setAlignment(Qt.AlignRight) self.comment = QLineEdit(self) self.layout.addWidget(self.from_date_label, 1, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.from_account_label, 2, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.from_amount_label, 3, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.comment_label, 5, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.withdrawal_timestamp, 1, 1, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.from_account_widget, 2, 1, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.withdrawal, 3, 1, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.comment, 5, 1, 1, 4) self.layout.addWidget(self.copy_date_btn, 1, 2, 1, 1) self.layout.addWidget(self.arrow_account, 2, 2, 1, 1, Qt.AlignCenter) self.layout.addWidget(self.copy_amount_btn, 3, 2, 1, 1) self.layout.addWidget(self.to_date_label, 1, 3, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.to_account_label, 2, 3, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.to_amount_label, 3, 3, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.fee_account_label, 4, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.fee_amount_label, 4, 3, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.deposit_timestamp, 1, 4, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.to_account_widget, 2, 4, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.deposit, 3, 4, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.fee_account_widget, 4, 1, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.fee, 4, 4, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.commit_button, 0, 6, 1, 1) self.layout.addWidget(self.revert_button, 0, 7, 1, 1) self.layout.addItem(self.verticalSpacer, 6, 0, 1, 1) self.layout.addItem(self.horizontalSpacer, 1, 5, 1, 1) self.copy_date_btn.clicked.connect(self.onCopyDate) self.copy_amount_btn.clicked.connect(self.onCopyAmount) super()._init_db("transfers") self.mapper.setItemDelegate(TransferWidgetDelegate(self.mapper)) self.from_account_widget.changed.connect(self.mapper.submit) self.to_account_widget.changed.connect(self.mapper.submit) self.fee_account_widget.changed.connect(self.mapper.submit) self.mapper.addMapping(self.withdrawal_timestamp, self.model.fieldIndex("withdrawal_timestamp")) self.mapper.addMapping(self.from_account_widget, self.model.fieldIndex("withdrawal_account")) self.mapper.addMapping(self.withdrawal, self.model.fieldIndex("withdrawal")) self.mapper.addMapping(self.deposit_timestamp, self.model.fieldIndex("deposit_timestamp")) self.mapper.addMapping(self.to_account_widget, self.model.fieldIndex("deposit_account")) self.mapper.addMapping(self.deposit, self.model.fieldIndex("deposit")) self.mapper.addMapping(self.fee_account_widget, self.model.fieldIndex("fee_account")) self.mapper.addMapping(self.fee, self.model.fieldIndex("fee")) self.mapper.addMapping(self.comment, self.model.fieldIndex("note")) self.model.select() @Slot() def saveChanges(self): record = self.model.record(0) # Set related fields NULL if we don't have fee. This is required for correct transfer processing fee_amount = record.value(self.model.fieldIndex("fee")) if not fee_amount: fee_amount = 0 if abs(float(fee_amount)) < Setup.CALC_TOLERANCE: self.model.setData( self.model.index(0, self.model.fieldIndex("fee_account")), None) self.model.setData( self.model.index(0, self.model.fieldIndex("fee")), None) super().saveChanges() def prepareNew(self, account_id): new_record = super().prepareNew(account_id) new_record.setValue( "withdrawal_timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) new_record.setValue("withdrawal_account", account_id) new_record.setValue("withdrawal", 0) new_record.setValue( "deposit_timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) new_record.setValue("deposit_account", 0) new_record.setValue("deposit", 0) new_record.setValue("fee_account", 0) new_record.setValue("fee", 0) new_record.setValue("asset", None) new_record.setValue("note", None) return new_record def copyToNew(self, row): new_record = self.model.record(row) new_record.setNull("id") new_record.setValue( "withdrawal_timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) new_record.setValue( "deposit_timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) return new_record @Slot() def onCopyDate(self): self.deposit_timestamp.setDateTime( self.withdrawal_timestamp.dateTime()) # mapper.submit() isn't needed here as 'changed' signal of 'deposit_timestamp' is linked with it @Slot() def onCopyAmount(self): self.deposit.setText(self.withdrawal.text()) self.mapper.submit()
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 LogViewer(QPlainTextEdit, logging.Handler): def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) logging.Handler.__init__(self) self.app = QApplication.instance() self.setReadOnly(True) self.status_bar = None # Status bar where notifications and control are located self.expandButton = None # Button that shows/hides log window self.notification = None # Here is QLabel element to display LOG update status self.clear_color = None # Variable to store initial "clear" background color self.collapsed_text = self.tr("▶ logs") self.expanded_text = self.tr("▲ logs") def emit(self, record, **kwargs): predefinded_colors = { logging.DEBUG: CustomColor.Grey, logging.INFO: self.clear_color, logging.WARNING: CustomColor.LightRed, logging.ERROR: CustomColor.LightRed, logging.CRITICAL: CustomColor.LightRed } try: msg_color = predefinded_colors[record.levelno] except KeyError: self.appendPlainText( self.tr("Unknown logging level provided: ") + f"{record.levelno}") msg_color = CustomColor.LightRed # Store message in log window msg = self.format(record) tf = self.currentCharFormat() tf.setForeground(QBrush(msg_color)) self.setCurrentCharFormat(tf) self.appendPlainText(msg) # Show in status bar if self.notification: palette = self.notification.palette() palette.setColor(self.notification.foregroundRole(), msg_color) self.notification.setPalette(palette) msg = msg.replace('\n', "; ") # Get rid of new lines in error message elided_text = self.notification.fontMetrics().elidedText( msg, Qt.ElideRight, self.get_available_width()) self.notification.setText(elided_text) # Set button color if self.expandButton: palette = self.expandButton.palette() palette.setColor(self.expandButton.foregroundRole(), msg_color) self.app.processEvents() def showEvent(self, event): self.cleanNotification() super().showEvent(event) def setStatusBar(self, status_bar): self.setVisible(False) self.status_bar = status_bar self.expandButton = QPushButton(self.collapsed_text, parent=self) self.expandButton.setFixedWidth( self.expandButton.fontMetrics().horizontalAdvance( self.collapsed_text) * 1.25) self.expandButton.setCheckable(True) self.expandButton.clicked.connect(self.showLogs) self.status_bar.addWidget(self.expandButton) self.notification = QLabel(self) self.status_bar.addWidget(self.notification) self.notification.setAutoFillBackground(True) self.clear_color = self.expandButton.palette().color( self.notification.foregroundRole()) self.setFormatter( logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) def removeStatusBar(self): self.cleanNotification() self.notification = None def cleanNotification(self): palette = self.notification.palette() palette.setColor(self.notification.foregroundRole(), self.clear_color) self.notification.setPalette(palette) self.notification.setText("") @Slot() def showLogs(self): self.setVisible(self.expandButton.isChecked()) text = self.expanded_text if self.expandButton.isChecked( ) else self.collapsed_text self.expandButton.setText(text) # Calculates maximum width that is free on status bar def get_available_width(self): width = self.status_bar.width() for child in self.status_bar.children(): if hasattr(child, "width") and child != self.notification: width -= child.width() return width - 8 # return calculated width reduced by small safety gap