def excel_like_enter_filter(source: QtWidgets.QTableView, event: QtCore.QEvent): if event.type() == event.KeyPress: if event.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]: if int(source.editTriggers()) > int( QtWidgets.QAbstractItemView.NoEditTriggers): next_row = source.currentIndex().row() + 1 if next_row + 1 > source.model().rowCount(): next_row -= 1 if source.state() == source.EditingState: next_index = source.model().index( next_row, source.currentIndex().column()) source.setCurrentIndex(next_index) else: source.edit(source.currentIndex())
def edit(self, index, trigger, event): value = QTableView.edit(self, index, trigger, event) if int(trigger) in [2, 8, 16]: logMsg('user is now editing a cell') self.state_ = 'E' return value
class Dip3TabWidget(QTabWidget): # Signals need to notify from not Qt thread alias_updated = pyqtSignal(str) diff_updated = pyqtSignal(dict) network_updated = pyqtSignal() def __init__(self, gui, wallet, *args, **kwargs): QTabWidget.__init__(self, *args, **kwargs) self.tab_bar = Dip3TabBar(self) self.setTabBar(self.tab_bar) self.setTabPosition(QTabWidget.East) self.gui = gui self.wallet = wallet self.manager = wallet.protx_manager self.mn_list = gui.network.mn_list if gui.network else None self.reg_cur_protx = '' self.w_cur_alias = '' self.w_cur_state = '' self.w_cur_idx = None self.wallet_mn_tab = self.create_wallet_mn_tab() self.registerd_mn_tab = self.create_registered_mn_tab() self.searchable_list = self.w_model self.currentChanged.connect(self.on_tabs_current_changed) if self.mn_list: self.tab_bar.update_heights(self.mn_list.protx_height, self.mn_list.llmq_human_height, gui.network.get_local_height()) self.mn_list.register_callback(self.on_mn_list_diff_updated, ['mn-list-diff-updated']) self.gui.network.register_callback(self.on_cb_network_updated, ['network_updated']) self.manager.register_callback(self.on_manager_alias_updated, ['manager-alias-updated']) self.alias_updated.connect(self.on_alias_updated) self.diff_updated.connect(self.on_diff_updated) self.network_updated.connect(self.on_network_updated) @pyqtSlot() def on_tabs_current_changed(self): cur_widget = self.currentWidget() if cur_widget == self.wallet_mn_tab: self.searchable_list = self.w_model else: self.searchable_list = self.reg_model def on_mn_list_diff_updated(self, key, value): self.diff_updated.emit(value) def on_manager_alias_updated(self, key, value): self.alias_updated.emit(value) def on_cb_network_updated(self, key): self.network_updated.emit() @pyqtSlot(str) def on_alias_updated(self, alias): self.w_model.reload_data() @pyqtSlot() def on_network_updated(self): self.tab_bar.update_heights(None, None, self.gui.network.get_local_height()) @pyqtSlot(dict) def on_diff_updated(self, value): state = value.get('state', self.mn_list.DIP3_DISABLED) if self.mn_list.load_mns and not self.mn_list.protx_loading: self.reg_model.reload_data() self.w_model.reload_data() if state == self.mn_list.DIP3_ENABLED: self.reg_search_btn.setEnabled(True) else: self.reg_search_btn.setEnabled(False) self.tab_bar.update_heights(self.mn_list.protx_height, self.mn_list.llmq_human_height, self.gui.network.get_local_height()) self.update_registered_label() self.update_wallet_label() def registered_label(self): if not self.mn_list: return _('Offline') state = self.mn_list.protx_state if state == self.mn_list.DIP3_DISABLED: return _('DIP3 Masternodes is currently disabled.') count = len(self.mn_list.protx_mns) connected = self.gui.network.is_connected() loading = connected and self.mn_list.protx_loading ready = _('Loading') if loading else _('Found') return (_('%s %s registered DIP3 Masternodes.') % (ready, count)) def update_registered_label(self): self.reg_label.setText(self.registered_label()) def wallet_label(self): mns = self.manager.mns count = len(mns) mn_str = _('Masternode') if count == 1 else _('Masternodes') def_label_str = _('Wallet contains %s DIP3 %s.') % (count, mn_str) if not self.mn_list: return def_label_str state = self.mn_list.protx_state if state == self.mn_list.DIP3_DISABLED: return (_('DIP3 Masternodes is currently disabled.')) connected = self.gui.network.is_connected() loading = connected and self.mn_list.protx_loading if loading: height = self.mn_list.protx_height return (_('Loading DIP3 data at Height: %s.') % height) return def_label_str def update_wallet_label(self): self.w_label.setText(self.wallet_label()) def create_registered_mn_tab(self): w = QWidget() hw = QWidget() self.reg_label = QLabel(self.registered_label()) self.reg_search_btn = QPushButton(_('Search')) self.reg_search_btn.clicked.connect(self.on_reg_search) src_model = RegisteredMNsModel(self.mn_list) self.reg_model = Dip3FilterProxyModel() self.reg_model.setSourceModel(src_model) self.reg_view = QTableView() self.reg_view.setContextMenuPolicy(Qt.CustomContextMenu) self.reg_view.customContextMenuRequested.connect(self.create_reg_menu) self.reg_hheader = QHeaderView(Qt.Horizontal, self.reg_view) self.reg_hheader.setSectionResizeMode(QHeaderView.ResizeToContents) self.reg_hheader.setStretchLastSection(True) self.reg_view.setHorizontalHeader(self.reg_hheader) self.reg_view.verticalHeader().hide() self.reg_view.setModel(self.reg_model) self.reg_view.setSelectionMode(QAbstractItemView.SingleSelection) self.reg_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.reg_view.doubleClicked.connect(self.reg_mn_dbl_clicked) sel_model = self.reg_view.selectionModel() sel_model.selectionChanged.connect(self.on_reg_selection_changed) hbox = QHBoxLayout() vbox = QVBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) hbox.addWidget(self.reg_label) hbox.addStretch(1) hbox.addWidget(self.reg_search_btn) hw.setLayout(hbox) vbox.addWidget(hw) vbox.addWidget(self.reg_view) w.setLayout(vbox) self.addTab(w, read_QIcon('tab_search.png'), _('Registered MNs')) if not self.mn_list.protx_loading: self.reg_model.reload_data() return w def create_reg_menu(self, position): menu = QMenu() h = self.reg_cur_protx menu.addAction(_('Details'), lambda: Dip3MNInfoDialog(self, protx_hash=h).show()) menu.exec_(self.reg_view.viewport().mapToGlobal(position)) def create_wallet_mn_tab(self): w = QWidget() hw = QWidget() self.w_label = QLabel(self.wallet_label()) self.w_add_btn = QPushButton(_('Add / Import')) self.w_file_btn = QPushButton(_('File')) self.w_del_btn = QPushButton(_('Remove')) self.w_up_params_btn = QPushButton(_('Update Params')) self.w_up_coll_btn = QPushButton(_('Change Collateral')) self.w_protx_btn = QPushButton(_('Register')) self.w_up_srv_btn = QPushButton(_('Update Service')) self.w_up_reg_btn = QPushButton(_('Update Registrar')) self.w_add_btn.clicked.connect(self.on_add_masternode) self.w_file_btn.clicked.connect(self.on_file) self.w_del_btn.clicked.connect(self.on_del_masternode) self.w_up_params_btn.clicked.connect(self.on_update_params) self.w_up_coll_btn.clicked.connect(self.on_update_collateral) self.w_protx_btn.clicked.connect(self.on_make_pro_reg_tx) self.w_up_srv_btn.clicked.connect(self.on_make_pro_up_srv_tx) self.w_up_reg_btn.clicked.connect(self.on_make_pro_up_reg_tx) self.w_view = QTableView() self.w_view.setContextMenuPolicy(Qt.CustomContextMenu) self.w_view.customContextMenuRequested.connect(self.create_wallet_menu) self.w_hheader = QHeaderView(Qt.Horizontal, self.w_view) self.w_hheader.setSectionResizeMode(QHeaderView.ResizeToContents) self.w_hheader.setStretchLastSection(True) self.w_view.setHorizontalHeader(self.w_hheader) self.w_view.verticalHeader().hide() self.w_view.setSelectionMode(QAbstractItemView.SingleSelection) self.w_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.w_view.doubleClicked.connect(self.w_mn_dbl_clicked) row_h = self.w_view.verticalHeader().defaultSectionSize() self.w_hheader.setMinimumSectionSize(row_h) src_model = WalletMNsModel(self.manager, self.mn_list, self.gui, row_h) src_model.dataChanged.connect(self.w_data_changed) self.w_model = Dip3FilterProxyModel() self.w_model.setSourceModel(src_model) self.w_view.setModel(self.w_model) sel_model = self.w_view.selectionModel() sel_model.selectionChanged.connect(self.on_wallet_selection_changed) self.w_model.modelReset.connect(self.on_wallet_model_reset) hbox = QHBoxLayout() vbox = QVBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) hbox.addWidget(self.w_label) hbox.addStretch(1) hbox.addWidget(self.w_del_btn) hbox.addWidget(self.w_up_params_btn) hbox.addWidget(self.w_up_coll_btn) hbox.addWidget(self.w_protx_btn) hbox.addWidget(self.w_up_reg_btn) hbox.addWidget(self.w_up_srv_btn) hbox.addWidget(self.w_file_btn) hbox.addWidget(self.w_add_btn) hw.setLayout(hbox) vbox.addWidget(hw) vbox.addWidget(self.w_view) w.setLayout(vbox) self.addTab(w, read_QIcon('tab_dip3.png'), _('Wallet MNs')) if not self.mn_list.protx_loading: self.w_model.reload_data() return w @pyqtSlot() def on_reg_search(self): self.gui.toggle_search() def create_wallet_menu(self, position): a = self.w_cur_alias s = self.w_cur_state i = self.w_cur_idx if not i: return mn = self.manager.mns.get(a) owned = mn.is_owned operated = mn.is_operated protx_hash = mn.protx_hash removed = (s == WalletMNsModel.STATE_REMOVED) menu = QMenu() menu.addAction(_('Remove'), self.on_del_masternode) if not protx_hash: menu.addAction(_('Update Params'), self.on_update_params) if removed and owned: menu.addAction(_('Change Collateral'), self.on_update_collateral) if owned and not protx_hash: menu.addAction(_('Register Masternode'), self.on_make_pro_reg_tx) if operated and protx_hash and not removed: menu.addAction(_('Update Service'), self.on_make_pro_up_srv_tx) if owned and protx_hash and not removed: menu.addAction(_('Update Registrar'), self.on_make_pro_up_reg_tx) menu.addSeparator() menu.addAction(_('Rename Alias'), lambda: self.w_view.edit(i)) menu.addSeparator() menu.addAction(_('Details'), lambda: Dip3MNInfoDialog(self, alias=a).show()) menu.exec_(self.w_view.viewport().mapToGlobal(position)) @pyqtSlot() def on_update_collateral(self): mn = self.manager.mns.get(self.w_cur_alias) removed = (self.w_cur_state == WalletMNsModel.STATE_REMOVED) if not mn or not mn.is_owned or not mn.protx_hash or not removed: return start_id = Dip3MasternodeWizard.COLLATERAL_PAGE wiz = Dip3MasternodeWizard(self, mn=mn, start_id=start_id) wiz.open() @pyqtSlot() def on_update_params(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or mn.protx_hash: return if mn.is_owned: start_id = Dip3MasternodeWizard.COLLATERAL_PAGE else: start_id = Dip3MasternodeWizard.SERVICE_PAGE wiz = Dip3MasternodeWizard(self, mn=mn, start_id=start_id) wiz.open() @pyqtSlot() def on_make_pro_reg_tx(self): try: pro_reg_tx = self.manager.prepare_pro_reg_tx(self.w_cur_alias) except ProRegTxExc as e: self.gui.show_error(e) return self.gui.payto_e.setText(self.wallet.get_unused_address()) self.gui.extra_payload.set_extra_data(SPEC_PRO_REG_TX, pro_reg_tx) self.gui.show_extra_payload() self.gui.tabs.setCurrentIndex(self.gui.tabs.indexOf(self.gui.send_tab)) @pyqtSlot() def on_file(self): wiz = Dip3FileWizard(self) wiz.open() @pyqtSlot() def on_add_masternode(self): wiz = Dip3MasternodeWizard(self) wiz.open() @pyqtSlot() def on_make_pro_up_srv_tx(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or not mn.protx_hash: return wiz = Dip3MasternodeWizard(self, mn=mn, start_id=Dip3MasternodeWizard.UPD_SRV_PAGE) wiz.open() @pyqtSlot() def on_make_pro_up_reg_tx(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or not mn.protx_hash: return wiz = Dip3MasternodeWizard(self, mn=mn, start_id=Dip3MasternodeWizard.UPD_REG_PAGE) wiz.open() @pyqtSlot() def on_del_masternode(self): alias = self.w_cur_alias mn = self.manager.mns.get(alias) if not mn: return if not self.gui.question( _('Do you want to remove the masternode ' 'configuration for %s?') % alias): return if mn.protx_hash: if not self.gui.question( _('Masternode %s has RroRegTxHash ' 'already set. Are you sure?') % alias): return self.manager.remove_mn(self.w_cur_alias) self.w_model.reload_data() @pyqtSlot() def on_reg_selection_changed(self): sel = self.reg_view.selectionModel() if sel.hasSelection(): idx = sel.selectedRows()[0] self.reg_cur_protx = idx.data() else: self.reg_cur_protx = '' @pyqtSlot() def on_wallet_model_reset(self): self.update_wallet_label() self.w_file_btn.show() self.w_add_btn.show() self.w_up_params_btn.hide() self.w_up_coll_btn.hide() self.w_protx_btn.hide() self.w_up_srv_btn.hide() self.w_up_reg_btn.hide() self.w_del_btn.hide() @pyqtSlot() def on_wallet_selection_changed(self): sel = self.w_view.selectionModel() if not sel.hasSelection(): self.w_cur_alias = '' self.w_cur_state = '' self.w_cur_idx = None self.w_add_btn.show() self.w_file_btn.show() self.w_protx_btn.hide() self.w_del_btn.hide() self.w_up_params_btn.hide() self.w_up_coll_btn.hide() self.w_up_srv_btn.hide() self.w_up_reg_btn.hide() return self.w_add_btn.hide() self.w_file_btn.hide() idx = sel.selectedRows()[0] self.w_cur_alias = idx.data() self.w_cur_state = idx.sibling(idx.row(), 1).data(Qt.ToolTipRole) self.w_cur_idx = idx mn = self.manager.mns.get(self.w_cur_alias) owned = mn.is_owned operated = mn.is_operated protx_hash = mn.protx_hash removed = (self.w_cur_state == WalletMNsModel.STATE_REMOVED) self.w_del_btn.show() if not protx_hash: self.w_up_params_btn.show() else: self.w_up_params_btn.hide() if removed and owned: self.w_up_coll_btn.show() else: self.w_up_coll_btn.hide() if owned and not protx_hash: self.w_protx_btn.show() else: self.w_protx_btn.hide() if operated and protx_hash and not removed: self.w_up_srv_btn.show() else: self.w_up_srv_btn.hide() if owned and protx_hash and not removed: self.w_up_reg_btn.show() else: self.w_up_reg_btn.hide() @pyqtSlot(QModelIndex) def reg_mn_dbl_clicked(self, idx): row_idx = idx.sibling(idx.row(), 0) d = Dip3MNInfoDialog(self, protx_hash=row_idx.data()) d.show() @pyqtSlot(QModelIndex) def w_mn_dbl_clicked(self, idx): col = idx.column() if col == WalletMNsModel.ALIAS: return row_idx = idx.sibling(idx.row(), 0) d = Dip3MNInfoDialog(self, alias=row_idx.data()) d.show() @pyqtSlot(QModelIndex) def w_data_changed(self, idx): sel_model = self.w_view.selectionModel() sel_model.clear() sel_model.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) sel_model.select( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
class Dip3TabWidget(QTabWidget): # Signals need to notify from not Qt thread alias_updated = pyqtSignal(str) diff_updated = pyqtSignal(dict) info_updated = pyqtSignal(str) network_updated = pyqtSignal() def __init__(self, gui, wallet, *args, **kwargs): QTabWidget.__init__(self, *args, **kwargs) self.tab_bar = Dip3TabBar(self) self.setTabBar(self.tab_bar) self.setTabPosition(QTabWidget.East) self.gui = gui self.wallet = wallet self.manager = wallet.protx_manager self.mn_list = gui.network.mn_list if gui.network else None self.reg_cur_protx = '' self.reg_cur_idx = None self.w_cur_alias = '' self.w_cur_state = '' self.w_cur_idx = None self.wallet_mn_tab = self.create_wallet_mn_tab() if self.mn_list: self.registerd_mn_tab = self.create_registered_mn_tab() self.searchable_list = self.w_model self.currentChanged.connect(self.on_tabs_current_changed) if self.mn_list: self.tab_bar.update_stats(self.get_stats()) util.register_callback(self.on_mn_list_diff_updated, ['mn-list-diff-updated']) util.register_callback(self.on_mn_list_info_updated, ['mn-list-info-updated']) if self.gui.network: util.register_callback(self.on_cb_network_updated, ['network_updated']) util.register_callback(self.on_manager_alias_updated, ['manager-alias-updated']) self.alias_updated.connect(self.on_alias_updated) self.diff_updated.connect(self.on_diff_updated) self.info_updated.connect(self.on_info_updated) self.network_updated.connect(self.on_network_updated) def unregister_callbacks(self): if self.mn_list: util.unregister_callback(self.on_mn_list_diff_updated) util.unregister_callback(self.on_mn_list_info_updated) if self.gui.network: util.unregister_callback(self.on_cb_network_updated) util.unregister_callback(self.on_manager_alias_updated) def get_stats(self): mn_list = self.mn_list local_height = self.gui.network.get_local_height() protx_height = mn_list.protx_height llmq_height = mn_list.llmq_human_height protx_ready = 'Yes' if mn_list.protx_ready else 'No' llmq_ready = 'Yes' if mn_list.llmq_ready else 'No' completeness = mn_list.protx_info_completeness protx_info_completeness = '%s%%' % round(completeness * 100) return (local_height, protx_height, llmq_height, protx_ready, llmq_ready, protx_info_completeness) @pyqtSlot() def on_tabs_current_changed(self): cur_widget = self.currentWidget() if cur_widget == self.wallet_mn_tab: self.searchable_list = self.w_model else: self.searchable_list = self.reg_model def on_mn_list_diff_updated(self, key, value): self.diff_updated.emit(value) def on_mn_list_info_updated(self, key, value): self.info_updated.emit(value) def on_manager_alias_updated(self, key, value): self.alias_updated.emit(value) def on_cb_network_updated(self, key): self.network_updated.emit() @pyqtSlot(str) def on_alias_updated(self, alias): self.w_model.reload_data() self.restore_w_selection() @pyqtSlot() def on_network_updated(self): self.tab_bar.update_stats(self.get_stats()) @pyqtSlot(dict) def on_diff_updated(self, value): state = value.get('state', self.mn_list.DIP3_DISABLED) if self.mn_list.load_mns and not self.mn_list.protx_loading: self.reg_model.reload_data() self.restore_reg_selection() self.w_model.reload_data() self.restore_w_selection() if state == self.mn_list.DIP3_ENABLED: self.reg_search_btn.setEnabled(True) else: self.reg_search_btn.setEnabled(False) self.tab_bar.update_stats(self.get_stats()) self.update_registered_label() self.update_wallet_label() @pyqtSlot(str) def on_info_updated(self, value): self.tab_bar.update_stats(self.get_stats()) def registered_label(self): if not self.mn_list: return _('Offline') state = self.mn_list.protx_state if state == self.mn_list.DIP3_DISABLED: return _('DIP3 Masternodes is currently disabled.') count = len(self.mn_list.protx_mns) connected = self.gui.network.is_connected() loading = connected and self.mn_list.protx_loading ready = _('Loading') if loading else _('Found') return (_('%s %s registered DIP3 Masternodes.') % (ready, count)) def update_registered_label(self): self.reg_label.setText(self.registered_label()) def wallet_label(self): mns = self.manager.mns count = len(mns) mn_str = _('Masternode') if count == 1 else _('Masternodes') def_label_str = _('Wallet contains %s DIP3 %s.') % (count, mn_str) if not self.mn_list: return def_label_str state = self.mn_list.protx_state if state == self.mn_list.DIP3_DISABLED: return (_('DIP3 Masternodes is currently disabled.')) connected = self.gui.network.is_connected() loading = connected and self.mn_list.protx_loading if loading: height = self.mn_list.protx_height return (_('Loading DIP3 data at Height: %s.') % height) return def_label_str def update_wallet_label(self): self.w_label.setText(self.wallet_label()) def create_registered_mn_tab(self): w = QWidget() hw = QWidget() self.reg_label = QLabel(self.registered_label()) self.reg_search_btn = QPushButton(_('Search')) self.reg_search_btn.clicked.connect(self.on_reg_search) src_model = RegisteredMNsModel(self.mn_list) self.reg_model = Dip3FilterProxyModel() self.reg_model.setSourceModel(src_model) self.reg_view = QTableView() self.reg_view.setContextMenuPolicy(Qt.CustomContextMenu) self.reg_view.customContextMenuRequested.connect(self.create_reg_menu) self.reg_hheader = QHeaderView(Qt.Horizontal, self.reg_view) self.reg_hheader.setSectionResizeMode(QHeaderView.ResizeToContents) self.reg_hheader.setStretchLastSection(True) self.reg_view.setHorizontalHeader(self.reg_hheader) self.reg_view.verticalHeader().hide() self.reg_view.setModel(self.reg_model) self.reg_view.setSelectionMode(QAbstractItemView.SingleSelection) self.reg_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.reg_view.doubleClicked.connect(self.reg_mn_dbl_clicked) sel_model = self.reg_view.selectionModel() sel_model.selectionChanged.connect(self.on_reg_selection_changed) hbox = QHBoxLayout() vbox = QVBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) hbox.addWidget(self.reg_label) hbox.addStretch(1) hbox.addWidget(self.reg_search_btn) hw.setLayout(hbox) vbox.addWidget(hw) vbox.addWidget(self.reg_view) w.setLayout(vbox) self.addTab(w, read_QIcon('tab_search.png'), _('Registered MNs')) if not self.mn_list.protx_loading: self.reg_model.reload_data() self.restore_reg_selection() return w def create_reg_menu(self, position): menu = QMenu() h = self.reg_cur_protx menu.addAction(_('Details'), lambda: self.show_protx_hash_info(h)) menu.exec_(self.reg_view.viewport().mapToGlobal(position)) def create_wallet_mn_tab(self): w = QWidget() hw = QWidget() self.w_label = QLabel(self.wallet_label()) self.w_add_btn = QPushButton(_('Add / Import')) self.w_file_btn = QPushButton(_('File')) self.w_del_btn = QPushButton(_('Remove')) self.w_clear_protx_btn = QPushButton(_('Clear ProTxHash')) self.w_up_params_btn = QPushButton(_('Update Params')) self.w_protx_btn = QPushButton(_('Register')) self.w_up_reg_btn = QPushButton(_('Update Registrar')) self.w_up_rev_btn = QPushButton(_('Revoke')) self.w_new_bls_bnt = QPushButton(_('New BLS Keys')) self.w_up_srv_btn = QPushButton(_('Update Service')) self.w_add_btn.clicked.connect(self.on_add_masternode) self.w_file_btn.clicked.connect(self.on_file) self.w_del_btn.clicked.connect(self.on_del_masternode) self.w_clear_protx_btn.clicked.connect(self.on_clear_protx) self.w_up_params_btn.clicked.connect(self.on_update_params) self.w_protx_btn.clicked.connect(self.on_make_pro_reg_tx) self.w_up_reg_btn.clicked.connect(self.on_make_pro_up_reg_tx) self.w_up_rev_btn.clicked.connect(self.on_make_pro_up_rev_tx) self.w_new_bls_bnt.clicked.connect(self.on_new_bls) self.w_up_srv_btn.clicked.connect(self.on_make_pro_up_srv_tx) self.hide_mn_buttons() self.w_view = QTableView() self.w_view.setContextMenuPolicy(Qt.CustomContextMenu) self.w_view.customContextMenuRequested.connect(self.create_wallet_menu) self.w_hheader = QHeaderView(Qt.Horizontal, self.w_view) self.w_hheader.setSectionResizeMode(QHeaderView.ResizeToContents) self.w_hheader.setStretchLastSection(True) self.w_view.setHorizontalHeader(self.w_hheader) self.w_view.verticalHeader().hide() self.w_view.setSelectionMode(QAbstractItemView.SingleSelection) self.w_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.w_view.doubleClicked.connect(self.w_mn_dbl_clicked) row_h = self.w_view.verticalHeader().defaultSectionSize() self.w_hheader.setMinimumSectionSize(row_h) src_model = WalletMNsModel(self.manager, self.mn_list, self.gui, row_h) src_model.dataChanged.connect(self.w_data_changed) self.w_model = Dip3FilterProxyModel() self.w_model.setSourceModel(src_model) self.w_view.setModel(self.w_model) sel_model = self.w_view.selectionModel() sel_model.selectionChanged.connect(self.on_wallet_selection_changed) self.w_model.modelReset.connect(self.on_wallet_model_reset) hbox = QHBoxLayout() vbox = QVBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) hbox.addWidget(self.w_label) hbox.addStretch(1) hbox.addWidget(self.w_add_btn) hw.setLayout(hbox) vbox.addWidget(hw) vbox.addWidget(self.w_view) self.addTab(w, read_QIcon('tab_dip3.png'), _('Wallet MNs')) if self.mn_list and not self.mn_list.protx_loading: self.w_model.reload_data() self.restore_w_selection() hw = QWidget() hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(self.w_file_btn) hbox.addWidget(self.w_del_btn) hbox.addWidget(self.w_clear_protx_btn) hbox.addWidget(self.w_up_params_btn) hbox.addWidget(self.w_protx_btn) hbox.addWidget(self.w_up_reg_btn) hbox.addWidget(self.w_up_rev_btn) hbox.addWidget(self.w_new_bls_bnt) hbox.addWidget(self.w_up_srv_btn) hw.setLayout(hbox) vbox.addWidget(hw) w.setLayout(vbox) return w @pyqtSlot() def on_reg_search(self): self.gui.toggle_search() def create_wallet_menu(self, position): a = self.w_cur_alias s = self.w_cur_state i = self.w_cur_idx menu = QMenu() if not i or s == WalletMNsModel.STATE_LOADING: menu.addAction(_('Add / Import'), self.on_add_masternode) menu.addAction(_('File'), self.on_file) menu.exec_(self.w_view.viewport().mapToGlobal(position)) return mn = self.manager.mns.get(a) owned = mn.is_owned operated = mn.is_operated protx_hash = mn.protx_hash removed = (s == WalletMNsModel.STATE_REMOVED) menu.addAction(_('Remove'), self.on_del_masternode) if removed: menu.addAction(_('Clear ProTxHash'), self.on_clear_protx) if not protx_hash: menu.addAction(_('Update Params'), self.on_update_params) if owned and not protx_hash: menu.addAction(_('Register'), self.on_make_pro_reg_tx) if (owned and protx_hash and not removed and self.wallet.is_mine(mn.owner_addr)): menu.addAction(_('Update Registrar'), self.on_make_pro_up_reg_tx) if operated and not owned and protx_hash and not removed: menu.addAction(_('Revoke'), self.on_make_pro_up_rev_tx) if operated and not owned and protx_hash: menu.addAction(_('New BLS Keys'), self.on_new_bls) if operated and protx_hash and not removed: menu.addAction(_('Update Service'), self.on_make_pro_up_srv_tx) menu.addSeparator() menu.addAction(_('Rename Alias'), lambda: self.w_view.edit(i)) menu.addSeparator() menu.addAction(_('Details'), lambda: self.show_alias_info(a)) menu.addSeparator() menu.addAction(_('Add / Import'), self.on_add_masternode) menu.addAction(_('File'), self.on_file) menu.exec_(self.w_view.viewport().mapToGlobal(position)) @pyqtSlot() def on_new_bls(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or not mn.is_operated: return start_id = Dip3MasternodeWizard.BLS_KEYS_PAGE wiz = Dip3MasternodeWizard(self, mn=mn, start_id=start_id) wiz.open() @pyqtSlot() def on_update_params(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or mn.protx_hash: return if mn.is_owned: start_id = Dip3MasternodeWizard.COLLATERAL_PAGE else: start_id = Dip3MasternodeWizard.SERVICE_PAGE wiz = Dip3MasternodeWizard(self, mn=mn, start_id=start_id) wiz.open() @pyqtSlot() def on_make_pro_reg_tx(self): alias = self.w_cur_alias try: pro_reg_tx = self.manager.prepare_pro_reg_tx(alias) except ProRegTxExc as e: self.gui.show_error(e) return mn = self.manager.mns.get(alias) gui = self.gui gui.do_clear() if mn.collateral.is_null: gui.amount_e.setText('1000') mn_addrs = [mn.owner_addr, mn.voting_addr, mn.payout_address] for addr in self.wallet.get_unused_addresses(): if addr not in mn_addrs: gui.payto_e.setText(addr) break gui.extra_payload.set_extra_data(SPEC_PRO_REG_TX, pro_reg_tx, alias) gui.show_extra_payload() gui.tabs.setCurrentIndex(self.gui.tabs.indexOf(self.gui.send_tab)) @pyqtSlot() def on_file(self): wiz = Dip3FileWizard(self) wiz.open() @pyqtSlot() def on_add_masternode(self): wiz = Dip3MasternodeWizard(self) wiz.open() @pyqtSlot() def on_make_pro_up_srv_tx(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or not mn.protx_hash: return wiz = Dip3MasternodeWizard(self, mn=mn, start_id=Dip3MasternodeWizard.UPD_SRV_PAGE) wiz.open() @pyqtSlot() def on_make_pro_up_rev_tx(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or not mn.protx_hash: return d = RevokeOperatiorDialog(self, mn) if d.exec_() and d.reason is not None: try: rev_tx = self.manager.prepare_pro_up_rev_tx(mn.alias, d.reason) except ProRegTxExc as e: self.gui.show_error(e) return gui = self.gui gui.do_clear() mn_addrs = [mn.owner_addr, mn.voting_addr, mn.payout_address] for addr in self.manager.wallet.get_unused_addresses(): if addr not in mn_addrs: gui.payto_e.setText(addr) break tx_type = SPEC_PRO_UP_REV_TX gui.extra_payload.set_extra_data(tx_type, rev_tx, mn.alias) gui.show_extra_payload() gui.tabs.setCurrentIndex(gui.tabs.indexOf(gui.send_tab)) @pyqtSlot() def on_make_pro_up_reg_tx(self): mn = self.manager.mns.get(self.w_cur_alias) if not mn or not mn.protx_hash: return wiz = Dip3MasternodeWizard(self, mn=mn, start_id=Dip3MasternodeWizard.UPD_REG_PAGE) wiz.open() @pyqtSlot() def on_del_masternode(self): alias = self.w_cur_alias mn = self.manager.mns.get(alias) if not mn: return if not self.gui.question( _('Do you want to remove the masternode ' 'configuration for %s?') % alias): return if mn.protx_hash: if not self.gui.question( _('Masternode %s has ProRegTxHash ' 'already set. Are you sure?') % alias): return self.manager.remove_mn(self.w_cur_alias) self.w_model.reload_data() @pyqtSlot() def on_clear_protx(self): alias = self.w_cur_alias mn = self.manager.mns.get(alias) if not mn: return q = '\n'.join([ _('Clearing of ProTxHash allow reuse of masternode.'), _('Do you want to clear ProTxHash for %s?') % alias ]) if not self.gui.question(q): return if not mn.service.port: # service cleared on Revoke operator mn.service = ProTxService('', ProTxMN.default_port()) mn.protx_hash = '' self.manager.update_mn(alias, mn) self.w_model.reload_alias(alias) @pyqtSlot() def on_reg_selection_changed(self): sel = self.reg_view.selectionModel() if sel.hasSelection(): idx = sel.selectedRows()[0] self.reg_cur_idx = idx self.reg_cur_protx = idx.data() else: self.reg_cur_idx = None self.reg_cur_protx = '' def restore_reg_selection(self): cur_idx = self.reg_cur_idx if not cur_idx: return row_count = self.reg_model.sourceModel().row_count if row_count == 0 or row_count <= cur_idx.row(): self.reg_cur_idx = None self.reg_cur_protx = '' else: self.reg_view.selectRow(cur_idx.row()) def hide_mn_buttons(self): self.w_del_btn.hide() self.w_clear_protx_btn.hide() self.w_up_params_btn.hide() self.w_protx_btn.hide() self.w_up_reg_btn.hide() self.w_up_rev_btn.hide() self.w_new_bls_bnt.hide() self.w_up_srv_btn.hide() @pyqtSlot() def on_wallet_model_reset(self): self.update_wallet_label() self.hide_mn_buttons() @pyqtSlot() def on_wallet_selection_changed(self): sel = self.w_view.selectionModel() if not sel.hasSelection(): self.w_cur_alias = '' self.w_cur_state = '' self.w_cur_idx = None self.hide_mn_buttons() return idx = sel.selectedRows()[0] self.w_cur_alias = idx.data() self.w_cur_state = idx.sibling(idx.row(), 1).data(Qt.ToolTipRole) self.w_cur_idx = idx mn = self.manager.mns.get(self.w_cur_alias) if not mn or self.w_cur_state == WalletMNsModel.STATE_LOADING: return owned = mn.is_owned operated = mn.is_operated protx_hash = mn.protx_hash removed = (self.w_cur_state == WalletMNsModel.STATE_REMOVED) self.w_del_btn.show() if removed: self.w_clear_protx_btn.show() if not protx_hash: self.w_up_params_btn.show() else: self.w_up_params_btn.hide() if operated and not owned and protx_hash: self.w_new_bls_bnt.show() else: self.w_new_bls_bnt.hide() if owned and not protx_hash: self.w_protx_btn.show() else: self.w_protx_btn.hide() if operated and not owned and protx_hash and not removed: self.w_up_rev_btn.show() else: self.w_up_rev_btn.hide() if operated and protx_hash and not removed: self.w_up_srv_btn.show() else: self.w_up_srv_btn.hide() if (owned and protx_hash and not removed and self.wallet.is_mine(mn.owner_addr)): self.w_up_reg_btn.show() else: self.w_up_reg_btn.hide() def restore_w_selection(self): cur_idx = self.w_cur_idx if not cur_idx: return row_count = self.w_model.sourceModel().row_count if row_count == 0 or row_count <= cur_idx.row(): self.w_cur_alias = '' self.w_cur_state = '' self.w_cur_idx = None else: self.w_view.selectRow(cur_idx.row()) @pyqtSlot(QModelIndex) def reg_mn_dbl_clicked(self, idx): row_idx = idx.sibling(idx.row(), 0) self.show_protx_hash_info(row_idx.data()) def show_protx_hash_info(self, protx_hash): d = Dip3MNInfoDialog(self, protx_hash=protx_hash) mn_info_dialogs.append(d) d.show() @pyqtSlot(QModelIndex) def w_mn_dbl_clicked(self, idx): col = idx.column() if col == WalletMNsModel.ALIAS: return row_idx = idx.sibling(idx.row(), 0) self.show_alias_info(row_idx.data()) def show_alias_info(self, alias): d = Dip3MNInfoDialog(self, alias=alias) mn_info_dialogs.append(d) d.show() @pyqtSlot(QModelIndex) def w_data_changed(self, idx): sel_model = self.w_view.selectionModel() sel_model.clear() sel_model.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) sel_model.select( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
class ReferenceDataDlg(QDialog): def __init__(self, parent=None): super(ReferenceDataDlg, self).__init__(parent) self.model = QSqlTableModel(self) self.model.setTable("reference") self.model.setSort(ID, Qt.AscendingOrder) self.model.setHeaderData(ID, Qt.Horizontal, "ID") self.model.setHeaderData(CATEGORY, Qt.Horizontal, "小车编号") self.model.setHeaderData(SHORTDESC, Qt.Horizontal, "检查记录") self.model.setHeaderData(LONGDESC, Qt.Horizontal, "巡检日期") self.model.select() self.view = QTableView() self.view.setModel(self.model) self.view.setSelectionMode(QTableView.SingleSelection) self.view.setSelectionBehavior(QTableView.SelectRows) self.view.setColumnHidden(ID, True) self.view.resizeColumnsToContents() buttonBox = QDialogButtonBox() addButton = buttonBox.addButton("&添加", QDialogButtonBox.ActionRole) deleteButton = buttonBox.addButton("&删除", QDialogButtonBox.ActionRole) sortButton = buttonBox.addButton("&排序", QDialogButtonBox.ActionRole) if not MAC: addButton.setFocusPolicy(Qt.NoFocus) deleteButton.setFocusPolicy(Qt.NoFocus) sortButton.setFocusPolicy(Qt.NoFocus) menu = QMenu(self) sortByCategoryAction = menu.addAction("按小车编号排序") sortByDescriptionAction = menu.addAction("按检查记录排序") sortByIDAction = menu.addAction("按编号顺序排序") sortButton.setMenu(menu) closeButton = buttonBox.addButton(QDialogButtonBox.Close) layout = QVBoxLayout() layout.addWidget(self.view) layout.addWidget(buttonBox) self.setLayout(layout) addButton.clicked.connect(self.addRecord) deleteButton.clicked.connect(self.deleteRecord) sortByCategoryAction.triggered.connect(lambda: self.sort(CATEGORY)) sortByDescriptionAction.triggered.connect(lambda: self.sort(SHORTDESC)) sortByIDAction.triggered.connect(lambda: self.sort(ID)) closeButton.clicked.connect(self.accept) self.setWindowTitle("巡检历史数据") self.setWindowIcon( QtGui.QIcon('icon/update_128px_1156069_easyicon.net.ico')) def addRecord(self): row = self.model.rowCount() self.model.insertRow(row) index = self.model.index(row, CATEGORY) self.view.setCurrentIndex(index) self.view.edit(index) def deleteRecord(self): index = self.view.currentIndex() if not index.isValid(): return record = self.model.record(index.row()) category = record.value(CATEGORY) desc = record.value(SHORTDESC) if (QMessageBox.question(self, "Reference Data", ("确定删除 {1} 的数据?".format(desc, category)), QMessageBox.Yes | QMessageBox.No) == QMessageBox.No): return self.model.removeRow(index.row()) self.model.submitAll() self.model.select() def sort(self, column): self.model.setSort(column, Qt.AscendingOrder) self.model.select()
class EditPandasTable(QWidget): """A Widget to display and edit a pandas DataFrame """ def __init__(self, data=pandas.DataFrame([]), ui_buttons=True, parent=None): super().__init__(parent) self.ui_buttons = ui_buttons self.model = EditPandasModel(data) self.view = QTableView() self.view.setModel(self.model) self.layout = QHBoxLayout() self.rows_chkbx = QSpinBox() self.rows_chkbx.setMinimum(1) self.cols_chkbx = QSpinBox() self.cols_chkbx.setMinimum(1) self.init_ui() def init_ui(self): self.layout.addWidget(self.view) if self.ui_buttons: bt_layout = QVBoxLayout() addr_layout = QHBoxLayout() addr_bt = QPushButton('Add Row') addr_bt.clicked.connect(self.add_row) addr_layout.addWidget(addr_bt) addr_layout.addWidget(self.rows_chkbx) bt_layout.addLayout(addr_layout) addc_layout = QHBoxLayout() addc_bt = QPushButton('Add Column') addc_bt.clicked.connect(self.add_column) addc_layout.addWidget(addc_bt) addc_layout.addWidget(self.cols_chkbx) bt_layout.addLayout(addc_layout) rmr_bt = QPushButton('Remove Row') rmr_bt.clicked.connect(self.remove_row) bt_layout.addWidget(rmr_bt) rmc_bt = QPushButton('Remove Column') rmc_bt.clicked.connect(self.remove_column) bt_layout.addWidget(rmc_bt) edit_bt = QPushButton('Edit') edit_bt.clicked.connect(self.edit_item) bt_layout.addWidget(edit_bt) editrh_bt = QPushButton('Edit Row-Header') editrh_bt.clicked.connect(self.edit_row_header) bt_layout.addWidget(editrh_bt) editch_bt = QPushButton('Edit Column-Header') editch_bt.clicked.connect(self.edit_col_header) bt_layout.addWidget(editch_bt) self.layout.addLayout(bt_layout) self.setLayout(self.layout) def content_changed(self): """Informs ModelView about external change made in data """ self.model.layoutChanged.emit() def update_data(self): """Has to be called, when model._data is rereferenced by for example add_row to keep external data updated Returns ------- data : pandas.DataFrame The DataFrame of this widget Notes ----- You can overwrite this function in a subclass e.g. to update an objects attribute (e.g. obj.data = self.model._data) """ return self.model._data def replace_data(self, new_data): """Replaces model._data with new_data """ self.model._data = new_data self.content_changed() def add_row(self): row = self.view.selectionModel().currentIndex().row() # Add row at the bottom if nothing is selected if row == -1 or len(self.view.selectionModel().selectedIndexes()) == 0: row = len(self.model._data.index) self.model.insertRows(row, self.rows_chkbx.value()) self.update_data() def add_column(self): column = self.view.selectionModel().currentIndex().column() # Add column to the right if nothing is selected if column == -1 or len(self.view.selectionModel().selectedIndexes()) == 0: column = len(self.model._data.columns) self.model.insertColumns(column, self.cols_chkbx.value()) self.update_data() def remove_row(self): rows = sorted(set([ix.row() for ix in self.view.selectionModel().selectedIndexes()]), reverse=True) for row in rows: self.model.removeRow(row) self.update_data() def remove_column(self): columns = sorted(set([ix.column() for ix in self.view.selectionModel().selectedIndexes()]), reverse=True) for column in columns: self.model.removeColumn(column) self.update_data() def edit_item(self): self.view.edit(self.view.selectionModel().currentIndex()) def edit_row_header(self): row = self.view.selectionModel().currentIndex().row() old_value = self.model._data.index[row] text, ok = QInputDialog.getText(self, 'Change Row-Header', f'Change {old_value} in row {row} to:') if text and ok: self.model.setHeaderData(row, Qt.Vertical, text) def edit_col_header(self): column = self.view.selectionModel().currentIndex().column() old_value = self.model._data.columns[column] text, ok = QInputDialog.getText(self, 'Change Column-Header', f'Change {old_value} in column {column} to:') if text and ok: self.model.setHeaderData(column, Qt.Horizontal, text)
class MainForm(QDialog): def __init__(self): super(MainForm, self).__init__() self.assetModel = QSqlRelationalTableModel(self) self.assetModel.setTable("assets") self.assetModel.setRelation(CATEGORYID, QSqlRelation("categories", "id", "name")) self.assetModel.setSort(ROOM, Qt.AscendingOrder) self.assetModel.setHeaderData(ID, Qt.Horizontal, "ID") self.assetModel.setHeaderData(NAME, Qt.Horizontal, "Name") self.assetModel.setHeaderData(CATEGORYID, Qt.Horizontal, "Category") self.assetModel.setHeaderData(ROOM, Qt.Horizontal, "Room") self.assetModel.select() self.assetView = QTableView() self.assetView.setModel(self.assetModel) self.assetView.setItemDelegate(AssetDelegate(self)) self.assetView.setSelectionMode(QTableView.SingleSelection) self.assetView.setSelectionBehavior(QTableView.SelectRows) self.assetView.setColumnHidden(ID, True) self.assetView.resizeColumnsToContents() assetLabel = QLabel("A&ssets") assetLabel.setBuddy(self.assetView) self.logModel = QSqlRelationalTableModel(self) self.logModel.setTable("logs") self.logModel.setRelation(ACTIONID, QSqlRelation("actions", "id", "name")) self.logModel.setSort(DATE, Qt.AscendingOrder) self.logModel.setHeaderData(DATE, Qt.Horizontal, "Date") self.logModel.setHeaderData(ACTIONID, Qt.Horizontal, "Action") self.logModel.select() self.logView = QTableView() self.logView.setModel(self.logModel) self.logView.setItemDelegate(LogDelegate(self)) self.logView.setSelectionMode(QTableView.SingleSelection) self.logView.setSelectionBehavior(QTableView.SelectRows) self.logView.setColumnHidden(ID, True) self.logView.setColumnHidden(ASSETID, True) self.logView.resizeColumnsToContents() self.logView.horizontalHeader().setStretchLastSection(True) logLabel = QLabel("&Logs") logLabel.setBuddy(self.logView) addAssetButton = QPushButton("&Add Asset") deleteAssetButton = QPushButton("&Delete Asset") addActionButton = QPushButton("Add A&ction") deleteActionButton = QPushButton("Delete Ac&tion") editActionsButton = QPushButton("&Edit Actions...") editCategoriesButton = QPushButton("Ed&it Categories...") quitButton = QPushButton("&Quit") for button in (addAssetButton, deleteAssetButton, addActionButton, deleteActionButton, editActionsButton, editCategoriesButton, quitButton): if MAC: button.setDefault(False) button.setAutoDefault(False) else: button.setFocusPolicy(Qt.NoFocus) dataLayout = QVBoxLayout() dataLayout.addWidget(assetLabel) dataLayout.addWidget(self.assetView, 1) dataLayout.addWidget(logLabel) dataLayout.addWidget(self.logView) buttonLayout = QVBoxLayout() buttonLayout.addWidget(addAssetButton) buttonLayout.addWidget(deleteAssetButton) buttonLayout.addWidget(addActionButton) buttonLayout.addWidget(deleteActionButton) buttonLayout.addWidget(editActionsButton) buttonLayout.addWidget(editCategoriesButton) buttonLayout.addStretch() buttonLayout.addWidget(quitButton) layout = QHBoxLayout() layout.addLayout(dataLayout, 1) layout.addLayout(buttonLayout) self.setLayout(layout) #self.connect(self.assetView.selectionModel(), #SIGNAL(("currentRowChanged(QModelIndex,QModelIndex)")), #self.assetChanged) self.assetView.selectionModel().currentRowChanged.connect( self.assetChanged) addAssetButton.clicked.connect(self.addAsset) deleteAssetButton.clicked.connect(self.deleteAsset) addActionButton.clicked.connect(self.addAction) deleteActionButton.clicked.connect(self.deleteAction) editActionsButton.clicked.connect(self.editActions) editCategoriesButton.clicked.connect(self.editCategories) quitButton.clicked.connect(self.done) self.assetChanged(self.assetView.currentIndex()) self.setMinimumWidth(650) self.setWindowTitle("Asset Manager") def done(self, result=1): query = QSqlQuery() query.exec_("DELETE FROM logs WHERE logs.assetid NOT IN" "(SELECT id FROM assets)") QDialog.done(self, 1) def assetChanged(self, index): if index.isValid(): record = self.assetModel.record(index.row()) #print(index.row()) id = record.value("id") self.logModel.setFilter("assetid = {0}".format(id)) else: self.logModel.setFilter("assetid = -1") #self.logModel.reset() # workaround for Qt <= 4.3.3/SQLite bug #self.logModel.beginResetModel() self.logModel.select() self.logView.horizontalHeader().setVisible( self.logModel.rowCount() > 0) if PYQT_VERSION_STR < "4.1.0": self.logView.setColumnHidden(ID, True) self.logView.setColumnHidden(ASSETID, True) #self.logModel.endResetModel() def addAsset(self): row = (self.assetView.currentIndex().row() if self.assetView.currentIndex().isValid() else 0) QSqlDatabase.database().transaction() self.assetModel.insertRow(row) index = self.assetModel.index(row, NAME) self.assetView.setCurrentIndex(index) assetid = 1 query = QSqlQuery() query.exec_("SELECT MAX(id) FROM assets") if query.next(): assetid = query.value(0) query.prepare("INSERT INTO logs (assetid, date, actionid) " "VALUES (:assetid, :date, :actionid)") query.bindValue(":assetid", assetid + 1) query.bindValue(":date", QDate.currentDate()) query.bindValue(":actionid", ACQUIRED) query.exec_() QSqlDatabase.database().commit() #self.logModel.select() self.assetView.edit(index) def deleteAsset(self): index = self.assetView.currentIndex() if not index.isValid(): return QSqlDatabase.database().transaction() record = self.assetModel.record(index.row()) assetid = record.value(ID) logrecords = 1 query = QSqlQuery( "SELECT COUNT(*) FROM logs WHERE assetid = {0}".format(assetid)) if query.next(): logrecords = query.value(0) msg = ("<font color=red>Delete</font><br><b>{0}</b>" "<br>from room {1}").format(record.value(NAME), record.value(ROOM)) if logrecords > 1: msg += (", along with {0} log records".format(logrecords)) msg += "?" if (QMessageBox.question(self, "Delete Asset", msg, QMessageBox.Yes | QMessageBox.No) == QMessageBox.No): QSqlDatabase.database().rollback() return #query.exec_("DELETE FROM logs WHERE assetid = {0}" # .format(assetid)) #use model API self.logModel.setFilter("assetid={0}".format(assetid)) self.logModel.select() if self.logModel.rowCount() > 0: self.logModel.removeRows(0, self.logModel.rowCount()) self.logModel.submitAll() self.assetModel.removeRow(index.row()) self.assetModel.submitAll() QSqlDatabase.database().commit() self.assetModel.select() self.assetChanged(self.assetView.currentIndex()) def addAction(self): index = self.assetView.currentIndex() if not index.isValid(): return QSqlDatabase.database().transaction() record = self.assetModel.record(index.row()) assetid = record.value(ID) row = self.logModel.rowCount() self.logModel.insertRow(row) self.logModel.setData(self.logModel.index(row, ASSETID), assetid) self.logModel.setData(self.logModel.index(row, DATE), QDate.currentDate()) QSqlDatabase.database().commit() index = self.logModel.index(row, ACTIONID) self.logView.setCurrentIndex(index) self.logView.edit(index) def deleteAction(self): index = self.logView.currentIndex() if not index.isValid(): return record = self.logModel.record(index.row()) action = record.value(ACTIONID) if action == "Acquired": QMessageBox.information( self, "Delete Log", "The 'Acquired' log record cannot be deleted.<br>" "You could delete the entire asset instead.") return when = str(record.value(DATE)) if (QMessageBox.question(self, "Delete Log", "Delete log<br>{0} {1}?".format(when, action), QMessageBox.Yes | QMessageBox.No) == QMessageBox.No): return self.logModel.removeRow(index.row()) self.logModel.submitAll() self.logModel.select() def editActions(self): form = ReferenceDataDlg("actions", "Action", self) form.exec_() def editCategories(self): form = ReferenceDataDlg("categories", "Category", self) form.exec_()
class ReferenceDataDlg(QDialog): def __init__(self, table, title, parent=None): super(ReferenceDataDlg, self).__init__(parent) self.model = QSqlTableModel(self) self.model.setTable(table) self.model.setSort(NAME, Qt.AscendingOrder) self.model.setHeaderData(ID, Qt.Horizontal, "ID") self.model.setHeaderData(NAME, Qt.Horizontal, "Name") self.model.setHeaderData(DESCRIPTION, Qt.Horizontal, "Description") self.model.select() self.view = QTableView() self.view.setModel(self.model) self.view.setSelectionMode(QTableView.SingleSelection) self.view.setSelectionBehavior(QTableView.SelectRows) self.view.setColumnHidden(ID, True) self.view.resizeColumnsToContents() addButton = QPushButton("&Add") deleteButton = QPushButton("&Delete") okButton = QPushButton("&OK") if not MAC: addButton.setFocusPolicy(Qt.NoFocus) deleteButton.setFocusPolicy(Qt.NoFocus) buttonLayout = QHBoxLayout() buttonLayout.addWidget(addButton) buttonLayout.addWidget(deleteButton) buttonLayout.addStretch() buttonLayout.addWidget(okButton) layout = QVBoxLayout() layout.addWidget(self.view) layout.addLayout(buttonLayout) self.setLayout(layout) addButton.clicked.connect(self.addRecord) deleteButton.clicked.connect(self.deleteRecord) okButton.clicked.connect(self.accept) self.setWindowTitle( "Asset Manager - Edit {0} Reference Data".format(title)) def addRecord(self): row = self.model.rowCount() self.model.insertRow(row) index = self.model.index(row, NAME) self.view.setCurrentIndex(index) self.view.edit(index) def deleteRecord(self): index = self.view.currentIndex() if not index.isValid(): return #QSqlDatabase.database().transaction() record = self.model.record(index.row()) id = record.value(ID) table = self.model.tableName() query = QSqlQuery() if table == "actions": query.exec_("SELECT COUNT(*) FROM logs " "WHERE actionid = {0}".format(id)) elif table == "categories": query.exec_("SELECT COUNT(*) FROM assets " "WHERE categoryid = {0}".format(id)) count = 0 if query.next(): count = query.value(0) if count: QMessageBox.information( self, "Delete {0}".format(table), ("Cannot delete {0}<br>" "from the {1} table because it is used by " "{2} records").format(record.value(NAME), table, count)) #QSqlDatabase.database().rollback() return self.model.removeRow(index.row()) self.model.submitAll() self.model.select()
class SubtitleEditor(SubTab): def __init__(self, filePath, subtitleData, parent = None): name = os.path.split(filePath)[1] super(SubtitleEditor, self).__init__(name, parent) self.__initWidgets() self.__initContextMenu() self._settings = SubSettings() self._filePath = filePath self._movieFilePath = None self._subtitleData = subtitleData self.refreshSubtitles() # Some signals self._subtitleData.fileChanged.connect(self.fileChanged) self._subtitleData.subtitlesAdded.connect(self._subtitlesAdded) self._subtitleData.subtitlesRemoved.connect(self._subtitlesRemoved) self._subtitleData.subtitlesChanged.connect(self._subtitlesChanged) self._model.itemChanged.connect(self._subtitleEdited) self.customContextMenuRequested.connect(self.showContextMenu) def __initWidgets(self): minimalSizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) # List of subtitles subListDelegate = SubListItemDelegate() self._model = QStandardItemModel(0, 3, self) self._model.setHorizontalHeaderLabels([_("Begin"), _("End"), _("Subtitle")]) self._subList = QTableView(self) self._subList.setModel(self._model) self._subList.setItemDelegateForColumn(0, subListDelegate) self._subList.setItemDelegateForColumn(1, subListDelegate) self._subList.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self._searchBar = SearchBar(self) self._searchBar.hide() # Top toolbar toolbar = QHBoxLayout() toolbar.setAlignment(Qt.AlignLeft) #toolbar.addWidget(someWidget....) toolbar.addStretch(1) # Main layout grid = QGridLayout() grid.setSpacing(10) grid.setContentsMargins(0, 3, 0, 0) grid.addLayout(toolbar, 0, 0, 1, 1) # stretch to the right grid.addWidget(self._subList, 1, 0) grid.addWidget(self._searchBar, 2, 0) self.setLayout(grid) def __initContextMenu(self): self._contextMenu = QMenu(self) self.setContextMenuPolicy(Qt.CustomContextMenu) af = ActionFactory(self) insertSub = af.create(title = _("&Insert subtitle"), icon = "list-add", connection = self.insertNewSubtitle) self._contextMenu.addAction(insertSub) insertSub = af.create(title = _("&Add subtitle"), icon = "list-add", connection = self.addNewSubtitle) self._contextMenu.addAction(insertSub) removeSub = af.create(title = _("&Remove subtitles"), icon = "list-remove", connection = self.removeSelectedSubtitles) self._contextMenu.addAction(removeSub) def _changeRowBackground(self, rowNo, bg): with DisableSignalling(self._model.itemChanged, self._subtitleEdited): for columnNo in range(self._model.columnCount()): item = self._model.item(rowNo, columnNo) item.setBackground(bg) def _changeItemData(self, item, val, role): with DisableSignalling(self._model.itemChanged, self._subtitleEdited): item.setData(val, role) def _handleIncorrectItem(self, item): with DisableSignalling(self._model.itemChanged, self._subtitleEdited): item.setData(False, CustomDataRoles.ErrorFlagRole) bg = item.background() rowNo = item.row() self._changeRowBackground(rowNo, Qt.red) QTimer.singleShot(600, lambda rowNo=rowNo, bg=bg: self._changeRowBackground(rowNo, bg)) def _subtitleEdited(self, item): modelIndex = item.index() column = modelIndex.column() subNo = modelIndex.row() errorFlag = item.data(CustomDataRoles.ErrorFlagRole) if errorFlag is True: self._handleIncorrectItem(item) else: # TODO: timeStart and timeEnd might be displayed in a frame format in a bright future. # Check it and create FrameTime properly in that case. # TODO: Maybe add column numbers to some kind of enum to avoid magic numbers? oldSubtitle = self.subtitles[subNo] newSubtitle = oldSubtitle.clone() if 0 == column: timeStart = item.data(CustomDataRoles.FrameTimeRole) newSubtitle.change(start = timeStart) elif 1 == column: timeEnd = item.data(CustomDataRoles.FrameTimeRole) newSubtitle.change(end = timeEnd) elif 2 == column: newSubtitle.change(text = item.text()) command = ChangeSubtitle(self.filePath, oldSubtitle, newSubtitle, subNo) self._subtitleData.execute(command) def _subtitlesAdded(self, path, subNos): if path != self.filePath: return subtitles = self.subtitles for subNo in subNos: row = createRow(subtitles[subNo]) self._model.insertRow(subNo, row) def _subtitlesRemoved(self, path, subNos): if path != self.filePath: return for subNo in subNos: self._model.removeRow(subNo) def _subtitlesChanged(self, path, subNos): if path != self.filePath: return for subNo in subNos: self.refreshSubtitle(subNo) def _createNewSubtitle(self, data, subNo): fps = data.fps # data is passed to avoid unnecessary copies minFrameTime = FrameTime(fps, frames = 1) # calculate correct minimum subtitle start time if subNo > 0: timeStart = data.subtitles[subNo - 1].end + minFrameTime else: timeStart = FrameTime(fps, frames = 0) # calculate correct maximum subtitle end time if subNo < data.subtitles.size(): try: timeEnd = data.subtitles[subNo].start - minFrameTime except SubException: timeEnd = FrameTime(fps, frames = 0) else: timeEnd = timeStart + FrameTime(fps, frames = 50) # add subtitle to DataModel sub = Subtitle(timeStart, timeEnd, "") command = AddSubtitle(self.filePath, subNo, sub) with DisableSignalling(self._subtitleData.subtitlesAdded, self._subtitlesAdded): self._subtitleData.execute(command) # create subtitle graphical representation in editor sub list row = createRow(sub) self._model.insertRow(subNo, row) index = self._model.index(subNo, 2) self._subList.clearSelection() self._subList.setCurrentIndex(index) self._subList.edit(index) def addNewSubtitle(self): data = self.data subNo = data.subtitles.size() indices = self._subList.selectedIndexes() if len(indices) > 0: rows = [index.row() for index in indices] subNo = max(rows) + 1 self._createNewSubtitle(data, subNo) def insertNewSubtitle(self): data = self.data subNo = 0 indices = self._subList.selectedIndexes() if len(indices) > 0: rows = [index.row() for index in indices] subNo = max(rows) self._createNewSubtitle(data, subNo) def removeSelectedSubtitles(self): indices = self._subList.selectedIndexes() if len(indices) > 0: rows = list(set([index.row() for index in indices])) command = RemoveSubtitles(self.filePath, rows) self._subtitleData.execute(command) if self._model.rowCount() > rows[-1]: self._subList.selectRow(rows[-1]) else: self._subList.selectRow(self._model.rowCount() - 1) def highlight(self): self._searchBar.show() self._searchBar.highlight() def showContextMenu(self): self._contextMenu.exec(QCursor.pos()) def changeInputEncoding(self, encoding): data = self._subtitleData.data(self.filePath) if encoding != data.inputEncoding: try: data.encode(encoding) except UnicodeDecodeError: message = QMessageBox( QMessageBox.Warning, _("Decoding error"), _("Cannot decode subtitles to '%s' encoding.\nPlease try different encoding.") % encoding, QMessageBox.Ok, self ) message.exec() except LookupError: message = QMessageBox(QMessageBox.Warning, _("Unknown encoding"), _("Unknown encoding: '%s'") % encoding, QMessageBox.Ok, self ) message.exec() else: # TODO: outputEncoding command = ChangeData(self.filePath, data, _("Input encoding: %s") % encoding) self._subtitleData.execute(command) def changeOutputEncoding(self, encoding): data = self._subtitleData.data(self.filePath) if encoding != data.outputEncoding: data.outputEncoding = encoding command = ChangeData(self.filePath, data, _("Output encoding: %s") % encoding) self._subtitleData.execute(command) def changeSubFormat(self, fmt): data = self._subtitleData.data(self.filePath) if data.outputFormat != fmt: data.outputFormat = fmt command = ChangeData(self.filePath, data) self._subtitleData.execute(command) def changeFps(self, fps): data = self.data if data.fps != fps: data.subtitles.changeFps(fps) data.fps = fps command = ChangeData(self.filePath, data, _("FPS: %s") % fps) self._subtitleData.execute(command) def changeVideoPath(self, path): data = self.data if data.videoPath != path: data.videoPath = path command = ChangeData(self.filePath, data, _("Video path: %s") % path) self._subtitleData.execute(command) def offset(self, seconds): data = self.data fps = data.subtitles.fps if fps is None: log.error(_("No FPS for '%s' (empty subtitles)." % self.filePath)) return ft = FrameTime(data.subtitles.fps, seconds=seconds) data.subtitles.offset(ft) command = ChangeData(self.filePath, data, _("Offset by: %s") % ft.toStr()) self._subtitleData.execute(command) def detectFps(self): data = self.data if data.videoPath is not None: fpsInfo = File.detectFpsFromMovie(data.videoPath) if data.videoPath != fpsInfo.videoPath or data.fps != fpsInfo.fps: data.videoPath = fpsInfo.videoPath data.subtitles.changeFps(fpsInfo.fps) data.fps = fpsInfo.fps command = ChangeData(self.filePath, data, _("Detected FPS: %s") % data.fps) self._subtitleData.execute(command) def fileChanged(self, filePath): if filePath == self._filePath: self.refreshSubtitles() def refreshSubtitle(self, subNo): sub = self.subtitles[subNo] self._model.removeRow(subNo) self._model.insertRow(subNo, createRow(sub)) def refreshSubtitles(self): self._model.removeRows(0, self._model.rowCount()) for sub in self.subtitles: self._model.appendRow(createRow(sub)) def updateTab(self): self.refreshSubtitles() def selectedSubtitles(self): rows = self.selectedRows() subtitleList = [self.subtitles[row] for row in rows] return subtitleList def selectedRows(self): indices = self._subList.selectedIndexes() # unique list rows = list(set([index.row() for index in indices])) rows.sort() return rows def selectRow(self, row): self._subList.selectRow(row) @property def filePath(self): return self._filePath @property def movieFilePath(self): return self._movieFilePath @property def data(self): return self._subtitleData.data(self.filePath) @property def subtitles(self): return self.data.subtitles @property def history(self): return self._subtitleData.history(self.filePath) @property def inputEncoding(self): return self.data.inputEncoding @property def outputEncoding(self): return self.data.outputEncoding @property def outputFormat(self): return self.data.outputFormat
class ReferenceDataDlg(QDialog): def __init__(self, parent=None): super(ReferenceDataDlg, self).__init__(parent) self.model = QSqlTableModel(self) self.model.setTable("reference") self.model.setSort(ID, Qt.AscendingOrder) self.model.setHeaderData(ID, Qt.Horizontal, "ID") self.model.setHeaderData(NAME, Qt.Horizontal, "NAME") self.model.setHeaderData(Description, Qt.Horizontal, "Description") self.model.setHeaderData(AGE, Qt.Horizontal, "AGE") self.model.select() self.view = QTableView() self.view.setModel(self.model) self.view.setSelectionMode(QTableView.SingleSelection) self.view.setSelectionBehavior(QTableView.SelectRows) self.view.setColumnHidden(ID, True) self.view.resizeColumnsToContents() buttonBox = QDialogButtonBox() addButton = buttonBox.addButton("&Add", QDialogButtonBox.ActionRole) deleteButton = buttonBox.addButton("&Delete", QDialogButtonBox.ActionRole) sortButton = buttonBox.addButton("&Sort", QDialogButtonBox.ActionRole) if not MAC: addButton.setFocusPolicy(Qt.NoFocus) deleteButton.setFocusPolicy(Qt.NoFocus) sortButton.setFocusPolicy(Qt.NoFocus) menu = QMenu(self) sortByNAMEAction = menu.addAction("Sort by &NAME") sortByDescriptionAction = menu.addAction("Sort by &Description") sortByIDAction = menu.addAction("Sort by &ID") sortButton.setMenu(menu) closeButton = buttonBox.addButton(QDialogButtonBox.Close) layout = QVBoxLayout() layout.addWidget(self.view) layout.addWidget(buttonBox) self.setLayout(layout) addButton.clicked.connect(self.addRecord) deleteButton.clicked.connect(self.deleteRecord) sortByNAMEAction.triggered.connect(lambda: self.sort(NAME)) sortByDescriptionAction.triggered.connect( lambda: self.sort(Description)) sortByIDAction.triggered.connect(lambda: self.sort(ID)) closeButton.clicked.connect(self.accept) self.setWindowTitle("数据库小程序") def addRecord(self): row = self.model.rowCount() self.model.insertRow(row) index = self.model.index(row, NAME) self.view.setCurrentIndex(index) self.view.edit(index) def deleteRecord(self): index = self.view.currentIndex() if not index.isValid(): return record = self.model.record(index.row()) NAME = record.value(NAME) desc = record.value(Description) if (QMessageBox.question( self, "数据库小程序", ("Delete {0} from NAME {1}?".format(desc, NAME)), QMessageBox.Yes | QMessageBox.No) == QMessageBox.No): return self.model.removeRow(index.row()) self.model.submitAll() self.model.select() def sort(self, column): self.model.setSort(column, Qt.AscendingOrder) self.model.select()
class ApiDatabaseDialog(QDialog): def __init__(self): super().__init__() self.setMinimumSize(QSize(800, 400)) config_folder = os.path.join(os.path.expanduser("~"), '.config', 'CSV_Viewer') os.makedirs(config_folder, exist_ok=True) db_file = "apilinks.sqlite" db_path = os.path.join(config_folder, db_file) self.db = QSqlDatabase("QSQLITE") self.db.setDatabaseName(db_path) if self.db.open(): if os.path.getsize(db_path) == 0: query = QSqlQuery(db=self.db) query.exec_("CREATE TABLE links(address TEXT)") demo = "http://climatedataapi.worldbank.org/climateweb/rest/v1/country/cru/tas/year/POL.csv" query.exec_(f"INSERT INTO links VALUES ('{demo}')") self.db.commit() else: QMessageBox.warning(self, "Error", "API links database not open.") self.table = QTableView() self.model = QSqlTableModel(db=self.db) self.model.setTable('links') self.model.setEditStrategy(QSqlTableModel.OnFieldChange) self.model.select() # set headers column_titles = {"address": "API Address"} for n, t in column_titles.items(): idx = self.model.fieldIndex(n) self.model.setHeaderData(idx, Qt.Horizontal, t) self.table.setModel(self.model) self.table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) self.table.setSelectionMode(QtWidgets.QTableView.SingleSelection) self.table.setColumnWidth(0, 750) self.table.selectRow(0) self.table.setFocus() self.layout = QVBoxLayout() QBtn = QDialogButtonBox.Ok self.buttonBox = QDialogButtonBox(QBtn) style_add = self.buttonBox.style() icon = style_add.standardIcon(QStyle.SP_DialogYesButton) self.button_add = QPushButton(icon, "&Add") self.button_add.setStatusTip("Add new api link") self.button_add.clicked.connect(self.add_link) style_del = self.buttonBox.style() icon = style_del.standardIcon(QStyle.SP_DialogCloseButton) self.button_del = QPushButton(icon, "&Delete") self.button_del.setStatusTip("Delete api link") self.button_del.clicked.connect(self.del_link) self.buttonBox.accepted.connect(self.accept) self.layout.addWidget(self.table) layout_btn = QHBoxLayout() layout_btn.addWidget(self.button_add) layout_btn.addWidget(self.button_del) layout_btn.addSpacerItem(QSpacerItem(150, 10, QSizePolicy.Expanding)) layout_btn.addWidget(self.buttonBox) self.layout.addLayout(layout_btn) self.setLayout(self.layout) def closeEvent(self, event) -> None: """ Quit dialog """ self.db.close() def add_link(self): self.model.insertRows(self.model.rowCount(), 1) self.table.setFocus() self.table.selectRow(self.model.rowCount() - 1) index = self.table.currentIndex() self.table.edit(index) def del_link(self): if self.model.rowCount() > 0: index = self.table.currentIndex() self.model.removeRow(index.row()) self.model.submitAll() self.table.setRowHidden(index.row(), True) if index.row() == 0: current = 0 else: current = index.row() - 1 self.table.selectRow(current)