class MainWindow(QMainWindow): def __init__(self, dbService, lang): super().__init__() self.lang = lang self.dbService = dbService self.appSize = [1000, 1000] self.initUI() def initUI(self): self.tabs = QTabWidget(self) self.tabs.resize(self.appSize[0], self.appSize[1]) self.colorPanel = ColorPanel(self.dbService, self.lang) self.brushPanel = BrushPanel(self.dbService, self.lang) self.tabs.addTab(self.colorPanel, Strings.str_LABEL_COLORS.get(self.lang)) self.tabs.addTab(self.brushPanel, Strings.str_LABEL_BRUSHES.get(self.lang)) self.setWindowTitle("Tabletop Manager") self.move(100, 100) #self.resize(self.appSize[0], self.appSize[1]) self.setFixedSize(self.appSize[0], self.appSize[1]) self.show()
class Tabs(QWidget): def __init__(self, parent): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) self.console = Console("Superconsole") self.tabs = QTabWidget() self.ddosTab = DosTab(self.console) self.portscannerTab = PortScannerTab(self.console) # self.hashcrackerTab = HashcrackerTab(self.console) self.hashGeneratorTab = HashGeneratorTab(self.console) self.rtGenTab = RainbowGeneratorTab(self.console) self.stressTab = CPUStressTab(self.console) # self.tabs.resize(800, 600) self.tabs.addTab(self.ddosTab, "Denial of Service") self.tabs.addTab(self.portscannerTab, "Portscanner") self.tabs.addTab(self.hashGeneratorTab, "HashGenrator") # self.tabs.addTab(self.hashcrackerTab, "Hashcracker (Not Working)") self.tabs.addTab(self.rtGenTab, "Rainbowtable Generator") self.tabs.addTab(self.stressTab, "CPU Stress") self.layout.addWidget(self.tabs) self.layout.addStretch(1) self.layout.addWidget(self.console)
class Dialog(QtWidgets.QDialog): _layout = None buttonBox = None okButtonText = None cancelButtonText = None okButton = None cancelButton = None _tab = None def __init__(self, title=None, f=None, desc=None): # FIXME: f no lo uso , es qt.windowsflg super(Dialog, self).__init__() if title: self.setWindowTitle(str(title)) self.setWindowModality(QtCore.Qt.ApplicationModal) self._layout = QtWidgets.QVBoxLayout() self.setLayout(self._layout) self.buttonBox = QtWidgets.QDialogButtonBox() self.okButton = QtWidgets.QPushButton("&Aceptar") self.cancelButton = QtWidgets.QPushButton("&Cancelar") self.buttonBox.addButton( self.okButton, QtWidgets.QDialogButtonBox.AcceptRole) self.buttonBox.addButton( self.cancelButton, QtWidgets.QDialogButtonBox.RejectRole) self.okButton.clicked.connect(self.accept) self.cancelButton.clicked.connect(self.reject) from PyQt5.Qt import QTabWidget self._tab = QTabWidget() self._tab.hide() self._layout.addWidget(self._tab) self.oKButtonText = None self.cancelButtonText = None def add(self, _object): self._layout.addWidget(_object) def exec_(self): if self.okButtonText: self.okButton.setText(str(self.okButtonText)) if (self.cancelButtonText): self.cancelButton.setText(str(self.cancelButtonText)) self._layout.addWidget(self.buttonBox) return super(Dialog, self).exec_() def newTab(self, name): if self._tab.isHidden(): self._tab.show() self._tab.addTab(QtWidgets.QWidget(), str(name)) def __getattr__(self, name): if name == "caption": name = self.setWindowTitle return getattr(super(Dialog, self), name)
class Dialog(QtWidgets.QDialog): _layout = None buttonBox = None okButtonText = None cancelButtonText = None okButton = None cancelButton = None _tab = None def __init__(self, title=None, f=None, desc=None): # FIXME: f no lo uso , es qt.windowsflg super(Dialog, self).__init__() if title: self.setWindowTitle(str(title)) self.setWindowModality(QtCore.Qt.ApplicationModal) self._layout = QtWidgets.QVBoxLayout() self.setLayout(self._layout) self.buttonBox = QtWidgets.QDialogButtonBox() self.okButton = QtWidgets.QPushButton("&Aceptar") self.cancelButton = QtWidgets.QPushButton("&Cancelar") self.buttonBox.addButton(self.okButton, QtWidgets.QDialogButtonBox.AcceptRole) self.buttonBox.addButton(self.cancelButton, QtWidgets.QDialogButtonBox.RejectRole) self.okButton.clicked.connect(self.accept) self.cancelButton.clicked.connect(self.reject) self._tab = QTabWidget() self._tab.hide() self._layout.addWidget(self._tab) self.oKButtonText = None self.cancelButtonText = None def add(self, _object): self._layout.addWidget(_object) def exec_(self): if self.okButtonText: self.okButton.setText(str(self.okButtonText)) if (self.cancelButtonText): self.cancelButton.setText(str(self.cancelButtonText)) self._layout.addWidget(self.buttonBox) return super(Dialog, self).exec_() def newTab(self, name): if self._tab.isHidden(): self._tab.show() self._tab.addTab(QtWidgets.QWidget(), str(name)) def __getattr__(self, name): if name == "caption": name = self.setWindowTitle return getattr(super(Dialog, self), name)
def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action self.l = QVBoxLayout() self.setLayout(self.l) tab_widget = QTabWidget(self) self.l.addWidget(tab_widget) self.basic_tab = BasicTab(self, plugin_action) tab_widget.addTab(self.basic_tab, _('Basic'))
def init_results_widget(self): results_widget = QTabWidget(self) results_widget.setTabsClosable(False) table_view = QTableView(self) table_view.setModel(self.model) table_view.sizePolicy().setVerticalPolicy( QSizePolicy.MinimumExpanding) results_widget.addTab(table_view, 'Data') log = QTextEdit(self) log.setReadOnly(True) self.model.executed.connect(log.append) results_widget.addTab(log, 'Events') return results_widget
def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action self.l = QVBoxLayout() self.setLayout(self.l) tab_widget = QTabWidget(self) self.l.addWidget(tab_widget) self.basic_tab = BasicTab(self, plugin_action) tab_widget.addTab(self.basic_tab, _('Basic')) self.columns_tab = ColumnsTab(self, plugin_action) tab_widget.addTab(self.columns_tab, _('Columns'))
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setMinimumWidth(640) self.setMinimumHeight(480) # Set up QTabWidget as a central widget self.tab_widget = QTabWidget(self) self.tab_widget.setTabsClosable(True) self.tab_widget.tabCloseRequested.connect(self.on_tab_close_clicked) self.setCentralWidget(self.tab_widget) # Create "Connection" menu menu_bar = self.menuBar() connection_menu = menu_bar.addMenu('Connection') # Add "Create" connection button create_connection_action = QAction('Create', self) create_connection_action.triggered.connect(self.add_new_tab) connection_menu.addAction(create_connection_action) # Add "Close" connection button close_connection_action = QAction('Close', self) close_connection_action.triggered.connect(self.close_current_tab) connection_menu.addAction(close_connection_action) # self.tool_bar = self.addToolBar('test bar') # self.connect_action = self.tool_bar.addAction('connect') self.add_new_tab() def add_new_tab(self): connection_widget = ConnectionWidget(self.tab_widget) connection_widget.title_changed.connect(self.on_tab_name_changed) self.tab_widget.addTab(connection_widget, 'Untitled') def close_current_tab(self): idx = self.tab_widget.currentIndex() self.tab_widget.removeTab(idx) def on_tab_close_clicked(self, idx): self.tab_widget.removeTab(idx) def on_tab_name_changed(self, widget, name): idx = self.tab_widget.indexOf(widget) if idx != -1: self.tab_widget.setTabText(idx, name)
class _ControlTab(QWidget): """Tab which allows the user to control individual bots. Handles any events fired by the table etc. """ def __init__(self, model): super().__init__() self._model = model layout = QGridLayout() splitter = QSplitter() self._bot_table = _BotTable() self._responses_tab = _ResponsesTab() self._execute_tab = _ExecuteTab(self._responses_tab, model) self._tab_widget = QTabWidget() self._tab_widget.addTab(self._execute_tab, "Execute") self._tab_widget.addTab(self._responses_tab, "Responses") splitter.setOrientation(Qt.Vertical) splitter.addWidget(self._bot_table) splitter.addWidget(self._tab_widget) splitter.setSizes([50, 100]) layout.addWidget(splitter) self.setLayout(layout) self._register_listeners() def _register_listeners(self): self._bot_table.set_on_selection_changed( self.on_table_selection_changed) def on_table_selection_changed(self): bot_uid = self._bot_table.item(self._bot_table.currentRow(), 0).text() self._execute_tab.set_current_bot(self._model.get_bot(bot_uid)) self._execute_tab.set_module_layout() self._responses_tab.clear() def get_table(self) -> _BotTable: return self._bot_table def get_responses_tab(self) -> _ResponsesTab: return self._responses_tab
def build_results_widget(self): # Initialize QTabWidget to display table view and log # in differnt unclosable tabs results_widget = QTabWidget(self) results_widget.setTabsClosable(False) # Add table view table_view = QTableView(self) table_view.setModel(self.model) table_view.sizePolicy().setVerticalPolicy(QSizePolicy.MinimumExpanding) results_widget.addTab(table_view, UI.QUERY_RESULTS_DATA_TAB_TEXT) # Att log view log = QTextEdit(self) log.setReadOnly(True) self.model.executed.connect(log.append) results_widget.addTab(log, UI.QUERY_RESULTS_EVENTS_TAB_TEXT) return results_widget
def do_config(self): # Save values that need to be synced between the dialog and the # search widget. self.config['open_external'] = self.open_external.isChecked() # Create the config dialog. It's going to put two config widgets # into a QTabWidget for displaying all of the settings. d = QDialog(self) button_box = QDialogButtonBox(QDialogButtonBox.Close) v = QVBoxLayout(d) button_box.accepted.connect(d.accept) button_box.rejected.connect(d.reject) d.setWindowTitle(_('Customize Get books search')) tab_widget = QTabWidget(d) v.addWidget(tab_widget) v.addWidget(button_box) chooser_config_widget = StoreChooserWidget() search_config_widget = StoreConfigWidget(self.config) tab_widget.addTab(chooser_config_widget, _('Choose s&tores')) tab_widget.addTab(search_config_widget, _('Configure s&earch')) # Restore dialog state. geometry = self.config.get('config_dialog_geometry', None) if geometry: d.restoreGeometry(geometry) else: d.resize(800, 600) tab_index = self.config.get('config_dialog_tab_index', 0) tab_index = min(tab_index, tab_widget.count() - 1) tab_widget.setCurrentIndex(tab_index) d.exec_() # Save dialog state. self.config['config_dialog_geometry'] = bytearray(d.saveGeometry()) self.config['config_dialog_tab_index'] = tab_widget.currentIndex() search_config_widget.save_settings() self.config_changed() self.gui.load_store_plugins() self.setup_store_checks()
def do_config(self): # Save values that need to be synced between the dialog and the # search widget. self.config['open_external'] = self.open_external.isChecked() # Create the config dialog. It's going to put two config widgets # into a QTabWidget for displaying all of the settings. d = QDialog(self) button_box = QDialogButtonBox(QDialogButtonBox.Close) v = QVBoxLayout(d) button_box.accepted.connect(d.accept) button_box.rejected.connect(d.reject) d.setWindowTitle(_('Customize Get books search')) tab_widget = QTabWidget(d) v.addWidget(tab_widget) v.addWidget(button_box) chooser_config_widget = StoreChooserWidget() search_config_widget = StoreConfigWidget(self.config) tab_widget.addTab(chooser_config_widget, _('Choose s&tores')) tab_widget.addTab(search_config_widget, _('Configure s&earch')) # Restore dialog state. geometry = self.config.get('config_dialog_geometry', None) if geometry: QApplication.instance().safe_restore_geometry(d, geometry) else: d.resize(800, 600) tab_index = self.config.get('config_dialog_tab_index', 0) tab_index = min(tab_index, tab_widget.count() - 1) tab_widget.setCurrentIndex(tab_index) d.exec_() # Save dialog state. self.config['config_dialog_geometry'] = bytearray(d.saveGeometry()) self.config['config_dialog_tab_index'] = tab_widget.currentIndex() search_config_widget.save_settings() self.config_changed() self.gui.load_store_plugins() self.setup_store_checks()
def init_results_widget(self): # Initialize QTabWidget to display table view and log # in differnt unclosable tabs results_widget = QTabWidget(self) results_widget.setTabsClosable(False) # Add table view table_view = QTableView(self) table_view.setModel(self.model) table_view.sizePolicy().setVerticalPolicy( QSizePolicy.MinimumExpanding) results_widget.addTab(table_view, 'Data') # Att log view log = QTextEdit(self) log.setReadOnly(True) self.model.executed.connect(log.append) results_widget.addTab(log, 'Events') return results_widget
def _set_tab(self): tab_widget = QTabWidget() tab_widget.setMaximumHeight(self.main_ui.window.geometry().height() // 2) self.layout.addWidget(tab_widget) toolBox1 = QToolBox() toolBox2 = QToolBox() groupBox1 = QGroupBox() groupBox2 = QGroupBox() toolBox1.addItem(groupBox1, "") toolBox2.addItem(groupBox2, "") tab_widget.addTab(toolBox1, i18n[self.lang]['Angle']) tab_widget.addTab(toolBox2, i18n[self.lang]['Coordinate']) joint_layout = QVBoxLayout(groupBox1) cartesian_layout = QVBoxLayout(groupBox2) self.cartesian_ui = CoordinateUI(self, cartesian_layout) self.axis_ui = AngleUI(self, joint_layout)
class Preferences(QDialog): """docstring for Preferences.""" def __init__(self, parent): super(Preferences, self).__init__(parent) self.layout = QVBoxLayout(self) self.tab = QTabWidget(self) self.themeChooser = ThemeChooser(self.tab) self.tab.addTab(self.themeChooser, 'Theme') self.saveOption = SaveOption(self.tab, self.parent().saveThreading) self.tab.addTab(self.saveOption, 'Save') self.layout.addWidget(self.tab) self.buttonBox = QDialogButtonBox(self) self.buttonBox.addButton('Ok', QDialogButtonBox.AcceptRole) self.buttonBox.addButton('Cancel', QDialogButtonBox.RejectRole) self.buttonBox.addButton( 'Apply', QDialogButtonBox.ApplyRole).clicked.connect(self.commit) self.buttonBox.accepted.connect(self.acceptButtonTriggered) self.buttonBox.rejected.connect(self.rejectButtonTriggered) self.layout.addWidget(self.buttonBox) self.setMinimumWidth(self.tab.widget(0).width()) self.setMinimumHeight(self.tab.widget(0).height()) def acceptButtonTriggered(self): self.accept() def rejectButtonTriggered(self): self.reject() def commit(self): for i in range(0, self.tab.count()): self.tab.widget(i).commit() def rollback(self): for i in range(0, self.tab.count()): self.tab.widget(i).rollback()
def initGui(self): self.setGeometry(300, 300, 800, 600) self.setWindowTitle('Road warrior Upload File Converter') self.center() fMain = QFrame() mainLayout = QGridLayout() fMain.setLayout(mainLayout) self.setCentralWidget(fMain) tabWidget = QTabWidget() mainLayout.addWidget(tabWidget, 0, 0) self.closeBtn = QPushButton('Close Application') self.closeBtn.clicked.connect(self.handleCloseClicked) mainLayout.addWidget(self.closeBtn, 1, 0) tabWidget.addTab(self.initConvertPage(), 'Converter') tabWidget.addTab(self.initResultPage(), 'Converter') tabWidget.addTab(self.initConfigPage(), 'Configuration')
class UFDebugToolUI(object): def __init__(self, window=None): self.window = window if window is not None else QWidget super(UFDebugToolUI, self).__init__() self.lang = 'en' self.set_ui() def set_ui(self): self._set_window() self._set_menubar() self._set_tab() def _set_window(self): self.window.setWindowTitle(self.window.tr('UF-Debug-Tool')) self.window.setMinimumHeight(800) self.window.setMinimumWidth(1080) self.main_layout = QVBoxLayout(self.window) def _set_menubar(self): self.menuBar = QMenuBar() self.main_layout.setMenuBar(self.menuBar) fileMenu = self.menuBar.addMenu('File') self.newFileAction = QAction(self.window.tr('New'), self.window) self.newFileAction.setShortcut('Ctrl+N') self.newFileAction.setStatusTip('New File') fileMenu.addAction(self.newFileAction) self.openFileAction = QAction(self.window.tr('Open'), self.window) self.openFileAction.setShortcut('Ctrl+O') self.openFileAction.setToolTip('Open File') fileMenu.addAction(self.openFileAction) self.saveFileAction = QAction(self.window.tr('Save'), self.window) self.saveFileAction.setShortcut('Ctrl+S') self.saveFileAction.setStatusTip('Save File') fileMenu.addAction(self.saveFileAction) self.closeFileAction = QAction(self.window.tr('Close'), self.window) self.closeFileAction.setShortcut('Ctrl+W') self.closeFileAction.setStatusTip('Close File') fileMenu.addAction(self.closeFileAction) self.newFileAction.triggered.connect(self.new_dialog) self.openFileAction.triggered.connect(self.open_dialog) self.saveFileAction.triggered.connect(self.save_dialog) self.closeFileAction.triggered.connect(self.close_dialog) debugMenu = self.menuBar.addMenu('Debug') self.logAction = QAction(self.window.tr('Log'), self.window) self.logAction.setShortcut('Ctrl+D') self.logAction.setStatusTip('Open-Log') self.logAction.triggered.connect(self.control_log_window) debugMenu.addAction(self.logAction) def control_log_window(self): if self.window.log_window.isHidden(): self.window.log_window.show() self.logAction.setText('Close-Log') else: self.window.log_window.hide() self.logAction.setText('Open-Log') def switch_tab(self, index): pass # if index == 2: # self.menuBar.setHidden(False) # else: # self.menuBar.setHidden(True) def _set_tab(self): self.tab_widget = QTabWidget() # self.tab_widget.currentChanged.connect(self.switch_tab) # tab_widget.setMaximumHeight(self.window.geometry().height() // 2) self.main_layout.addWidget(self.tab_widget) toolbox1 = QToolBox() toolbox2 = QToolBox() toolbox3 = QToolBox() toolbox4 = QToolBox() toolbox5 = QToolBox() groupbox1 = QGroupBox() groupbox2 = QGroupBox() groupbox3 = QGroupBox() groupbox4 = QGroupBox() groupbox5 = QGroupBox() toolbox1.addItem(groupbox1, "") toolbox2.addItem(groupbox2, "") toolbox3.addItem(groupbox3, "") toolbox4.addItem(groupbox4, "") toolbox5.addItem(groupbox5, "") self.tab_widget.addTab(toolbox1, "uArm") self.tab_widget.addTab(toolbox2, "xArm") self.tab_widget.addTab(toolbox3, "OpenMV") self.tab_widget.addTab(toolbox4, "Gcode") self.tab_widget.addTab(toolbox5, "WebView") uarm_layout = QVBoxLayout(groupbox1) xarm_layout = QVBoxLayout(groupbox2) openmv_layout = QHBoxLayout(groupbox3) gcode_layout = QVBoxLayout(groupbox4) webview_layout = QVBoxLayout(groupbox5) self.uarm_ui = UArmUI(self, uarm_layout) self.xarm_ui = XArmUI(self, xarm_layout) self.openmv_ui = OpenMV_UI(self, openmv_layout) self.gcode_ui = GcodeUI(self, gcode_layout) self.webview_ui = WebViewUI(self, webview_layout) self.tab_widget.setCurrentIndex(0) def new_dialog(self): self.openmv_ui.textEdit.setText('') self.openmv_ui.textEdit.filename = None self.openmv_ui.label_title.setText('untitled') self.tab_widget.setCurrentIndex(2) self.openmv_ui.textEdit.setDisabled(False) def open_dialog(self): fname = QFileDialog.getOpenFileName(self.window, 'Open file', '') if fname and fname[0]: with open(fname[0], "r") as f: self.openmv_ui.textEdit.setText(f.read()) self.openmv_ui.label_title.setText(fname[0]) self.openmv_ui.textEdit.filename = fname[0] self.tab_widget.setCurrentIndex(2) self.openmv_ui.textEdit.setDisabled(False) def save_dialog(self): widget = self.window.focusWidget() if widget: if not self.openmv_ui.textEdit.filename: fname = QFileDialog.getSaveFileName(self.window, 'Save File', '') if fname and fname[0]: self.openmv_ui.textEdit.filename = fname[0] if self.openmv_ui.textEdit.filename: data = widget.toPlainText() with open(self.openmv_ui.textEdit.filename, "w") as f: f.write(data) def close_dialog(self): self.openmv_ui.textEdit.clear() self.openmv_ui.textEdit.filename = None self.openmv_ui.label_title.setText('') self.openmv_ui.textEdit.setDisabled(True)
def addTab(self, page, *args): return QTabWidget.addTab(self, self.wrap_widget(page), *args)
class IgnoredFolders(QDialog): def __init__(self, dev, ignored_folders=None, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel('<p>' + _('<b>Scanned folders:</b>') + ' ' + _('You can select which folders calibre will ' 'scan when searching this device for books.')) la.setWordWrap(True) l.addWidget(la) self.tabs = QTabWidget(self) l.addWidget(self.tabs) self.widgets = [] for storage in dev.filesystem_cache.entries: self.dev = dev w = Storage(storage, item_func=self.create_item) del self.dev self.tabs.addTab(w, storage.name) self.widgets.append(w) w.itemChanged.connect(self.item_changed) self.la2 = la = QLabel( _('If you a select a previously unselected folder, any sub-folders' ' will not be visible until you restart calibre.')) l.addWidget(la) la.setWordWrap(True) self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.sab = self.bb.addButton(_('Select &all'), self.bb.ActionRole) self.sab.clicked.connect(self.select_all) self.snb = self.bb.addButton(_('Select &none'), self.bb.ActionRole) self.snb.clicked.connect(self.select_none) l.addWidget(self.bb) self.setWindowTitle(_('Choose folders to scan')) self.setWindowIcon(QIcon(I('devices/tablet.png'))) self.resize(600, 500) def item_changed(self, item, column): w = item.treeWidget() root = w.invisibleRootItem() w.itemChanged.disconnect(self.item_changed) try: if item.checkState(0) == Qt.Checked: # Ensure that the parents of this item are checked p = item.parent() while p is not None and p is not root: p.setCheckState(0, Qt.Checked) p = p.parent() # Set the state of all descendants to the same state as this item for child in self.iterchildren(item): child.setCheckState(0, item.checkState(0)) finally: w.itemChanged.connect(self.item_changed) def iterchildren(self, node): ' Iterate over all descendants of node ' for i in range(node.childCount()): child = node.child(i) yield child for gc in self.iterchildren(child): yield gc def create_item(self, f, parent): name = f.name ans = QTreeWidgetItem(parent, [name]) ans.setData(0, Qt.UserRole, '/'.join(f.full_path[1:])) ans.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) ans.setCheckState( 0, Qt.Unchecked if self.dev.is_folder_ignored( f.storage_id, f.full_path[1:]) else Qt.Checked) ans.setData(0, Qt.DecorationRole, file_icon_provider().icon_from_ext('dir')) return ans def select_all(self): w = self.tabs.currentWidget() for i in range(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Checked) def select_none(self): w = self.tabs.currentWidget() for i in range(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Unchecked) @property def ignored_folders(self): ans = {} for w in self.widgets: folders = set() for node in self.iterchildren(w.invisibleRootItem()): if node.checkState(0) == Qt.Checked: continue path = unicode_type(node.data(0, Qt.UserRole) or '') parent = path.rpartition('/')[0] if '/' not in path or icu_lower(parent) not in folders: folders.add(icu_lower(path)) ans[unicode_type(w.storage.storage_id)] = list(folders) return ans
class MainWindow(QWidget): def __init__(self, parent, masternode_list, imgDir): super(QWidget, self).__init__(parent) self.parent = parent self.imgDir = imgDir self.runInThread = ThreadFuns.runInThread ###-- Masternode list self.masternode_list = masternode_list ###-- Create clients and statuses self.hwdevice = None self.hwStatus = 0 self.hwStatusMess = "Not Connected" self.rpcClient = None self.rpcConnected = False self.rpcStatusMess = "Not Connected" self.isBlockchainSynced = False ###-- Load icons & images self.loadIcons() ###-- Create main layout self.layout = QVBoxLayout() self.header = GuiHeader(self) self.initConsole() self.layout.addWidget(self.header) ###-- Create RPC Whatchdog self.rpc_watchdogThread = QThread() self.myRpcWd = RpcWatchdog(self) self.myRpcWd.moveToThread(self.rpc_watchdogThread) self.rpc_watchdogThread.started.connect(self.myRpcWd.run) ###-- Create Queues and redirect stdout and stderr self.queue = Queue() self.queue2 = Queue() sys.stdout = WriteStream(self.queue) sys.stderr = WriteStream(self.queue2) ###-- Init last logs logFile = open(log_File, 'w+') timestamp = strftime('%Y-%m-%d %H:%M:%S', gmtime(now())) log_line = '<b style="color: blue">{}</b><br>'.format('STARTING QMT at ' + timestamp) logFile.write(log_line) logFile.close() ###-- Create the thread to update console log for stdout self.consoleLogThread = QThread() self.myWSReceiver = WriteStreamReceiver(self.queue) self.myWSReceiver.mysignal.connect(self.append_to_console) self.myWSReceiver.moveToThread(self.consoleLogThread) self.consoleLogThread.started.connect(self.myWSReceiver.run) self.consoleLogThread.start() printDbg("Console Log thread started") ###-- Create the thread to update console log for stderr self.consoleLogThread2 = QThread() self.myWSReceiver2 = WriteStreamReceiver(self.queue2) self.myWSReceiver2.mysignal.connect(self.append_to_console) self.myWSReceiver2.moveToThread(self.consoleLogThread2) self.consoleLogThread2.started.connect(self.myWSReceiver2.run) self.consoleLogThread2.start() printDbg("Console Log thread 2 started") ###-- Initialize tabs self.tabs = QTabWidget() self.t_main = TabMain(self) self.t_mnconf = TabMNConf(self) self.t_rewards = TabRewards(self) self.t_governance = TabGovernance(self) self.t_add_torrent = TabAddTorrent(self) ###-- Add tabs self.tabs.addTab(self.tabGovernance, "Search Torrents") self.tabs.addTab(self.tabAddTorrent, "Add Torrents") # self.tabs.addTab(self.tabMain, "Masternode Control") # self.tabs.addTab(self.tabMNConf, "MN Configuration") # We will put these back later, just with RPC instead of messy key handling which we don't need or want anyway ! # self.tabs.addTab(self.tabRewards, "Transfer Rewards") ###-- Connect change action self.tabs.currentChanged.connect(lambda: self.onTabChange()) ###-- Draw Tabs self.splitter = QSplitter(Qt.Vertical) ###-- Add tabs and console to Layout self.splitter.addWidget(self.tabs) self.splitter.addWidget(self.console) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter.setSizes(self.parent.cache.get("splitter_sizes")) self.layout.addWidget(self.splitter) ###-- Set Layout self.setLayout(self.layout) ###-- Let's go self.mnode_to_change = None printOK("Hello! Welcome to " + parent.title) ###-- Hide console if it was previously hidden if self.parent.cache.get("console_hidden"): self.onToggleConsole() ##-- Check version self.onCheckVersion() ##-- init Api Client self.apiClient = ApiClient() @pyqtSlot(str) def append_to_console(self, text): self.consoleArea.moveCursor(QTextCursor.End) self.consoleArea.insertHtml(text) def initConsole(self): self.console = QGroupBox() self.console.setTitle("Console Log") layout = QVBoxLayout() self.btn_consoleToggle = QPushButton('Hide') self.btn_consoleToggle.setToolTip('Show/Hide console') self.btn_consoleToggle.clicked.connect(lambda: self.onToggleConsole()) consoleHeader = QHBoxLayout() consoleHeader.addWidget(self.btn_consoleToggle) self.consoleSaveButton = QPushButton('Save') self.consoleSaveButton.clicked.connect(lambda: self.onSaveConsole()) consoleHeader.addWidget(self.consoleSaveButton) self.btn_consoleClean = QPushButton('Clean') self.btn_consoleClean.setToolTip('Clean console log area') self.btn_consoleClean.clicked.connect(lambda: self.onCleanConsole()) consoleHeader.addWidget(self.btn_consoleClean) consoleHeader.addStretch(1) self.versionLabel = QLabel("--") self.versionLabel.setOpenExternalLinks(True) consoleHeader.addWidget(self.versionLabel) self.btn_checkVersion = QPushButton("Check QMT version") self.btn_checkVersion.setToolTip("Check latest stable release of QMT") self.btn_checkVersion.clicked.connect(lambda: self.onCheckVersion()) consoleHeader.addWidget(self.btn_checkVersion) layout.addLayout(consoleHeader) self.consoleArea = QTextEdit() almostBlack = QColor(40, 40, 40) palette = QPalette() palette.setColor(QPalette.Base, almostBlack) green = QColor(0, 255, 0) palette.setColor(QPalette.Text, green) self.consoleArea.setPalette(palette) layout.addWidget(self.consoleArea) self.console.setLayout(layout) def isMasternodeInList(self, mn_alias): return (mn_alias in [x['name'] for x in self.masternode_list]) def loadIcons(self): # Load Icons self.ledPurpleH_icon = QPixmap(os.path.join(self.imgDir, 'icon_purpleLedH.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledGrayH_icon = QPixmap(os.path.join(self.imgDir, 'icon_grayLedH.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledHalfPurpleH_icon = QPixmap(os.path.join(self.imgDir, 'icon_halfPurpleLedH.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledRedV_icon = QPixmap(os.path.join(self.imgDir, 'icon_redLedV.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledGrayV_icon = QPixmap(os.path.join(self.imgDir, 'icon_grayLedV.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledGreenV_icon = QPixmap(os.path.join(self.imgDir, 'icon_greenLedV.png')).scaledToHeight(17, Qt.SmoothTransformation) def loadMNConf(self, fileName): hot_masternodes = loadMNConfFile(fileName) if hot_masternodes == None: messText = "Unable to load data from file '%s'" % fileName self.myPopUp2(QMessageBox.Warning, "QMT - warning", messText) else: # Append new masternodes to list new_masternodes = [] skip_masternodes = [] for x in hot_masternodes: if not self.isMasternodeInList(x['name']): self.masternode_list.append(x) new_masternodes.append(x) else: skip_masternodes.append(x) # Show new list for new_masternode in new_masternodes: name = new_masternode['name'] self.tabMain.insert_mn_list(name, new_masternode['ip'], new_masternode['port'], None, isHardware=False) self.tabMain.btn_remove[name].clicked.connect(lambda: self.t_main.onRemoveMN()) # print number of nodes added new_nodes = len(new_masternodes) final_message = "" if new_nodes == 0: final_message = "No External Masternode " elif new_nodes == 1: final_message = "1 External Masternode " else: final_message = "%d External Masternodes " % new_nodes final_message += "added to the list. " if new_nodes > 0: final_message += str([x['name'] for x in new_masternodes]) + ". " if len(skip_masternodes) > 0: final_message += "Following entries skipped due to duplicate names:" final_message += str([x['name'] for x in skip_masternodes]) + ". " printDbg(final_message) if new_nodes > 0: # update files printDbg("saving MN configuration file") writeToFile(self.masternode_list, masternodes_File) printDbg("saved") # Clear voting masternodes configuration and update cache self.t_governance.clear() def myPopUp(self, messType, messTitle, messText, defaultButton=QMessageBox.No): mess = QMessageBox(messType, messTitle, messText, defaultButton, parent=self) mess.setStandardButtons(QMessageBox.Yes | QMessageBox.No) mess.setDefaultButton(defaultButton) return mess.exec_() def myPopUp2(self, messType, messTitle, messText, singleButton=QMessageBox.Ok): mess = QMessageBox(messType, messTitle, messText, singleButton, parent=self) mess.setStandardButtons(singleButton | singleButton) return mess.exec_() @pyqtSlot() def onCheckHw(self): printDbg("Checking for HW device...") self.updateHWstatus(None) self.showHWstatus() @pyqtSlot() def onCheckRpc(self): printDbg("Checking RPC server...") self.runInThread(self.updateRPCstatus, (), self.showRPCstatus) @pyqtSlot() def onCheckVersion(self): printDbg("Checking QMT version...") self.versionLabel.setText("--") self.runInThread(self.checkVersion, (), self.updateVersion) def checkVersion(self, ctrl): local_version = self.parent.version['number'].split('.') remote_version = getRemoteQMTversion().split('.') if (remote_version[0] > local_version[0]) or \ (remote_version[0] == local_version[0] and remote_version[1] > local_version[1]) or \ (remote_version[0] == local_version[0] and remote_version[1] == local_version[1] and remote_version[2] > local_version[2]): self.versionMess = '<b style="color:red">New Version Available:</b> %s.%s.%s ' % ( remote_version[0], remote_version[1], remote_version[2]) self.versionMess += '(<a href="https://github.com/project-qmc/QMT/releases/">download</a>)' else: self.versionMess = "You have the latest version of QMT" def updateVersion(self): if self.versionMess is not None: self.versionLabel.setText(self.versionMess) @pyqtSlot() def onCleanConsole(self): self.consoleArea.clear() @pyqtSlot() def onSaveConsole(self): timestamp = strftime('%Y-%m-%d_%H-%M-%S', gmtime(now())) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName(self, "Save Logs to file", "QMT_Logs_%s.txt" % timestamp, "All Files (*);; Text Files (*.txt)", options=options) try: if fileName: printOK("Saving logs to %s" % fileName) log_file = open(fileName, 'w+') log_text = self.consoleArea.toPlainText() log_file.write(log_text) log_file.close() except Exception as e: err_msg = "error writing Log file" printException(getCallerName(), getFunctionName(), err_msg, e.args) @pyqtSlot() def onTabChange(self): # reload (and re-sort)masternode list in tabs if self.tabs.currentWidget() == self.tabRewards: # reload last used address self.tabRewards.destinationLine.setText(self.parent.cache.get("lastAddress")) # get new order mnOrder = {} mnList = self.tabMain.myList for i in range(mnList.count()): mnName = mnList.itemWidget(mnList.item(i)).alias mnOrder[mnName] = i self.parent.cache['mnList_order'] = mnOrder # Sort masternode list (by alias if no previous order set) if self.parent.cache.get('mnList_order') != {}: self.masternode_list.sort(key=self.parent.extract_order) self.t_rewards.loadMnSelect() self.t_rewards.selectedRewards = None # reload torrent and voting masternode list if self.tabs.currentWidget() == self.tabGovernance: self.t_governance.onRefreshTorrents() self.t_governance.updateSelectedMNlabel() @pyqtSlot() def onToggleConsole(self): if self.btn_consoleToggle.text() == 'Hide': self.btn_consoleToggle.setText('Show') self.consoleArea.hide() self.console.setMinimumHeight(70) self.console.setMaximumHeight(70) else: self.console.setMinimumHeight(70) self.console.setMaximumHeight(starting_height) self.btn_consoleToggle.setText('Hide') self.consoleArea.show() def showHWstatus(self): self.updateHWleds() self.myPopUp2(QMessageBox.Information, 'QMT - hw check', "%s" % self.hwStatusMess, QMessageBox.Ok) def showRPCstatus(self): self.updateRPCled() self.myPopUp2(QMessageBox.Information, 'QMT - rpc check', "%s" % self.rpcStatusMess, QMessageBox.Ok) def updateHWleds(self): if self.hwStatus == 1: self.header.hwLed.setPixmap(self.ledHalfPurpleH_icon) elif self.hwStatus == 2: self.header.hwLed.setPixmap(self.ledPurpleH_icon) else: self.header.hwLed.setPixmap(self.ledGrayH_icon) self.header.hwLed.setToolTip(self.hwStatusMess) def updateHWstatus(self, ctrl): if self.hwdevice is not None: if hasattr(self.hwdevice, 'dongle'): self.hwdevice.dongle.close() self.hwdevice = HWdevice() statusCode, statusMess = self.hwdevice.getStatus() printDbg("mess: %s" % statusMess) if statusCode != 2: # If is not connected try again try: if hasattr(self.hwdevice, 'dongle'): self.hwdevice.dongle.close() self.hwdevice = HWdevice() self.hwdevice.initDevice() statusCode, statusMess = self.hwdevice.getStatus() except Exception as e: err_msg = "error in checkHw" printException(getCallerName(), getFunctionName(), err_msg, e.args) self.hwStatus = statusCode self.hwStatusMess = statusMess # if all is good connect the signals if statusCode == 2: self.hwdevice.sigTxdone.connect(self.t_rewards.FinishSend) self.hwdevice.sigTxabort.connect(self.t_rewards.onCancel) self.hwdevice.tx_progress.connect(self.t_rewards.updateProgressPercent) def updateLastBlockLabel(self): text = '--' if self.rpcLastBlock == 1: text = "Loading block index..." elif self.rpcLastBlock > 0 and self.rpcConnected: text = str(self.rpcLastBlock) text += " (" if not self.isBlockchainSynced: text += "Synchronizing" else: text += "Synced" text += ")" self.header.lastBlockLabel.setText(text) def updateRPCled(self): if self.rpcConnected: self.header.rpcLed.setPixmap(self.ledPurpleH_icon) else: if self.rpcLastBlock == 1: self.header.rpcLed.setPixmap(self.ledHalfPurpleH_icon) else: self.header.rpcLed.setPixmap(self.ledGrayH_icon) self.header.rpcLed.setToolTip(self.rpcStatusMess) self.updateLastBlockLabel() def updateRPCstatus(self, ctrl): if self.rpcClient is None: self.rpcClient = RpcClient() status, statusMess, lastBlock = self.rpcClient.getStatus() self.rpcConnected = status self.rpcLastBlock = lastBlock self.rpcStatusMess = statusMess self.isBlockchainSynced = self.rpcClient.isBlockchainSynced() # If is not connected try again if not status: self.rpcClient = RpcClient()
class IgnoredFolders(QDialog): def __init__(self, dev, ignored_folders=None, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel('<p>'+ _('<b>Scanned folders:</b>') + ' ' + _('You can select which folders calibre will ' 'scan when searching this device for books.')) la.setWordWrap(True) l.addWidget(la) self.tabs = QTabWidget(self) l.addWidget(self.tabs) self.widgets = [] for storage in dev.filesystem_cache.entries: self.dev = dev w = Storage(storage, item_func=self.create_item) del self.dev self.tabs.addTab(w, storage.name) self.widgets.append(w) w.itemChanged.connect(self.item_changed) self.la2 = la = QLabel(_( 'If you a select a previously unselected folder, any sub-folders' ' will not be visible until you restart calibre.')) l.addWidget(la) la.setWordWrap(True) self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.sab = self.bb.addButton(_('Select &All'), self.bb.ActionRole) self.sab.clicked.connect(self.select_all) self.snb = self.bb.addButton(_('Select &None'), self.bb.ActionRole) self.snb.clicked.connect(self.select_none) l.addWidget(self.bb) self.setWindowTitle(_('Choose folders to scan')) self.setWindowIcon(QIcon(I('devices/tablet.png'))) self.resize(600, 500) def item_changed(self, item, column): w = item.treeWidget() root = w.invisibleRootItem() w.itemChanged.disconnect(self.item_changed) try: if item.checkState(0) == Qt.Checked: # Ensure that the parents of this item are checked p = item.parent() while p is not None and p is not root: p.setCheckState(0, Qt.Checked) p = p.parent() # Set the state of all descendants to the same state as this item for child in self.iterchildren(item): child.setCheckState(0, item.checkState(0)) finally: w.itemChanged.connect(self.item_changed) def iterchildren(self, node): ' Iterate over all descendants of node ' for i in xrange(node.childCount()): child = node.child(i) yield child for gc in self.iterchildren(child): yield gc def create_item(self, f, parent): name = f.name ans = QTreeWidgetItem(parent, [name]) ans.setData(0, Qt.UserRole, '/'.join(f.full_path[1:])) ans.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) ans.setCheckState(0, Qt.Unchecked if self.dev.is_folder_ignored(f.storage_id, f.full_path[1:]) else Qt.Checked) ans.setData(0, Qt.DecorationRole, file_icon_provider().icon_from_ext('dir')) return ans def select_all(self): w = self.tabs.currentWidget() for i in xrange(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Checked) def select_none(self): w = self.tabs.currentWidget() for i in xrange(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Unchecked) @property def ignored_folders(self): ans = {} for w in self.widgets: folders = set() for node in self.iterchildren(w.invisibleRootItem()): if node.checkState(0) == Qt.Checked: continue path = unicode(node.data(0, Qt.UserRole) or '') parent = path.rpartition('/')[0] if '/' not in path or icu_lower(parent) not in folders: folders.add(icu_lower(path)) ans[unicode(w.storage.storage_id)] = list(folders) return ans
class MainWin(QMainWindow): """ It's a window, stores a TabWidget """ def __init__(self, parent=None): super(MainWin, self).__init__(parent) self.setWindowTitle("Eilat Browser") # gc.set_debug(gc.DEBUG_LEAK) self.last_closed = None self.tab_widget = QTabWidget(self) self.tab_widget.setTabBar(MidClickTabBar(self)) self.tab_widget.tabBar().setMovable(True) self.tab_widget.setTabsClosable(True) # the right side of the tab already has the space for # a non-shown close button self.tab_widget.setStyleSheet( 'QTabBar::tab {padding-top: 0px; padding-bottom: 0px; ' 'padding-left: 0.3em;} ' 'QTabBar::tab:selected {color: #00f;}') # tabCloseRequested carries int (index of a tab) self.tab_widget.tabCloseRequested.connect(self.del_tab) self.setCentralWidget(self.tab_widget) self.tooltip = NotifyLabel(parent=self) def restore_last_closed(): """ One-use callback for QShortcut. Opens a fresh new tab with the url address of the last tab closed """ if self.last_closed is not None: url = self.last_closed self.add_tab(url) self.last_closed = None def dump_gc(): """ prints sizes for large memory collectable objects """ objs = gc.get_objects() pairs = [(str(k)[:80], type(k).__name__, sys.getsizeof(k)) for k in objs if sys.getsizeof(k) > 1024 * 4 * 5] for pair in pairs: print(pair) def reload_disk_init(): """ transfer options.yaml and the css directory to global maps """ load_options() load_css() notify("reloaded disk config") set_shortcuts([ ("F9", self, dump_gc), # reload configuration ("Ctrl+Shift+R", self, reload_disk_init), # new tabs ("Ctrl+T", self, self.add_tab), ("Ctrl+Shift+T", self, partial(self.add_tab, scripting=True)), ("Y", self, self.new_tab_from_clipboard), # movement ("M", self, self.inc_tab), ("N", self, partial(self.inc_tab, -1)), ("Ctrl+PgUp", self, partial(self.inc_tab, -1)), ("Ctrl+PgDown", self, self.inc_tab), # destroy/undestroy ("U", self, restore_last_closed), ("Ctrl+W", self, self.del_tab), ("Ctrl+Q", self, self.finalize) ]) def new_tab_from_clipboard(self): """ One-use callback for QShortcut. Reads the content of the PRIMARY clipboard and navigates to it on a new tab. """ url = clipboard() if url is not None: self.add_tab(url) # aux. action (en register_actions) def inc_tab(self, incby=1): """ Takes the current tab index, modifies wrapping around, and sets as current. Afterwards the active tab has focus on its webkit area. """ if self.tab_widget.count() < 2: return idx = self.tab_widget.currentIndex() idx += incby if idx < 0: idx = self.tab_widget.count() - 1 elif idx >= self.tab_widget.count(): idx = 0 self.tab_widget.setCurrentIndex(idx) self.tab_widget.currentWidget().webkit.setFocus() def finalize(self): """ Just doing self.close() doesn't clean up; for example, closing when the address bar popup is visible doesn't close the popup, and leaves the window hidden and unclosable (except e.g. for KILL 15) Makes a hard app close through os._exit to prevent garbage collection; cleanup has typically done more harm than good. Any state that we may want to preserve we should do ourselves (e.g. cookies through the NAMs) """ idx = self.tab_widget.currentIndex() self.tab_widget.widget(idx).deleteLater() self.tab_widget.removeTab(idx) close_managers() # also does an os._exit # action y connect en llamada en constructor def del_tab(self, idx=None): """ Closes a tab. If 'idx' is set, it was called by a tabCloseRequested signal (maybe mid click). If not, it was called by a keyboard action and closes the currently active tab. Afterwards the active tab has focus on its webkit area. It closes the window when deleting the last active tab. """ if idx is None: idx = self.tab_widget.currentIndex() self.tab_widget.widget(idx).webkit.stop() self.last_closed = self.tab_widget.widget(idx).webkit.url() self.tab_widget.widget(idx).deleteLater() self.tab_widget.removeTab(idx) if len(self.tab_widget) == 0: close_managers() # also does an os.__exit else: self.tab_widget.currentWidget().webkit.setFocus() # action (en register_actions) # only way to create a new tab # called externally in eilat.py to create the first tab def add_tab(self, url=None, scripting=False): """ Creates a new tab, either empty or navegating to the url. Sets itself as the active tab. If navegating to an url it gives focus to the webkit area. Otherwise, the address bar is focused. """ tab = WebTab(parent=self.tab_widget) self.tab_widget.addTab(tab, tab.current['title']) self.tab_widget.setCurrentWidget(tab) tab_idx = self.tab_widget.indexOf(tab) self.tab_widget.tabBar().tabButton(tab_idx, 1).hide() # 1: right align if scripting: tab.toggle_script() if url is not None: qurl = fix_url(url) tab.webkit.navigate(qurl) else: tab.address_bar.setFocus()
class Editor(QWidget): # {{{ def __init__(self, parent=None, one_line_toolbar=False): QWidget.__init__(self, parent) self.toolbar1 = QToolBar(self) self.toolbar2 = QToolBar(self) self.toolbar3 = QToolBar(self) for i in range(1, 4): t = getattr(self, 'toolbar%d'%i) t.setIconSize(QSize(18, 18)) self.editor = EditorWidget(self) self.set_html = self.editor.set_html self.tabs = QTabWidget(self) self.tabs.setTabPosition(self.tabs.South) self.wyswyg = QWidget(self.tabs) self.code_edit = QPlainTextEdit(self.tabs) self.source_dirty = False self.wyswyg_dirty = True self._layout = QVBoxLayout(self) self.wyswyg.layout = l = QVBoxLayout(self.wyswyg) self.setLayout(self._layout) l.setContentsMargins(0, 0, 0, 0) if one_line_toolbar: tb = QHBoxLayout() l.addLayout(tb) else: tb = l tb.addWidget(self.toolbar1) tb.addWidget(self.toolbar2) tb.addWidget(self.toolbar3) l.addWidget(self.editor) self._layout.addWidget(self.tabs) self.tabs.addTab(self.wyswyg, _('N&ormal view')) self.tabs.addTab(self.code_edit, _('&HTML Source')) self.tabs.currentChanged[int].connect(self.change_tab) self.highlighter = Highlighter(self.code_edit.document()) self.layout().setContentsMargins(0, 0, 0, 0) # toolbar1 {{{ self.toolbar1.addAction(self.editor.action_undo) self.toolbar1.addAction(self.editor.action_redo) self.toolbar1.addAction(self.editor.action_select_all) self.toolbar1.addAction(self.editor.action_remove_format) self.toolbar1.addAction(self.editor.action_clear) self.toolbar1.addSeparator() for x in ('copy', 'cut', 'paste'): ac = getattr(self.editor, 'action_'+x) self.toolbar1.addAction(ac) self.toolbar1.addSeparator() self.toolbar1.addAction(self.editor.action_background) # }}} # toolbar2 {{{ for x in ('', 'un'): ac = getattr(self.editor, 'action_%sordered_list'%x) self.toolbar2.addAction(ac) self.toolbar2.addSeparator() for x in ('superscript', 'subscript', 'indent', 'outdent'): self.toolbar2.addAction(getattr(self.editor, 'action_' + x)) if x in ('subscript', 'outdent'): self.toolbar2.addSeparator() self.toolbar2.addAction(self.editor.action_block_style) w = self.toolbar2.widgetForAction(self.editor.action_block_style) w.setPopupMode(w.InstantPopup) self.toolbar2.addAction(self.editor.action_insert_link) # }}} # toolbar3 {{{ for x in ('bold', 'italic', 'underline', 'strikethrough'): ac = getattr(self.editor, 'action_'+x) self.toolbar3.addAction(ac) self.toolbar3.addSeparator() for x in ('left', 'center', 'right', 'justified'): ac = getattr(self.editor, 'action_align_'+x) self.toolbar3.addAction(ac) self.toolbar3.addSeparator() self.toolbar3.addAction(self.editor.action_color) # }}} self.code_edit.textChanged.connect(self.code_dirtied) self.editor.page().contentsChanged.connect(self.wyswyg_dirtied) def set_minimum_height_for_editor(self, val): self.editor.setMinimumHeight(val) @dynamic_property def html(self): def fset(self, v): self.editor.html = v def fget(self): self.tabs.setCurrentIndex(0) return self.editor.html return property(fget=fget, fset=fset) def change_tab(self, index): # print 'reloading:', (index and self.wyswyg_dirty) or (not index and # self.source_dirty) if index == 1: # changing to code view if self.wyswyg_dirty: self.code_edit.setPlainText(self.editor.html) self.wyswyg_dirty = False elif index == 0: # changing to wyswyg if self.source_dirty: self.editor.html = unicode(self.code_edit.toPlainText()) self.source_dirty = False @dynamic_property def tab(self): def fget(self): return 'code' if self.tabs.currentWidget() is self.code_edit else 'wyswyg' def fset(self, val): self.tabs.setCurrentWidget(self.code_edit if val == 'code' else self.wyswyg) return property(fget=fget, fset=fset) def wyswyg_dirtied(self, *args): self.wyswyg_dirty = True def code_dirtied(self, *args): self.source_dirty = True def hide_toolbars(self): self.toolbar1.setVisible(False) self.toolbar2.setVisible(False) self.toolbar3.setVisible(False) def show_toolbars(self): self.toolbar1.setVisible(True) self.toolbar2.setVisible(True) self.toolbar3.setVisible(True) @dynamic_property def toolbars_visible(self): def fget(self): return self.toolbar1.isVisible() or self.toolbar2.isVisible() or self.toolbar3.isVisible() def fset(self, val): getattr(self, ('show' if val else 'hide') + '_toolbars')() return property(fget=fget, fset=fset) def set_readonly(self, what): self.editor.set_readonly(what) def hide_tabs(self): self.tabs.tabBar().setVisible(False)
class SchedulerDialog(QDialog): SCHEDULE_TYPES = OrderedDict([ ('days_of_week', DaysOfWeek), ('days_of_month', DaysOfMonth), ('every_x_days', EveryXDays), ]) download = pyqtSignal(object) def __init__(self, recipe_model, parent=None): QDialog.__init__(self, parent) self.commit_on_change = True self.previous_urn = None self.setWindowIcon(QIcon(I('scheduler.png'))) self.setWindowTitle(_("Schedule news download")) self.l = l = QGridLayout(self) # Left panel self.h = h = QHBoxLayout() l.addLayout(h, 0, 0, 1, 1) self.search = s = SearchBox2(self) self.search.initialize('scheduler_search_history') self.search.setMinimumContentsLength(15) self.go_button = b = QToolButton(self) b.setText(_("Go")) b.clicked.connect(self.search.do_search) h.addWidget(s), h.addWidget(b) self.recipes = RecipesView(self) l.addWidget(self.recipes, 1, 0, 1, 1) self.recipe_model = recipe_model self.recipe_model.do_refresh() self.recipes.setModel(self.recipe_model) self.recipes.setFocus(Qt.OtherFocusReason) self.count_label = la = QLabel(_('%s news sources') % self.recipe_model.showing_count) la.setAlignment(Qt.AlignCenter) l.addWidget(la, 2, 0, 1, 1) self.search.search.connect(self.recipe_model.search) self.recipe_model.searched.connect(self.search.search_done, type=Qt.QueuedConnection) self.recipe_model.searched.connect(self.search_done) # Right Panel self.scroll_area_contents = sac = QWidget(self) self.l.addWidget(sac, 0, 1, 2, 1) sac.v = v = QVBoxLayout(sac) v.setContentsMargins(0, 0, 0, 0) self.detail_box = QTabWidget(self) self.detail_box.setVisible(False) self.detail_box.setCurrentIndex(0) v.addWidget(self.detail_box) v.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) # First Tab (scheduling) self.tab = QWidget() self.detail_box.addTab(self.tab, _("&Schedule")) self.tab.v = vt = QVBoxLayout(self.tab) vt.setContentsMargins(0, 0, 0, 0) self.blurb = la = QLabel('blurb') la.setWordWrap(True), la.setOpenExternalLinks(True) vt.addWidget(la) self.frame = f = QFrame(self.tab) vt.addWidget(f) f.setFrameShape(f.StyledPanel) f.setFrameShadow(f.Raised) f.v = vf = QVBoxLayout(f) self.schedule = s = QCheckBox(_("&Schedule for download:"), f) self.schedule.stateChanged[int].connect(self.toggle_schedule_info) vf.addWidget(s) f.h = h = QHBoxLayout() vf.addLayout(h) self.days_of_week = QRadioButton(_("&Days of week"), f) self.days_of_month = QRadioButton(_("Da&ys of month"), f) self.every_x_days = QRadioButton(_("Every &x days"), f) self.days_of_week.setChecked(True) h.addWidget(self.days_of_week), h.addWidget(self.days_of_month), h.addWidget(self.every_x_days) self.schedule_stack = ss = QStackedWidget(f) self.schedule_widgets = [] for key in reversed(self.SCHEDULE_TYPES): self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self)) self.schedule_stack.insertWidget(0, self.schedule_widgets[0]) vf.addWidget(ss) self.last_downloaded = la = QLabel(f) la.setWordWrap(True) vf.addWidget(la) self.account = acc = QGroupBox(self.tab) acc.setTitle(_("&Account")) vt.addWidget(acc) acc.g = g = QGridLayout(acc) acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account) spw.stateChanged[int].connect(self.set_pw_echo_mode) g.addWidget(spw, 2, 0, 1, 2) self.rla = la = QLabel(_("For the scheduling to work, you must leave calibre running.")) vt.addWidget(la) for b, c in iteritems(self.SCHEDULE_TYPES): b = getattr(self, b) b.toggled.connect(self.schedule_type_selected) b.setToolTip(textwrap.dedent(c.HELP)) # Second tab (advanced settings) self.tab2 = t2 = QWidget() self.detail_box.addTab(self.tab2, _("&Advanced")) self.tab2.g = g = QGridLayout(t2) g.setContentsMargins(0, 0, 0, 0) self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2) g.addWidget(tt, 0, 0, 1, 2) t2.la = la = QLabel(_("&Extra tags:")) self.custom_tags = ct = QLineEdit(self) la.setBuddy(ct) g.addWidget(la), g.addWidget(ct, 1, 1) t2.la2 = la = QLabel(_("&Keep at most:")) la.setToolTip(_("Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable).")) self.keep_issues = ki = QSpinBox(t2) tt.toggled['bool'].connect(self.keep_issues.setEnabled) ki.setMaximum(100000), la.setBuddy(ki) ki.setToolTip(_( "<p>When set, this option will cause calibre to keep, at most, the specified number of issues" " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the" " total is larger than this number.\n<p>Note that this feature only works if you have the" " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals" " older than a number of days, below, takes priority over this setting.")) ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues")) g.addWidget(la), g.addWidget(ki, 2, 1) si = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) g.addItem(si, 3, 1, 1, 1) # Bottom area self.hb = h = QHBoxLayout() self.l.addLayout(h, 2, 1, 1, 1) self.labt = la = QLabel(_("Delete downloaded &news older than:")) self.old_news = on = QSpinBox(self) on.setToolTip(_( "<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n" "<p>You can also control the maximum number of issues of a specific periodical that are kept" " by clicking the Advanced tab for that periodical above.")) on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days")) on.setMaximum(1000), la.setBuddy(on) on.setValue(gconf['oldest_news']) h.addWidget(la), h.addWidget(on) self.download_all_button = b = QPushButton(QIcon(I('news.png')), _("Download &all scheduled"), self) b.setToolTip(_("Download all scheduled news sources at once")) b.clicked.connect(self.download_all_clicked) self.l.addWidget(b, 3, 0, 1, 1) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self) bb.accepted.connect(self.accept), bb.rejected.connect(self.reject) self.download_button = b = bb.addButton(_('&Download now'), bb.ActionRole) b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False) b.clicked.connect(self.download_clicked) self.l.addWidget(bb, 3, 1, 1, 1) geom = gprefs.get('scheduler_dialog_geometry') if geom is not None: QApplication.instance().safe_restore_geometry(self, geom) def sizeHint(self): return QSize(800, 600) def set_pw_echo_mode(self, state): self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password) def schedule_type_selected(self, *args): for i, st in enumerate(self.SCHEDULE_TYPES): if getattr(self, st).isChecked(): self.schedule_stack.setCurrentIndex(i) break def keyPressEvent(self, ev): if ev.key() not in (Qt.Key_Enter, Qt.Key_Return): return QDialog.keyPressEvent(self, ev) def break_cycles(self): try: self.recipe_model.searched.disconnect(self.search_done) self.recipe_model.searched.disconnect(self.search.search_done) self.search.search.disconnect() self.download.disconnect() except: pass self.recipe_model = None def search_done(self, *args): if self.recipe_model.showing_count < 10: self.recipes.expandAll() def toggle_schedule_info(self, *args): enabled = self.schedule.isChecked() for x in self.SCHEDULE_TYPES: getattr(self, x).setEnabled(enabled) self.schedule_stack.setEnabled(enabled) self.last_downloaded.setVisible(enabled) def current_changed(self, current, previous): if self.previous_urn is not None: self.commit(urn=self.previous_urn) self.previous_urn = None urn = self.current_urn if urn is not None: self.initialize_detail_box(urn) self.recipes.scrollTo(current) def accept(self): if not self.commit(): return False self.save_geometry() return QDialog.accept(self) def reject(self): self.save_geometry() return QDialog.reject(self) def save_geometry(self): gprefs.set('scheduler_dialog_geometry', bytearray(self.saveGeometry())) def download_clicked(self, *args): self.commit() if self.commit() and self.current_urn: self.download.emit(self.current_urn) def download_all_clicked(self, *args): if self.commit() and self.commit(): self.download.emit(None) @property def current_urn(self): current = self.recipes.currentIndex() if current.isValid(): return getattr(current.internalPointer(), 'urn', None) def commit(self, urn=None): urn = self.current_urn if urn is None else urn if not self.detail_box.isVisible() or urn is None: return True if self.account.isVisible(): un, pw = map(unicode_type, (self.username.text(), self.password.text())) un, pw = un.strip(), pw.strip() if not un and not pw and self.schedule.isChecked(): if not getattr(self, 'subscription_optional', False): error_dialog(self, _('Need username and password'), _('You must provide a username and/or password to ' 'use this news source.'), show=True) return False if un or pw: self.recipe_model.set_account_info(urn, un, pw) else: self.recipe_model.clear_account_info(urn) if self.schedule.isChecked(): schedule_type, schedule = \ self.schedule_stack.currentWidget().schedule self.recipe_model.schedule_recipe(urn, schedule_type, schedule) else: self.recipe_model.un_schedule_recipe(urn) add_title_tag = self.add_title_tag.isChecked() keep_issues = '0' if self.keep_issues.isEnabled(): keep_issues = unicode_type(self.keep_issues.value()) custom_tags = unicode_type(self.custom_tags.text()).strip() custom_tags = [x.strip() for x in custom_tags.split(',')] self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues) return True def initialize_detail_box(self, urn): self.previous_urn = urn self.detail_box.setVisible(True) self.download_button.setVisible(True) self.detail_box.setCurrentIndex(0) recipe = self.recipe_model.recipe_from_urn(urn) try: schedule_info = self.recipe_model.schedule_info_from_urn(urn) except: # Happens if user does something stupid like unchecking all the # days of the week schedule_info = None account_info = self.recipe_model.account_info_from_urn(urn) customize_info = self.recipe_model.get_customize_info(urn) ns = recipe.get('needs_subscription', '') self.account.setVisible(ns in ('yes', 'optional')) self.subscription_optional = ns == 'optional' act = _('Account') act2 = _('(optional)') if self.subscription_optional else \ _('(required)') self.account.setTitle(act+' '+act2) un = pw = '' if account_info is not None: un, pw = account_info[:2] if not un: un = '' if not pw: pw = '' self.username.setText(un) self.password.setText(pw) self.show_password.setChecked(False) self.blurb.setText(''' <p> <b>%(title)s</b><br> %(cb)s %(author)s<br/> %(description)s </p> '''%dict(title=recipe.get('title'), cb=_('Created by: '), author=recipe.get('author', _('Unknown')), description=recipe.get('description', ''))) self.download_button.setToolTip( _('Download %s now')%recipe.get('title')) scheduled = schedule_info is not None self.schedule.setChecked(scheduled) self.toggle_schedule_info() self.last_downloaded.setText(_('Last downloaded: never')) ld_text = _('never') if scheduled: typ, sch, last_downloaded = schedule_info d = utcnow() - last_downloaded def hm(x): return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60 hours, minutes = hm(d.seconds) tm = _('%(days)d days, %(hours)d hours' ' and %(mins)d minutes ago')%dict( days=d.days, hours=hours, mins=minutes) if d < timedelta(days=366): ld_text = tm else: typ, sch = 'day/time', (-1, 6, 0) sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1, 'interval':2}[typ] rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget]) rb.setChecked(True) self.schedule_stack.setCurrentIndex(sch_widget) self.schedule_stack.currentWidget().initialize(typ, sch) add_title_tag, custom_tags, keep_issues = customize_info self.add_title_tag.setChecked(add_title_tag) self.custom_tags.setText(', '.join(custom_tags)) self.last_downloaded.setText(_('Last downloaded:') + ' ' + ld_text) try: keep_issues = int(keep_issues) except: keep_issues = 0 self.keep_issues.setValue(keep_issues) self.keep_issues.setEnabled(self.add_title_tag.isChecked())
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip( _('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip( _('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>' )) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex( self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip( _('<qt>Specify additional command-line arguments for the conversion</qt>' )) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80, 20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text( self.replace_templates( prefs['custom_CSS_list'][prefs['default_CSS']]), 'css') self.css1.setToolTip( _('<qt>This stylesheet can be modified<br/>The default can be configured</qt>' )) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip( i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip( _('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip( i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>' )) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip( _('<qt>Show/hide the additional styles used for the conversion</qt>' )) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text( self.replace_templates(prefs['custom_CSS_list'][style]), 'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@', text, re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print( _('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile(mode='w+') custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile(mode='w+') for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels) process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog( self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()
class SchedulerDialog(QDialog): SCHEDULE_TYPES = OrderedDict([ ('days_of_week', DaysOfWeek), ('days_of_month', DaysOfMonth), ('every_x_days', EveryXDays), ]) download = pyqtSignal(object) def __init__(self, recipe_model, parent=None): QDialog.__init__(self, parent) self.commit_on_change = True self.previous_urn = None self.setWindowIcon(QIcon(I('scheduler.png'))) self.setWindowTitle(_("Schedule news download")) self.l = l = QGridLayout(self) # Left panel self.h = h = QHBoxLayout() l.addLayout(h, 0, 0, 1, 1) self.search = s = SearchBox2(self) self.search.initialize('scheduler_search_history') self.search.setMinimumContentsLength(15) self.go_button = b = QToolButton(self) b.setText(_("Go")) b.clicked.connect(self.search.do_search) self.clear_search_button = cb = QToolButton(self) self.clear_search_button.clicked.connect(self.search.clear_clicked) cb.setIcon(QIcon(I('clear_left.png'))) h.addWidget(s), h.addWidget(b), h.addWidget(cb) self.recipes = RecipesView(self) l.addWidget(self.recipes, 1, 0, 1, 1) self.recipe_model = recipe_model self.recipe_model.do_refresh() self.recipes.setModel(self.recipe_model) self.recipes.setFocus(Qt.OtherFocusReason) self.count_label = la = QLabel(_('%s news sources') % self.recipe_model.showing_count) la.setAlignment(Qt.AlignCenter) l.addWidget(la, 2, 0, 1, 1) self.search.search.connect(self.recipe_model.search) self.recipe_model.searched.connect(self.search.search_done, type=Qt.QueuedConnection) self.recipe_model.searched.connect(self.search_done) # Right Panel self.scroll_area = sa = QScrollArea(self) self.l.addWidget(sa, 0, 1, 2, 1) sa.setFrameShape(QFrame.NoFrame) sa.setWidgetResizable(True) self.scroll_area_contents = sac = QWidget(self) sa.setWidget(sac) sac.v = v = QVBoxLayout(sac) v.setContentsMargins(0, 0, 0, 0) self.detail_box = QTabWidget(self) self.detail_box.setVisible(False) self.detail_box.setCurrentIndex(0) v.addWidget(self.detail_box) v.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) # First Tab (scheduling) self.tab = QWidget() self.detail_box.addTab(self.tab, _("&Schedule")) self.tab.v = vt = QVBoxLayout(self.tab) vt.setContentsMargins(0, 0, 0, 0) self.blurb = la = QLabel('blurb') la.setWordWrap(True), la.setOpenExternalLinks(True) vt.addWidget(la) vt.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.frame = f = QFrame(self.tab) vt.addWidget(f) f.setFrameShape(f.StyledPanel) f.setFrameShadow(f.Raised) f.v = vf = QVBoxLayout(f) self.schedule = s = QCheckBox(_("&Schedule for download:"), f) self.schedule.stateChanged[int].connect(self.toggle_schedule_info) vf.addWidget(s) f.h = h = QHBoxLayout() vf.addLayout(h) self.days_of_week = QRadioButton(_("&Days of week"), f) self.days_of_month = QRadioButton(_("Da&ys of month"), f) self.every_x_days = QRadioButton(_("Every &x days"), f) self.days_of_week.setChecked(True) h.addWidget(self.days_of_week), h.addWidget(self.days_of_month), h.addWidget(self.every_x_days) self.schedule_stack = ss = QStackedWidget(f) self.schedule_widgets = [] for key in reversed(self.SCHEDULE_TYPES): self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self)) self.schedule_stack.insertWidget(0, self.schedule_widgets[0]) vf.addWidget(ss) self.last_downloaded = la = QLabel(f) la.setWordWrap(True) vf.addWidget(la) vt.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.account = acc = QGroupBox(self.tab) acc.setTitle(_("&Account")) vt.addWidget(acc) acc.g = g = QGridLayout(acc) acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account) spw.stateChanged[int].connect(self.set_pw_echo_mode) g.addWidget(spw, 2, 0, 1, 2) self.rla = la = QLabel(_("For the scheduling to work, you must leave calibre running.")) vt.addWidget(la) for b, c in self.SCHEDULE_TYPES.iteritems(): b = getattr(self, b) b.toggled.connect(self.schedule_type_selected) b.setToolTip(textwrap.dedent(c.HELP)) # Second tab (advanced settings) self.tab2 = t2 = QWidget() self.detail_box.addTab(self.tab2, _("&Advanced")) self.tab2.g = g = QGridLayout(t2) g.setContentsMargins(0, 0, 0, 0) self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2) g.addWidget(tt, 0, 0, 1, 2) t2.la = la = QLabel(_("&Extra tags:")) self.custom_tags = ct = QLineEdit(self) la.setBuddy(ct) g.addWidget(la), g.addWidget(ct, 1, 1) t2.la2 = la = QLabel(_("&Keep at most:")) la.setToolTip(_("Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable).")) self.keep_issues = ki = QSpinBox(t2) tt.toggled['bool'].connect(self.keep_issues.setEnabled) ki.setMaximum(100000), la.setBuddy(ki) ki.setToolTip(_( "<p>When set, this option will cause calibre to keep, at most, the specified number of issues" " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the" " total is larger than this number.\n<p>Note that this feature only works if you have the" " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals" " older than a number of days, below, takes priority over this setting.")) ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues")) g.addWidget(la), g.addWidget(ki, 2, 1) si = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) g.addItem(si, 3, 1, 1, 1) # Bottom area self.hb = h = QHBoxLayout() self.l.addLayout(h, 2, 1, 1, 1) self.labt = la = QLabel(_("Delete downloaded &news older than:")) self.old_news = on = QSpinBox(self) on.setToolTip(_( "<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n" "<p>You can also control the maximum number of issues of a specific periodical that are kept" " by clicking the Advanced tab for that periodical above.")) on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days")) on.setMaximum(1000), la.setBuddy(on) on.setValue(gconf['oldest_news']) h.addWidget(la), h.addWidget(on) self.download_all_button = b = QPushButton(QIcon(I('news.png')), _("Download &all scheduled"), self) b.setToolTip(_("Download all scheduled news sources at once")) b.clicked.connect(self.download_all_clicked) self.l.addWidget(b, 3, 0, 1, 1) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Save, self) bb.accepted.connect(self.accept), bb.rejected.connect(self.reject) self.download_button = b = bb.addButton(_('&Download now'), bb.ActionRole) b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False) b.clicked.connect(self.download_clicked) self.l.addWidget(bb, 3, 1, 1, 1) geom = gprefs.get('scheduler_dialog_geometry') if geom is not None: self.restoreGeometry(geom) def sizeHint(self): return QSize(800, 600) def set_pw_echo_mode(self, state): self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password) def schedule_type_selected(self, *args): for i, st in enumerate(self.SCHEDULE_TYPES): if getattr(self, st).isChecked(): self.schedule_stack.setCurrentIndex(i) break def keyPressEvent(self, ev): if ev.key() not in (Qt.Key_Enter, Qt.Key_Return): return QDialog.keyPressEvent(self, ev) def break_cycles(self): try: self.recipe_model.searched.disconnect(self.search_done) self.recipe_model.searched.disconnect(self.search.search_done) self.search.search.disconnect() self.download.disconnect() except: pass self.recipe_model = None def search_done(self, *args): if self.recipe_model.showing_count < 10: self.recipes.expandAll() def toggle_schedule_info(self, *args): enabled = self.schedule.isChecked() for x in self.SCHEDULE_TYPES: getattr(self, x).setEnabled(enabled) self.schedule_stack.setEnabled(enabled) self.last_downloaded.setVisible(enabled) def current_changed(self, current, previous): if self.previous_urn is not None: self.commit(urn=self.previous_urn) self.previous_urn = None urn = self.current_urn if urn is not None: self.initialize_detail_box(urn) self.recipes.scrollTo(current) def accept(self): if not self.commit(): return False self.save_geometry() return QDialog.accept(self) def reject(self): self.save_geometry() return QDialog.reject(self) def save_geometry(self): gprefs.set('scheduler_dialog_geometry', bytearray(self.saveGeometry())) def download_clicked(self, *args): self.commit() if self.commit() and self.current_urn: self.download.emit(self.current_urn) def download_all_clicked(self, *args): if self.commit() and self.commit(): self.download.emit(None) @property def current_urn(self): current = self.recipes.currentIndex() if current.isValid(): return getattr(current.internalPointer(), 'urn', None) def commit(self, urn=None): urn = self.current_urn if urn is None else urn if not self.detail_box.isVisible() or urn is None: return True if self.account.isVisible(): un, pw = map(unicode, (self.username.text(), self.password.text())) un, pw = un.strip(), pw.strip() if not un and not pw and self.schedule.isChecked(): if not getattr(self, 'subscription_optional', False): error_dialog(self, _('Need username and password'), _('You must provide a username and/or password to ' 'use this news source.'), show=True) return False if un or pw: self.recipe_model.set_account_info(urn, un, pw) else: self.recipe_model.clear_account_info(urn) if self.schedule.isChecked(): schedule_type, schedule = \ self.schedule_stack.currentWidget().schedule self.recipe_model.schedule_recipe(urn, schedule_type, schedule) else: self.recipe_model.un_schedule_recipe(urn) add_title_tag = self.add_title_tag.isChecked() keep_issues = u'0' if self.keep_issues.isEnabled(): keep_issues = unicode(self.keep_issues.value()) custom_tags = unicode(self.custom_tags.text()).strip() custom_tags = [x.strip() for x in custom_tags.split(',')] self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues) return True def initialize_detail_box(self, urn): self.previous_urn = urn self.detail_box.setVisible(True) self.download_button.setVisible(True) self.detail_box.setCurrentIndex(0) recipe = self.recipe_model.recipe_from_urn(urn) try: schedule_info = self.recipe_model.schedule_info_from_urn(urn) except: # Happens if user does something stupid like unchecking all the # days of the week schedule_info = None account_info = self.recipe_model.account_info_from_urn(urn) customize_info = self.recipe_model.get_customize_info(urn) ns = recipe.get('needs_subscription', '') self.account.setVisible(ns in ('yes', 'optional')) self.subscription_optional = ns == 'optional' act = _('Account') act2 = _('(optional)') if self.subscription_optional else \ _('(required)') self.account.setTitle(act+' '+act2) un = pw = '' if account_info is not None: un, pw = account_info[:2] if not un: un = '' if not pw: pw = '' self.username.setText(un) self.password.setText(pw) self.show_password.setChecked(False) self.blurb.setText(''' <p> <b>%(title)s</b><br> %(cb)s %(author)s<br/> %(description)s </p> '''%dict(title=recipe.get('title'), cb=_('Created by: '), author=recipe.get('author', _('Unknown')), description=recipe.get('description', ''))) self.download_button.setToolTip( _('Download %s now')%recipe.get('title')) scheduled = schedule_info is not None self.schedule.setChecked(scheduled) self.toggle_schedule_info() self.last_downloaded.setText(_('Last downloaded: never')) ld_text = _('never') if scheduled: typ, sch, last_downloaded = schedule_info d = utcnow() - last_downloaded def hm(x): return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60 hours, minutes = hm(d.seconds) tm = _('%(days)d days, %(hours)d hours' ' and %(mins)d minutes ago')%dict( days=d.days, hours=hours, mins=minutes) if d < timedelta(days=366): ld_text = tm else: typ, sch = 'day/time', (-1, 6, 0) sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1, 'interval':2}[typ] rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget]) rb.setChecked(True) self.schedule_stack.setCurrentIndex(sch_widget) self.schedule_stack.currentWidget().initialize(typ, sch) add_title_tag, custom_tags, keep_issues = customize_info self.add_title_tag.setChecked(add_title_tag) self.custom_tags.setText(u', '.join(custom_tags)) self.last_downloaded.setText(_('Last downloaded:') + ' ' + ld_text) try: keep_issues = int(keep_issues) except: keep_issues = 0 self.keep_issues.setValue(keep_issues) self.keep_issues.setEnabled(self.add_title_tag.isChecked())
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip(_('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>')) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip(_('<qt>Specify additional command-line arguments for the conversion</qt>')) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80,20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][prefs['default_CSS']]),'css') self.css1.setToolTip(_('<qt>This stylesheet can be modified<br/>The default can be configured</qt>')) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip(i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip(_('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip(i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>')) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip(_('<qt>Show/hide the additional styles used for the conversion</qt>')) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][style]),'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@',text,re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print(_('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile() custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile() for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0,0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels); process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog(self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()
class MacleodWindow(QMainWindow): def __init__(self, parent=None): super(MacleodWindow, self).__init__(parent) # store the project path self.root_path = filemgt.read_config('system', 'path') # key: CodeEditor object, value: ontology object self.ontologies = dict() self.setup_widgets() self.setup_layout() def setup_widgets(self): # file editing and tabs self.editor_pane = gui_widgets.TabController(self, self.ontologies) self.editor_pane.currentChanged.connect(self.__on_tab_change) # project navigation self.explorer_tab = QTabWidget(self) self.project_explorer = gui_widgets.ProjectExplorer(self, self.root_path, self.editor_pane) self.explorer_tab.addTab(self.project_explorer, "Directory") self.import_explorer = gui_widgets.ImportSidebar(self, self.root_path, self.editor_pane) self.explorer_tab.addTab(self.import_explorer, "Imports") # informational sidebar self.info_bar = gui_widgets.InformationSidebar(self, self.root_path) # output self.console = gui_widgets.Console(self) main_menu = self.menuBar() # file menu and associated actions file_menu = main_menu.addMenu('File') # Create a new tab new_action = QAction("New File", self) file_menu.addAction(new_action) new_action.triggered.connect(self.new_command) # Open a file open_action = QAction("Open", self) file_menu.addAction(open_action) open_action.triggered.connect(self.open_command) open_shortcut = QShortcut(QKeySequence("Ctrl+O"), self) open_shortcut.activated.connect(self.open_command) # Save file; if no file, open dialog save_action = QAction("Save", self) file_menu.addAction(save_action) save_action.triggered.connect(self.save_command) save_shortcut = QShortcut(QKeySequence("Ctrl+S"), self) save_shortcut.activated.connect(self.save_command) # Open Save dialog saveas_action = QAction("Save As..", self) file_menu.addAction(saveas_action) saveas_action.triggered.connect(self.saveas_command) # Open settings dialog settings_action = QAction("Settings..", self) file_menu.addAction(settings_action) settings_action.triggered.connect(self.settings_command) # Open Export dialog export_action = QAction("Export.. ", self) file_menu.addAction(export_action) export_action.triggered.connect(self.export_command) # Run menu and associated actions run_menu = main_menu.addMenu('Run') # Run the parse w/out resolving imports parse_action = QAction("Parse (No Imports)", self) run_menu.addAction(parse_action) parse_action.triggered.connect(self.parse_command) # Run the parse w/ imports parse_imports_action = QAction("Parse (w/ Imports)", self) run_menu.addAction(parse_imports_action) parse_imports_action.triggered.connect(self.parse_imports_command) run_menu.addSeparator() # Run the check consistency dialog check_consistency_action = QAction("Check Consistency..", self) run_menu.addAction(check_consistency_action) check_consistency_action.triggered.connect(self.check_consistency_command) # Threads self.parse_thread = gui_threads.ParseThread() self.parse_thread.finished.connect(self.__on_parse_done) def setup_layout(self): # group the editor with the console vertical_splitter = QSplitter(self) vertical_splitter.setOrientation(Qt.Vertical) vertical_splitter.addWidget(self.editor_pane) vertical_splitter.addWidget(self.console) vertical_splitter.setStretchFactor(0, 3) vertical_splitter.setStretchFactor(1, 1) # group horizontal_splitter = QSplitter(self) horizontal_splitter.addWidget(self.explorer_tab) horizontal_splitter.addWidget(vertical_splitter) horizontal_splitter.addWidget(self.info_bar) horizontal_splitter.setStretchFactor(0, 1) horizontal_splitter.setStretchFactor(1, 4) horizontal_splitter.setStretchFactor(2, 1) self.setCentralWidget(horizontal_splitter) def __on_parse_done(self): """ Update the UI when the parse thread completes """ path = self.editor_pane.file_helper.get_path(self.editor_pane.currentWidget()) ontology = self.parse_thread.ontology self.info_bar.flush() self.import_explorer.clear() # See if the parse thread caught any errors if self.parse_thread.error.contents != "": print(self.parse_thread.error.contents) self.parse_thread.error.flush() self.info_bar.build_model(ontology, path) # See if the info bar caught any errors if self.info_bar.error: print(self.info_bar.error) self.info_bar.build_tree() self.import_explorer.build_tree(ontology) self.add_ontology(ontology) gui_highlighter.CLIFSyntaxHighlighter(self.editor_pane.currentWidget(), self.info_bar.predicates, self.info_bar.functions) def add_ontology(self, ontology=None): """ Stores ontology matching the current file """ key = self.editor_pane.currentWidget() self.ontologies[key] = ontology def open_command(self): filename = QFileDialog.getOpenFileName(self, "Open File", str(os.curdir), "Common Logic Files (*.clif);; All files (*)") if not filename[0]: return self.editor_pane.add_file(filename[0]) def new_command(self): self.editor_pane.add_file() def save_command(self): text_widget = self.editor_pane.currentWidget() path = self.editor_pane.file_helper.get_path(text_widget) if path is None: return self.saveas_command() f = open(path, 'w') with f: f.write(text_widget.toPlainText()) self.editor_pane.file_helper.update_clean_hash(text_widget, text_widget.toPlainText()) return path def saveas_command(self): text_widget = self.editor_pane.currentWidget() filename = QFileDialog.getSaveFileName(self, "Save As..", str(os.curdir), "Common Logic Files (*.clif);; All files (*)") path = filename[0] if path == "": return None f = open(path, 'w') with f: f.write(text_widget.toPlainText()) self.editor_pane.setTabText(self.editor_pane.currentIndex(), os.path.basename(path)) self.editor_pane.file_helper.add_path(text_widget, path) self.editor_pane.file_helper.update_clean_hash(text_widget, text_widget.toPlainText()) return path def parse_command(self): """ Parse only a single clif file :return: """ if self.editor_pane.currentWidget() is None: return self.console.flush() self.parse_thread.resolve = False self.parse_thread.path = self.editor_pane.file_helper.get_path(self.editor_pane.currentWidget()) self.parse_thread.text = self.editor_pane.currentWidget().toPlainText() if not self.parse_thread.isRunning(): self.parse_thread.start() def settings_command(self): settings = gui_settings.MacleodSettings(self) settings.exec() def export_command(self): path_to_file = self.editor_pane.file_helper.get_path(self.editor_pane.currentWidget()) if path_to_file is None: current_directory = self.root_path else: current_directory = os.path.dirname(path_to_file) export = gui_tool.Export(self, current_directory) export.exec() def parse_imports_command(self): """ Parse and resolve all imports :return: """ if self.editor_pane.currentWidget() is None: return self.console.flush() self.parse_thread.resolve = True self.parse_thread.path = self.editor_pane.file_helper.get_path(self.editor_pane.currentWidget()) self.parse_thread.text = self.editor_pane.currentWidget().toPlainText() if not self.parse_thread.isRunning(): self.parse_thread.start() def check_consistency_command(self): ontology = self.ontologies.get(self.editor_pane.currentWidget(),None) if ontology is None: pass else: (return_value, fastest_reasoner) = ontology.check_consistency(resolve=True) def __on_tab_change(self): """ Event handler for tab changes Tries to load a matching ontology for the tab """ key = self.editor_pane.currentWidget() if key is None: return path = self.editor_pane.file_helper.get_path(self.editor_pane.currentWidget()) self.info_bar.flush() self.import_explorer.clear() if self.editor_pane.file_helper.is_dirty(key, key.toPlainText()): self.parse_command() else: if key in self.ontologies: self.info_bar.build_model(self.ontologies[key], path) self.info_bar.build_tree() self.import_explorer.build_tree(self.ontologies[key])
class MainWindow(QWidget): def __init__(self, parent, masternode_list, imgDir): super(QWidget, self).__init__(parent) self.parent = parent self.imgDir = imgDir self.runInThread = ThreadFuns.runInThread ###-- Masternode list self.masternode_list = masternode_list ###-- Create clients and statuses self.hwdevice = None self.hwStatus = 0 self.hwStatusMess = "Not Connected" self.rpcClient = None self.rpcConnected = False self.rpcStatusMess = "Not Connected" self.isBlockchainSynced = False ###-- Load icons & images self.loadIcons() ###-- Create main layout self.layout = QVBoxLayout() self.header = GuiHeader(self) self.initConsole() self.layout.addWidget(self.header) ###-- Create RPC Whatchdog self.rpc_watchdogThread = QThread() self.myRpcWd = RpcWatchdog(self) self.myRpcWd.moveToThread(self.rpc_watchdogThread) self.rpc_watchdogThread.started.connect(self.myRpcWd.run) self.rpc_watchdogThread.start() ###-- Create Queues and redirect stdout and stderr (eventually) self.queue = Queue() self.queue2 = Queue() sys.stdout = WriteStream(self.queue) sys.stderr = WriteStream(self.queue2) ###-- Init last logs logFile = open(log_File, 'w+') timestamp = strftime('%Y-%m-%d %H:%M:%S', gmtime(now())) log_line = '<b style="color: blue">{}</b><br>'.format('STARTING SPMT at '+ timestamp) logFile.write(log_line) logFile.close() ###-- Create the thread to update console log for stdout self.consoleLogThread = QThread() self.myWSReceiver = WriteStreamReceiver(self.queue) self.myWSReceiver.mysignal.connect(self.append_to_console) self.myWSReceiver.moveToThread(self.consoleLogThread) self.consoleLogThread.started.connect(self.myWSReceiver.run) self.consoleLogThread.start() printDbg("Console Log thread started") ###-- Create the thread to update console log for stderr self.consoleLogThread2 = QThread() self.myWSReceiver2 = WriteStreamReceiver(self.queue2) self.myWSReceiver2.mysignal.connect(self.append_to_console) self.myWSReceiver2.moveToThread(self.consoleLogThread2) self.consoleLogThread2.started.connect(self.myWSReceiver2.run) self.consoleLogThread2.start() printDbg("Console Log thread 2 started") ###-- Initialize tabs self.tabs = QTabWidget() self.t_main = TabMain(self) self.t_mnconf = TabMNConf(self) self.t_rewards = TabRewards(self) ###-- Add tabs self.tabs.addTab(self.tabMain, "Masternode Control") #self.tabs.addTab(self.tabMNConf, "MN Configuration") self.tabs.addTab(self.tabRewards, "Transfer Rewards") ###-- Connect change action self.tabs.currentChanged.connect(lambda: self.onTabChange()) ###-- Draw Tabs self.splitter = QSplitter(Qt.Vertical) ###-- Add tabs and console to Layout self.splitter.addWidget(self.tabs) self.splitter.addWidget(self.console) self.splitter.setStretchFactor(0,0) self.splitter.setStretchFactor(1,1) self.splitter.setSizes(self.parent.cache.get("splitter_sizes")) self.layout.addWidget(self.splitter) ###-- Set Layout self.setLayout(self.layout) ###-- Let's go self.mnode_to_change = None printOK("Hello! Welcome to " + parent.title) ###-- Hide console if it was previously hidden if self.parent.cache.get("console_hidden"): self.onToggleConsole() ##-- Check version self.onCheckVersion() @pyqtSlot(str) def append_to_console(self, text): self.consoleArea.moveCursor(QTextCursor.End) self.consoleArea.insertHtml(text) # update last logs logFile = open(log_File, 'a+') logFile.write(text) logFile.close() def initConsole(self): self.console = QGroupBox() self.console.setTitle("Console Log") layout = QVBoxLayout() self.btn_consoleToggle = QPushButton('Hide') self.btn_consoleToggle.setToolTip('Show/Hide console') self.btn_consoleToggle.clicked.connect(lambda: self.onToggleConsole()) consoleHeader = QHBoxLayout() consoleHeader.addWidget(self.btn_consoleToggle) self.consoleSaveButton = QPushButton('Save') self.consoleSaveButton.clicked.connect(lambda: self.onSaveConsole()) consoleHeader.addWidget(self.consoleSaveButton) self.btn_consoleClean = QPushButton('Clean') self.btn_consoleClean.setToolTip('Clean console log area') self.btn_consoleClean.clicked.connect(lambda: self.onCleanConsole()) consoleHeader.addWidget(self.btn_consoleClean) consoleHeader.addStretch(1) self.versionLabel = QLabel("--") self.versionLabel.setOpenExternalLinks(True) consoleHeader.addWidget(self.versionLabel) self.btn_checkVersion = QPushButton("Check SPMT version") self.btn_checkVersion.setToolTip("Check latest stable release of SPMT") self.btn_checkVersion.clicked.connect(lambda: self.onCheckVersion()) consoleHeader.addWidget(self.btn_checkVersion) layout.addLayout(consoleHeader) self.consoleArea = QTextEdit() almostBlack = QColor(40, 40, 40) palette = QPalette() palette.setColor(QPalette.Base, almostBlack) green = QColor(0, 255, 0) palette.setColor(QPalette.Text, green) self.consoleArea.setPalette(palette) layout.addWidget(self.consoleArea) self.console.setLayout(layout) def loadIcons(self): # Load Icons self.ledPurpleH_icon = QPixmap(os.path.join(self.imgDir, 'icon_purpleLedH.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledGrayH_icon = QPixmap(os.path.join(self.imgDir, 'icon_grayLedH.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledHalfPurpleH_icon = QPixmap(os.path.join(self.imgDir, 'icon_halfPurpleLedH.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledRedV_icon = QPixmap(os.path.join(self.imgDir, 'icon_redLedV.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledGrayV_icon = QPixmap(os.path.join(self.imgDir, 'icon_grayLedV.png')).scaledToHeight(17, Qt.SmoothTransformation) self.ledGreenV_icon = QPixmap(os.path.join(self.imgDir, 'icon_greenLedV.png')).scaledToHeight(17, Qt.SmoothTransformation) def myPopUp(self, messType, messTitle, messText, defaultButton=QMessageBox.No): mess = QMessageBox(messType, messTitle, messText, defaultButton, parent=self) mess.setStandardButtons(QMessageBox.Yes | QMessageBox.No) mess.setDefaultButton(defaultButton) return mess.exec_() def myPopUp2(self, messType, messTitle, messText, singleButton=QMessageBox.Ok): mess = QMessageBox(messType, messTitle, messText, singleButton, parent=self) mess.setStandardButtons(singleButton | singleButton) return mess.exec_() @pyqtSlot() def onCheckHw(self): printDbg("Checking for HW device...") self.updateHWstatus(None) self.showHWstatus() @pyqtSlot() def onCheckRpc(self): printDbg("Checking RPC server...") self.runInThread(self.updateRPCstatus, (), self.showRPCstatus) @pyqtSlot() def onCheckVersion(self): printDbg("Checking SPMT version...") self.versionLabel.setText("--") self.runInThread(self.checkVersion, (), self.updateVersion) def checkVersion(self, ctrl): local_version = self.parent.version['number'].split('.') remote_version = getRemoteSPMTversion().split('.') if (remote_version[0] > local_version[0]) or \ (remote_version[0] == local_version[0] and remote_version[1] > local_version[1]) or \ (remote_version[0] == local_version[0] and remote_version[1] == local_version[1] and remote_version[2] > local_version[2]): self.versionMess = '<b style="color:red">New Version Available:</b> %s.%s.%s ' % (remote_version[0], remote_version[1], remote_version[2]) self.versionMess += '(<a href="https://github.com/PIVX-Project/PIVX-SPMT/releases/">download</a>)' else: self.versionMess = "You have the latest version of SPMT" def updateVersion(self): if self.versionMess is not None: self.versionLabel.setText(self.versionMess) @pyqtSlot() def onCleanConsole(self): self.consoleArea.clear() @pyqtSlot() def onSaveConsole(self): timestamp = strftime('%Y-%m-%d_%H-%M-%S', gmtime(now())) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName(self,"Save Logs to file","SPMT_Logs_%s.txt" % timestamp,"All Files (*);; Text Files (*.txt)", options=options) try: if fileName: printOK("Saving logs to %s" % fileName) log_file = open(fileName, 'w+') log_text = self.consoleArea.toPlainText() log_file.write(log_text) log_file.close() except Exception as e: err_msg = "error writing Log file" printException(getCallerName(), getFunctionName(), err_msg, e.args) @pyqtSlot() def onTabChange(self): # reload (and re-sort)masternode list in tabs if self.tabs.currentWidget() == self.tabRewards: # reload last used address self.tabRewards.destinationLine.setText(self.parent.cache.get("lastAddress")) # get new order mnOrder = {} mnList = self.tabMain.myList for i in range(mnList.count()): mnName = mnList.itemWidget(mnList.item(i)).alias mnOrder[mnName] = i self.parent.cache['mnList_order'] = mnOrder # Sort masternode list (by alias if no previous order set) if self.parent.cache.get('mnList_order') != {}: self.masternode_list.sort(key=self.parent.extract_order) self.t_rewards.loadMnSelect() self.t_rewards.selectedRewards = None @pyqtSlot() def onToggleConsole(self): if self.btn_consoleToggle.text() == 'Hide': self.btn_consoleToggle.setText('Show') self.consoleArea.hide() self.console.setMinimumHeight(70) self.console.setMaximumHeight(70) else: self.console.setMinimumHeight(70) self.console.setMaximumHeight(starting_height) self.btn_consoleToggle.setText('Hide') self.consoleArea.show() def showHWstatus(self): self.updateHWleds() self.myPopUp2(QMessageBox.Information, 'SPMT - hw check', "%s" % self.hwStatusMess, QMessageBox.Ok) def showRPCstatus(self): self.updateRPCled() self.myPopUp2(QMessageBox.Information, 'SPMT - rpc check', "%s" % self.rpcStatusMess, QMessageBox.Ok) def updateHWleds(self): if self.hwStatus == 1: self.header.hwLed.setPixmap(self.ledHalfPurpleH_icon) elif self.hwStatus == 2: self.header.hwLed.setPixmap(self.ledPurpleH_icon) else: self.header.hwLed.setPixmap(self.ledGrayH_icon) self.header.hwLed.setToolTip(self.hwStatusMess) def updateHWstatus(self, ctrl): if self.hwdevice is None: self.hwdevice = HWdevice() device = self.hwdevice statusCode = device.getStatusCode() statusMess = device.getStatusMess(statusCode) printDbg("code: %s - mess: %s" % (statusCode, statusMess)) if statusCode != 2: try: if getattr(self.hwdevice, 'dongle', None) is not None: self.hwdevice.dongle.close() self.hwdevice.initDevice() device = self.hwdevice statusCode = device.getStatusCode() statusMess = device.getStatusMess(statusCode) except Exception as e: err_msg = "error in checkHw" printException(getCallerName(), getFunctionName(), err_msg, e.args) self.hwStatus = statusCode self.hwStatusMess = statusMess def updateLastBlockLabel(self): text = '--' if self.rpcLastBlock == 1: text = "Loading block index..." elif self.rpcLastBlock > 0 and self.rpcConnected: text = str(self.rpcLastBlock) text += " (" if not self.isBlockchainSynced: text += "Synchronizing" else: text += "Synced" text += ")" self.header.lastBlockLabel.setText(text) def updateRPCled(self): if self.rpcConnected: self.header.rpcLed.setPixmap(self.ledPurpleH_icon) else: if self.rpcLastBlock == 1: self.header.rpcLed.setPixmap(self.ledHalfPurpleH_icon) else: self.header.rpcLed.setPixmap(self.ledGrayH_icon) self.header.rpcLed.setToolTip(self.rpcStatusMess) self.updateLastBlockLabel() def updateRPCstatus(self, ctrl): if self.rpcClient is None: try: self.rpcClient = RpcClient() except Exception as e: print(e) status, lastBlock = self.rpcClient.getStatus() statusMess = self.rpcClient.getStatusMess(status) if not status and lastBlock==0: try: self.rpcClient = RpcClient() status, lastBlock = self.rpcClient.getStatus() statusMess = self.rpcClient.getStatusMess(status) except Exception as e: err_msg = "error in checkRpc" printException(getCallerName(), getFunctionName(), err_msg, e) elif lastBlock == 1: statusMess = "PIVX wallet is connected but still synchronizing / verifying blocks" self.rpcConnected = status self.rpcLastBlock = lastBlock self.rpcStatusMess = statusMess self.isBlockchainSynced = self.rpcClient.isBlockchainSynced()
class LibraryCodesDialog(SizePersistedDialog): #----------------------------------------------------------------------------------------- def __init__(self, gui, icon, guidb, plugin_path, ui_exit, action_type): parent = gui unique_pref_name = 'library_codes:gui_parameters_dialog' SizePersistedDialog.__init__(self, parent, unique_pref_name) #----------------------------------------------------- self.gui = gui self.guidb = guidb #----------------------------------------------------- self.icon = icon #----------------------------------------------------- self.plugin_path = plugin_path #----------------------------------------------------- self.ui_exit = ui_exit #----------------------------------------------------- self.action_type = action_type #----------------------------------------------------- self.myparentprefs = collections.OrderedDict([]) prefsdefaults = deepcopy(prefs.defaults) tmp_list = [] #~ for k,v in prefs.iteritems(): for k, v in iteritems(prefs): tmp_list.append(k) #END FOR #~ for k,v in prefsdefaults.iteritems(): for k, v in iteritems(prefsdefaults): tmp_list.append(k) #END FOR tmp_set = set(tmp_list) tmp_list = list(tmp_set) #no duplicates del tmp_set tmp_list.sort() for k in tmp_list: self.myparentprefs[k] = " " # ordered by key #END FOR del tmp_list #~ for k,v in prefs.iteritems(): for k, v in iteritems(prefs): self.myparentprefs[k] = v #END FOR #~ for k,v in prefsdefaults.iteritems(): for k, v in iteritems(prefsdefaults): if not k in prefs: prefs[k] = v else: if not prefs[k] > " ": prefs[k] = v if not k in self.myparentprefs: self.myparentprefs[k] = v else: if not self.myparentprefs[k] > " ": self.myparentprefs[k] = v #END FOR #~ for k,v in self.myparentprefs.iteritems(): for k, v in iteritems(self.myparentprefs): prefs[k] = v #END FOR prefs #prefs now synched #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.param_dict = collections.OrderedDict([]) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.init_tooltips_for_parent() self.setToolTip(self.parent_tooltip) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- # Tab 0: LibraryCodesTab #----------------------------------------------------- #----------------------------------------------------- from calibre_plugins.library_codes.library_codes_dialog import LibraryCodesTab self.LibraryCodesTab = LibraryCodesTab(self.gui, self.guidb, self.myparentprefs, self.param_dict, self.ui_exit, self.save_dialog_geometry) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- # Parent LibraryCodesDialog #----------------------------------------------------- font = QFont() font.setBold(False) font.setPointSize(10) tablabel_font = QFont() tablabel_font.setBold(False) tablabel_font.setPointSize(10) #----------------------------------------------------- self.setWindowTitle('Library Codes') self.setWindowIcon(icon) #----------------------------------------------------- self.layout_frame = QVBoxLayout() self.layout_frame.setAlignment(Qt.AlignLeft) self.setLayout(self.layout_frame) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- n_width = 600 self.LCtabWidget = QTabWidget() self.LCtabWidget.setMaximumWidth(n_width) self.LCtabWidget.setFont(tablabel_font) self.LCtabWidget.addTab( self.LibraryCodesTab, "Derivation from ISBN or ISSN or Author/Title") self.LibraryCodesTab.setToolTip( "<p style='white-space:wrap'>Derive Library Codes DDC and/or LCC and/or OCLC-OWI from ISBN or ISSN or Author/Title. Visit: http://classify.oclc.org/classify2/ " ) self.LibraryCodesTab.setMaximumWidth(n_width) #----------------------------------------------------- self.layout_frame.addWidget(self.LCtabWidget) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.resize_dialog() # inherited from SizePersistedDialog #----------------------------------------------------- self.LCtabWidget.setCurrentIndex(0) #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- # OTHER #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- def init_tooltips_for_parent(self): self.setStyleSheet( "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }" ) self.parent_tooltip = "<p style='white-space:wrap'>" + \ '''
class FSMainWindow(QMainWindow, FSBase): """ Main Window """ def __init__(self): QMainWindow.__init__(self) FSBase.__init__(self) self.progressbar = None self.tab_widget = None self.file_tree_widget = None self.file_tree_model = None self.file_scanner_thread = None self.movie_table_widget = None self.movie_table_model = None self.movie_scanner_thread = None self.build_ui() self.build_content() def build_ui(self): self.setWindowTitle("Filesystem Analyzer") settings_action = QAction(QIcon("res/settings.svg"), "Settings", self) settings_action.setShortcut("Ctrl+S") settings_action.triggered.connect(self.settings_action_handler) set_path_action = QAction(QIcon("res/directory-submission-symbol.svg"), "Set path", self) set_path_action.setShortcut("Ctrl+N") set_path_action.triggered.connect(self.set_path_action_handler) refresh_action = QAction(QIcon("res/reload.svg"), "Refresh", self) refresh_action.setShortcut("Ctrl+R") refresh_action.triggered.connect(self.refresh_action_handler) menu_bar = self.menuBar() action_menu = menu_bar.addMenu("&Action") action_menu.addAction(settings_action) action_menu.addAction(set_path_action) action_menu.addAction(refresh_action) toolbar = self.addToolBar("Exit") toolbar.addAction(settings_action) toolbar.addAction(set_path_action) toolbar.addAction(refresh_action) self.progressbar = QProgressBar(self) self.progressbar.hide() self.progressbar.setRange(0, 100) cancel_button = QPushButton(QIcon("res/cancel-button.svg"), "Cancel", self) cancel_button.clicked.connect(self.set_path_cancel) self.statusBar().addPermanentWidget(self.progressbar) self.statusBar().addPermanentWidget(cancel_button) self.tab_widget = QTabWidget(self) self.file_tree_widget = FSFileTreeWidget() self.file_tree_model = FSFileTreeModel() self.file_tree_widget.setModel(self.file_tree_model) self.movie_table_widget = FSMovieTableWidget() self.movie_table_widget.setShowGrid(False) self.movie_table_widget.setAlternatingRowColors(True) self.movie_table_widget.verticalHeader().setVisible(False) self.movie_table_widget.setSelectionBehavior(QTableView.SelectRows) self.movie_table_model = FSMovieTableModel() self.movie_table_widget.setModel(self.movie_table_model) self.tab_widget.addTab(self.file_tree_widget, "General") self.tab_widget.addTab(self.movie_table_widget, "Movies") self.setCentralWidget(self.tab_widget) last_tab_index = self.app.load_setting("last_tab_index") if last_tab_index: self.tab_widget.setCurrentIndex(int(last_tab_index)) self.tab_widget.currentChanged.connect(self.tab_widget_changed) self.show() def settings_action_handler(self): settings_dialog = FSSettingsDialog(self) settings_dialog.show() def build_content(self): path = self.app.load_setting("path") self.set_path(path) def set_path_action_handler(self): file_dialog = QFileDialog() path = file_dialog.getExistingDirectory( self, "Select directory", "/home/", QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) self.logger.info("Path set to %s", path) self.set_path(path) self.app.save_setting("path", path) def set_path(self, path): self.progressbar.show() if self.tab_widget.currentIndex() == 0: self.file_tree_model.reset_root() file_scanner_context = FSFileScannerContext( path, self.file_tree_model.root) self.file_scanner_thread = FSFileScannerTask( self, file_scanner_context) self.file_scanner_thread.notifyProgress.connect( self.update_progress) self.file_scanner_thread.notifyFinish.connect( self.set_path_action_finish) self.file_scanner_thread.notifyError.connect(self.report_error) self.file_scanner_thread.start() else: movie_extensions = self.app.load_setting( self.app.ExtensionSettings[FSExtensionType.TYPE_MOVIE]) movie_extensions_list = [ movie_extension.strip() for movie_extension in movie_extensions.split(",") ] movie_scanner_context = FSMovieScannerContext( path, self.movie_table_model.movies, movie_extensions_list) self.movie_scanner_thread = FSMovieScannerTask( self, movie_scanner_context) self.movie_scanner_thread.notifyProgress.connect( self.update_progress) self.movie_scanner_thread.notifyFinish.connect( self.set_path_action_finish) self.movie_scanner_thread.notifyError.connect(self.report_error) self.movie_scanner_thread.start() def set_path_action_finish(self): self.progressbar.hide() if self.tab_widget.currentIndex() == 0: self.file_tree_model.reset_model() self.file_tree_widget.setColumnWidth(0, 250) else: self.movie_table_model.reset_model() self.movie_table_widget.resizeColumnsToContents() def set_path_cancel(self): self.file_scanner_thread.stop_flag = True def refresh_action_handler(self): path = self.app.load_setting("path") self.set_path(path) def update_progress(self, value): self.progressbar.setValue(value) def report_error(self, error): print(error) def tab_widget_changed(self, index): self.app.save_setting("last_tab_index", index)
class MainWin(QMainWindow): """ It's a window, stores a TabWidget """ def __init__(self, parent=None): super(MainWin, self).__init__(parent) self.setWindowTitle("Eilat Browser") # gc.set_debug(gc.DEBUG_LEAK) self.last_closed = None self.tab_widget = QTabWidget(self) self.tab_widget.setTabBar(MidClickTabBar(self)) self.tab_widget.tabBar().setMovable(True) self.tab_widget.setTabsClosable(True) # the right side of the tab already has the space for # a non-shown close button self.tab_widget.setStyleSheet( 'QTabBar::tab {padding-top: 0px; padding-bottom: 0px; ' 'padding-left: 0.3em;} ' 'QTabBar::tab:selected {color: #00f;}') # tabCloseRequested carries int (index of a tab) self.tab_widget.tabCloseRequested.connect(self.del_tab) self.setCentralWidget(self.tab_widget) self.tooltip = NotifyLabel(parent=self) def restore_last_closed(): """ One-use callback for QShortcut. Opens a fresh new tab with the url address of the last tab closed """ if self.last_closed is not None: url = self.last_closed self.add_tab(url) self.last_closed = None def dump_gc(): """ prints sizes for large memory collectable objects """ objs = gc.get_objects() pairs = [(str(k)[:80], type(k).__name__, sys.getsizeof(k)) for k in objs if sys.getsizeof(k) > 1024*4*5] for pair in pairs: print(pair) def reload_disk_init(): """ transfer options.yaml and the css directory to global maps """ load_options() load_css() notify("reloaded disk config") set_shortcuts([ ("F9", self, dump_gc), # reload configuration ("Ctrl+Shift+R", self, reload_disk_init), # new tabs ("Ctrl+T", self, self.add_tab), ("Ctrl+Shift+T", self, partial(self.add_tab, scripting=True)), ("Y", self, self.new_tab_from_clipboard), # movement ("M", self, self.inc_tab), ("N", self, partial(self.inc_tab, -1)), ("Ctrl+PgUp", self, partial(self.inc_tab, -1)), ("Ctrl+PgDown", self, self.inc_tab), # destroy/undestroy ("U", self, restore_last_closed), ("Ctrl+W", self, self.del_tab), ("Ctrl+Q", self, self.finalize) ]) def new_tab_from_clipboard(self): """ One-use callback for QShortcut. Reads the content of the PRIMARY clipboard and navigates to it on a new tab. """ url = clipboard() if url is not None: self.add_tab(url) # aux. action (en register_actions) def inc_tab(self, incby=1): """ Takes the current tab index, modifies wrapping around, and sets as current. Afterwards the active tab has focus on its webkit area. """ if self.tab_widget.count() < 2: return idx = self.tab_widget.currentIndex() idx += incby if idx < 0: idx = self.tab_widget.count()-1 elif idx >= self.tab_widget.count(): idx = 0 self.tab_widget.setCurrentIndex(idx) self.tab_widget.currentWidget().webkit.setFocus() def finalize(self): """ Just doing self.close() doesn't clean up; for example, closing when the address bar popup is visible doesn't close the popup, and leaves the window hidden and unclosable (except e.g. for KILL 15) Makes a hard app close through os._exit to prevent garbage collection; cleanup has typically done more harm than good. Any state that we may want to preserve we should do ourselves (e.g. cookies through the NAMs) """ idx = self.tab_widget.currentIndex() self.tab_widget.widget(idx).deleteLater() self.tab_widget.removeTab(idx) close_managers() # also does an os._exit # action y connect en llamada en constructor def del_tab(self, idx=None): """ Closes a tab. If 'idx' is set, it was called by a tabCloseRequested signal (maybe mid click). If not, it was called by a keyboard action and closes the currently active tab. Afterwards the active tab has focus on its webkit area. It closes the window when deleting the last active tab. """ if idx is None: idx = self.tab_widget.currentIndex() self.tab_widget.widget(idx).webkit.stop() self.last_closed = self.tab_widget.widget(idx).webkit.url() self.tab_widget.widget(idx).deleteLater() self.tab_widget.removeTab(idx) if len(self.tab_widget) == 0: close_managers() # also does an os.__exit else: self.tab_widget.currentWidget().webkit.setFocus() # action (en register_actions) # only way to create a new tab # called externally in eilat.py to create the first tab def add_tab(self, url=None, scripting=False): """ Creates a new tab, either empty or navegating to the url. Sets itself as the active tab. If navegating to an url it gives focus to the webkit area. Otherwise, the address bar is focused. """ tab = WebTab(parent=self.tab_widget) self.tab_widget.addTab(tab, tab.current['title']) self.tab_widget.setCurrentWidget(tab) tab_idx = self.tab_widget.indexOf(tab) self.tab_widget.tabBar().tabButton(tab_idx, 1).hide() # 1: right align if scripting: tab.toggle_script() if url is not None: qurl = fix_url(url) tab.webkit.navigate(qurl) else: tab.address_bar.setFocus()
class MainWindow(QWidget): def __init__(self, parent, imgDir): super(QWidget, self).__init__(parent) self.parent = parent self.imgDir = imgDir self.runInThread = ThreadFuns.runInThread ###-- Create clients and statuses self.hwdevice = None self.hwStatus = 0 self.hwStatusMess = "Not Connected" self.rpcClient = None self.rpcConnected = False self.rpcStatusMess = "Not Connected" ###-- Load icons & images self.loadIcons() ###-- Create main layout self.layout = QVBoxLayout() self.header = GuiHeader(self) self.initConsole() self.layout.addWidget(self.header) ###-- Create RPC Whatchdog self.rpc_watchdogThread = QThread() self.myRpcWd = RpcWatchdog(self) self.myRpcWd.moveToThread(self.rpc_watchdogThread) self.rpc_watchdogThread.started.connect(self.myRpcWd.run) self.rpc_watchdogThread.start() ###-- Create Queues and redirect stdout and stderr (eventually) self.queue = Queue() self.queue2 = Queue() sys.stdout = WriteStream(self.queue) sys.stderr = WriteStream(self.queue2) ###-- Init last logs logFile = open(log_File, 'w+') timestamp = strftime('%Y-%m-%d %H:%M:%S', gmtime(now())) log_line = '<b style="color: blue">{}</b><br>'.format( 'STARTING PET4L at ' + timestamp) logFile.write(log_line) logFile.close() ###-- Create the thread to update console log for stdout self.consoleLogThread = QThread() self.myWSReceiver = WriteStreamReceiver(self.queue) self.myWSReceiver.mysignal.connect(self.append_to_console) self.myWSReceiver.moveToThread(self.consoleLogThread) self.consoleLogThread.started.connect(self.myWSReceiver.run) self.consoleLogThread.start() printDbg("Console Log thread started") ###-- Create the thread to update console log for stderr self.consoleLogThread2 = QThread() self.myWSReceiver2 = WriteStreamReceiver(self.queue2) self.myWSReceiver2.mysignal.connect(self.append_to_console) self.myWSReceiver2.moveToThread(self.consoleLogThread2) self.consoleLogThread2.started.connect(self.myWSReceiver2.run) self.consoleLogThread2.start() printDbg("Console Log thread 2 started") ###-- Initialize tabs self.tabs = QTabWidget() self.t_rewards = TabRewards(self) ###-- Add tabs self.tabs.addTab(self.tabRewards, "Spend from Ledger") ###-- Draw Tabs self.splitter = QSplitter(Qt.Vertical) ###-- Add tabs and console to Layout self.splitter.addWidget(self.tabs) self.splitter.addWidget(self.console) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter.setSizes([2, 1]) self.layout.addWidget(self.splitter) ###-- Set Layout self.setLayout(self.layout) ###-- Let's go self.mnode_to_change = None printOK("Hello! Welcome to " + parent.title) @pyqtSlot(str) def append_to_console(self, text): self.consoleArea.moveCursor(QTextCursor.End) self.consoleArea.insertHtml(text) # update last logs logFile = open(log_File, 'a+') logFile.write(text) logFile.close() def initConsole(self): self.console = QGroupBox() self.console.setTitle("Console Log") layout = QVBoxLayout() self.btn_consoleToggle = QPushButton('Hide') self.btn_consoleToggle.setToolTip('Show/Hide console') self.btn_consoleToggle.clicked.connect(lambda: self.onToggleConsole()) consoleHeader = QHBoxLayout() consoleHeader.addWidget(self.btn_consoleToggle) self.consoleSaveButton = QPushButton('Save') self.consoleSaveButton.clicked.connect(lambda: self.onSaveConsole()) consoleHeader.addWidget(self.consoleSaveButton) self.btn_consoleClean = QPushButton('Clean') self.btn_consoleClean.setToolTip('Clean console log area') self.btn_consoleClean.clicked.connect(lambda: self.onCleanConsole()) consoleHeader.addWidget(self.btn_consoleClean) consoleHeader.addStretch(1) layout.addLayout(consoleHeader) self.consoleArea = QTextEdit() almostBlack = QColor(40, 40, 40) palette = QPalette() palette.setColor(QPalette.Base, almostBlack) green = QColor(0, 255, 0) palette.setColor(QPalette.Text, green) self.consoleArea.setPalette(palette) layout.addWidget(self.consoleArea) self.console.setLayout(layout) def loadIcons(self): # Load Icons self.ledPurpleH_icon = QPixmap( os.path.join(self.imgDir, 'icon_purpleLedH.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledGrayH_icon = QPixmap( os.path.join(self.imgDir, 'icon_grayLedH.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledHalfPurpleH_icon = QPixmap( os.path.join(self.imgDir, 'icon_halfPurpleLedH.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledRedV_icon = QPixmap( os.path.join(self.imgDir, 'icon_redLedV.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledGrayV_icon = QPixmap( os.path.join(self.imgDir, 'icon_grayLedV.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledGreenV_icon = QPixmap( os.path.join(self.imgDir, 'icon_greenLedV.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledgerImg = QPixmap(os.path.join(self.imgDir, 'ledger.png')) def myPopUp(self, messType, messTitle, messText, defaultButton=QMessageBox.No): mess = QMessageBox(messType, messTitle, messText, defaultButton, parent=self) mess.setStandardButtons(QMessageBox.Yes | QMessageBox.No) mess.setDefaultButton(defaultButton) return mess.exec_() def myPopUp2(self, messType, messTitle, messText, singleButton=QMessageBox.Ok): mess = QMessageBox(messType, messTitle, messText, singleButton, parent=self) mess.setStandardButtons(singleButton | singleButton) return mess.exec_() @pyqtSlot() def onCheckHw(self): printDbg("Checking for HW device...") self.runInThread(self.updateHWstatus, (), self.showHWstatus) @pyqtSlot() def onCheckRpc(self): printDbg("Checking RPC server...") self.runInThread(self.updateRPCstatus, (), self.showRPCstatus) @pyqtSlot() def onCleanConsole(self): self.consoleArea.clear() @pyqtSlot() def onSaveConsole(self): timestamp = strftime('%Y-%m-%d_%H-%M-%S', gmtime(now())) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName( self, "Save Logs to file", "PET4L_Logs_%s.txt" % timestamp, "All Files (*);; Text Files (*.txt)", options=options) try: if fileName: printOK("Saving logs to %s" % fileName) log_file = open(fileName, 'w+') log_text = self.consoleArea.toPlainText() log_file.write(log_text) log_file.close() except Exception as e: err_msg = "error writing Log file" printException(getCallerName(), getFunctionName(), err_msg, e.args) @pyqtSlot() def onToggleConsole(self): if self.btn_consoleToggle.text() == 'Hide': self.btn_consoleToggle.setText('Show') self.consoleArea.hide() self.previousH = self.splitter.sizes()[1] self.console.setMaximumHeight(70) else: self.console.setMinimumHeight(self.previousH) self.console.setMaximumHeight(starting_height) self.btn_consoleToggle.setText('Hide') self.consoleArea.show() def showHWstatus(self): self.updateHWleds() self.myPopUp2(QMessageBox.Information, 'PET4L - hw check', "STATUS: %s" % self.hwStatusMess, QMessageBox.Ok) def showRPCstatus(self): self.updateRPCled() self.myPopUp2(QMessageBox.Information, 'PET4L - rpc check', "STATUS: %s" % self.rpcStatusMess, QMessageBox.Ok) def updateHWleds(self): if self.hwStatus == 1: self.header.hwLed.setPixmap(self.ledHalfPurpleH_icon) elif self.hwStatus == 2: self.header.hwLed.setPixmap(self.ledPurpleH_icon) else: self.header.hwLed.setPixmap(self.ledGrayH_icon) self.header.hwLed.setToolTip(self.hwStatusMess) def updateHWstatus(self, ctrl): if self.hwdevice is None: self.hwdevice = HWdevice() device = self.hwdevice statusCode = device.getStatusCode() statusMess = device.getStatusMess(statusCode) printDbg("code: %s - mess: %s" % (statusCode, statusMess)) if statusCode != 2: try: if getattr(self.hwdevice, 'dongle', None) is not None: self.hwdevice.dongle.close() self.hwdevice.initDevice() device = self.hwdevice statusCode = device.getStatusCode() statusMess = device.getStatusMess(statusCode) except Exception as e: err_msg = "error in checkHw" printException(getCallerName(), getFunctionName(), err_msg, e.args) self.hwStatus = statusCode self.hwStatusMess = statusMess def updateLastBlockLabel(self): text = '--' if self.rpcLastBlock == 1: text = "Loading block index..." elif self.rpcLastBlock > 0 and self.rpcConnected: text = str(self.rpcLastBlock) self.header.lastBlockLabel.setText(text) def updateRPCled(self): if self.rpcConnected: self.header.rpcLed.setPixmap(self.ledPurpleH_icon) else: if self.rpcLastBlock == 1: self.header.rpcLed.setPixmap(self.ledHalfPurpleH_icon) else: self.header.rpcLed.setPixmap(self.ledGrayH_icon) self.header.rpcLed.setToolTip(self.rpcStatusMess) self.updateLastBlockLabel() def updateRPCstatus(self, ctrl): if self.rpcClient is None: try: self.rpcClient = RpcClient() except Exception as e: print(e) status, lastBlock = self.rpcClient.getStatus() statusMess = self.rpcClient.getStatusMess(status) if not status and lastBlock == 0: try: self.rpcClient = RpcClient() status, lastBlock = self.rpcClient.getStatus() statusMess = self.rpcClient.getStatusMess(status) except Exception as e: err_msg = "error in checkRpc" printException(getCallerName(), getFunctionName(), err_msg, e) elif lastBlock == 1: statusMess = "PIVX wallet is connected but still synchronizing / verifying blocks" self.rpcConnected = status self.rpcLastBlock = lastBlock self.rpcStatusMess = statusMess
class VideoTracker(QMainWindow): image_fmt = "image{:04d}.png" # to format image names icone_dir = "icones/" # directory of icones cur_dir = os.getcwd() # working directory image_dir = os.path.join(os.getcwd(), "Images") # directory of images csv_dir = os.path.join(cur_dir, "CSV") # directory of CSV files def __init__(self): if not os.path.isdir(VideoTracker.icone_dir) : print("Répertoire des icônes non trouvé.") if not os.path.isdir(VideoTracker.image_dir) : msg = "Répertoire des images créés :\n\t'{}'" print(msg.format(VideoTracker.image_dir)) os.mkdir(VideoTracker.image_dir) if not os.path.isdir(VideoTracker.csv_dir) : msg = "Répertoire CSV créé :\n\t'{}'" print(msg.format(VideoTracker.csv_dir)) os.mkdir(VideoTracker.csv_dir) # Simple syntaxe to call the base class contsructor super(VideoTracker, self).__init__() # # *** Bonnes pratiques *** # Définir dans le constructeur les données persistantes en tant qu'attributs, # et si on ne connaît pas leur valeur à ce endroit on utilise None: # self.csv_dir = os.path.join(VideoTracker.cur_dir, "CSV") # self.flags : dictionnary of flags : # debug -> display or not various informations # displayInfo -> display or non information windows # autoClearTraj -> automatically clear trajectory plot before a new plot # drawTargetSelection -> draw/not draw the selected color area self.flags = {"debug": False, "displayInfo": True, "autoClearTraj": True, "drawTargetSelection": True} self.csv_dataFrame = None # Data self.__target_pos = None # target positions x, y self.__target_veloc = None # target velocities x, y self.target_RGB = None # color plor drawing plots self.unit_dict = None self.__initUI() # User Interface initialisation self.show() # Display this window @property def target_pos(self): return self.__target_pos @target_pos.setter def target_pos(self, data): if not isinstance(data, np.ndarray): raise Exception("target_pos should be a numpy.ndarray object !") self.__target_pos = data @property def target_veloc(self): return self.__target_veloc @target_veloc.setter def target_veloc(self, data): if not isinstance(data, np.ndarray): raise Exception("target_pos should be a numpy.ndarray object !") self.__target_veloc = data def center(self): '''To center the current window in the current display''' desktop = QApplication.desktop() n = desktop.screenNumber(self.cursor().pos()) screen_center = desktop.screenGeometry(n).center() geo_window = self.frameGeometry() geo_window.moveCenter(screen_center) self.move(geo_window.topLeft()) def __initUI(self): self.resize(850, 650) self.center() self.setWindowTitle('Application de tracking vidéo') self.statusBar() # status bar at the bottom of the window # QTabWidget of the application showing the 5 tabs self.tabs = QTabWidget() # tab1: display video images & video metadata self.imageTab = ImageDisplay(self) # tab2: plot (y(t), x(t)) self.onePlot = OnePlot(self) # tab3: plot curves x(t) and y(t) self.twoPlots_xy = TwoPlots(self, "position") # tab4: plot curves Vx(t) and Vy(t) self.twoPlots_VxVy = TwoPlots(self, "velocity") # tab5: plot of f(t)=f(x(t), y(t), t) self.functionOfXY = FunctionPlot(self) # tab6: IPython shell self.pythonConsole = PythonConsole(self) self.tabs.addTab(self.imageTab,"Visualisation images") self.tabs.addTab(self.onePlot,"Trajectoire") self.tabs.addTab(self.twoPlots_xy,"Positions") self.tabs.addTab(self.twoPlots_VxVy,"Vitesses") self.tabs.addTab(self.functionOfXY,"Outil de tracé") self.tabs.addTab(self.pythonConsole,"IPython") self.setCentralWidget(self.tabs) # Menu(s) self.menubar = self.menuBar() if platform.uname().system.startswith('Darw') : # Mac OS specificity: self.menubar.setNativeMenuBar(False) ###### Menu 'Files' fileMenu = self.menubar.addMenu('&Fichier') ### Open images directory: qa = QAction(QIcon(VideoTracker.icone_dir+'/open.png'), 'Ouvrir dossier images', self) qa.setShortcut('Ctrl+D') qa.setStatusTip("Ouvre un dossier contenant déjà "+\ "les images d'une vidéo") # connexion avec la méthode 'load_images_from_directory' qui est # définie dans l'objet 'imageTab' : qa.triggered.connect(self.imageTab.load_images_from_directory) fileMenu.addAction(qa) ### Load a video file : qa = QAction(QIcon(VideoTracker.icone_dir+'/open.png'), "Charger un fichier vidéo", self) qa.setShortcut('Ctrl+O') qa.setStatusTip('Ouvre un fihier vidéo et le '+\ 'découpe en images successives...') # connexion avec la méthode 'open_video' qui est définie dans # l'objet 'imageTab' : qa.triggered.connect(self.imageTab.open_video) fileMenu.addAction(qa) ### Export CSV: qa = QAction(QIcon(VideoTracker.icone_dir+'/exportCSV.png'),\ 'Export CSV', self) qa.setStatusTip("Exporte les positions extraites de la vidéo dans un"+\ "fichier CSV.") qa.triggered.connect(self.ExportCSV) fileMenu.addAction(qa) ### Import CSV: qa = QAction(QIcon(VideoTracker.icone_dir+'/importCSV.png'),\ 'Import CSV', self) qa.setStatusTip("Importe les données depuis un fichier CSV forgé par VideoTracker.") qa.triggered.connect(self.ImportCSV) fileMenu.addAction(qa) ### Quit : qa = QAction(QIcon(VideoTracker.icone_dir+'/exit.png'),\ 'Quitter', self) qa.setShortcut('Ctrl+Q') qa.setStatusTip("Quitter l'application") qa.triggered.connect(self.close) fileMenu.addAction(qa) ###### Le menu 'Options' optionMenu = self.menubar.addMenu('&Options') ### Display info box windows: qa = QAction('Afficher boîtes info', self, checkable=True) text = 'Afficher ou non les boîtes de dialogue d\'information' qa.setStatusTip(text)# message in the status bar qa.setChecked(True) qa.triggered.connect(lambda e: self.set_flag("displayInfo",e)) optionMenu.addAction(qa) ### Verbose mode : qa = QAction('Mode verbeux', self, checkable=True) text = 'Afficher ou non des informations dans le shell Python' qa.setStatusTip(text) # message in the status bar qa.setChecked(True) qa.triggered.connect(lambda e: self.set_flag("debug", e)) optionMenu.addAction(qa) ### Clear trajectory plots before a new plot: qa = QAction('Effacement trajectoire avant tracé', self, checkable=True) text = 'Effacer automatiquement le tracé des onglets <Trajectoires> et ' text += '<X(t) et Y(t)> un nouveau tracé ?' qa.setStatusTip(text) # message in the status bar qa.setChecked(True) qa.triggered.connect(lambda e: self.set_flag("autoClearTraj", e) ) optionMenu.addAction(qa) ### draw/not draw the selected color area qa = QAction('Dessiner la sélection couleur de la cible', self, checkable=True) text = 'Dessine la zone sélectionnée pour la couleur de la cible' qa.setStatusTip(text) # message in the status bar qa.setChecked(True) qa.triggered.connect(lambda e: self.set_flag("drawTargetSelection", e)) optionMenu.addAction(qa) def set_flag(self, flag, state): if self.flags["debug"]: print("{} -> {}".format(flag, state)) self.flags[flag] = state if self.flags["debug"]: print("set_flag: {} -> {}".format(flag, self.flags[flag])) def clearPlots(self): self.onePlot.ClearAxes() self.twoPlots_xy.ClearAxes() self.twoPlots_VxVy.ClearAxes() self.functionOfXY.ClearAxes() def ImportCSV(self): '''Import Data from CSV file.''' # Lance un sélecteur de fichier pour choisir le fichier à écrire.''' fname = QFileDialog.getOpenFileName(self, 'Choisir un nom de fichier CSV à importer', VideoTracker.csv_dir, 'Fichier CSV (*.csv *.txt)') if fname[0] == "": return with open(fname[0], 'r', encoding="utf8") as F: data = F.readlines() if "VIDEOTRACKER MADE THIS FILE!" not in data[0]: rep = QMessageBox.critical( None, # QMessageBox parent widget 'Erreur', # window bar "Désolé, le fichier CSV <{}> n'a pas été\forgé par VideoTracker..."\ .format(os.path.basename(fname[0])), QMessageBox.Ok) return self.clearPlots() # Extract the meta-data dictionary and fill the field in the Image display: exec("self.imageTab.dico_video="+data[1].split('#')[1].strip()) self.imageTab.parse_meta_data() self.imageTab.setTextInfoVideoGrid() # Extract the unit-scale dictionary and fill the field in the Image display: unit_data = data[2].split('#')[1].strip() exec("self.unit_dict={}".format(unit_data)) print("self.unit_dict:",self.unit_dict) self.imageTab.scale_pixel.setText(str(self.unit_dict["pixels"])) self.imageTab.scale_mm.setText(str(self.unit_dict["mm"])) self.imageTab.scale_XY() self.imageTab.scaleInfoVisible(True) # Extract algo information: algo = data[3].split('#')[1].strip() try: index = ImageDisplay.algo_traj.index(algo) except: rep = QMessageBox.critical( None, # QMessageBox parent widget 'Erreur', # window bar "L'information sur l'algorithme <{}> n'est pas reconnue".format(algo)) return self.imageTab.btn_algo.setCurrentIndex(index) print('index:', index, 'self.imageTab.btn_algo.currentText():', self.imageTab.btn_algo.currentText()) # Extract RGB target color: RGB = data[4].split('#')[1].strip() print("self.target_RGB=np."+RGB) try: exec("self.target_RGB=np."+RGB) except: rep = QMessageBox.critical( None, # QMessageBox parent widget 'Erreur', # window bar "L'information RGB <{}> n'est pas reconnue".format(RGB)) return # Read the CSV file with pandas: self.csv_dataFrame = pandas.read_csv(fname[0], header=5, delimiter=';', encoding="utf8") data = self.csv_dataFrame.values data = [data[:,1], data[:,2], data[:,3]] self.target_pos = np.array(data) self.imageTab.display_plots() # Clear display tab: self.imageTab.btn_algo.clear() self.imageTab.buttonsState(importCSV=True) self.imageTab.img_lbl.setPixmap(QPixmap()) self.twoPlots_VxVy.reset() def ExportCSV(self): '''Export Data in a CSV file.''' if self.__target_pos is None : self.statusBar().showMessage("pas de données à exporter.") return # Lance un sélecteur de fichier pour choisir le fichier à écrire.''' video_name = self.imageTab.dico_video['videoname'].replace(".mp4","") cvs_name = os.path.join(VideoTracker.csv_dir, video_name) fname = QFileDialog.getSaveFileName(self, 'Choisir un nom de fichier CSV à écrire', cvs_name, 'Fichier CSV (*.csv *.txt)') if fname[0] == "": return nbImages = len(self.__target_pos[0]) if self.imageTab.video_FPS is not None: deltaT = 1./self.imageTab.video_FPS time = np.arange(nbImages)*deltaT tlabel, tformat = "T [seconde]", "%10.6e" else: time = range(1, nbImages+1) tlabel, tformat = "image #", "%10d" # building headers: xlabel, ylabel = "X [pixels]", "Y [pixels]" xformat, yformat = "%10d", "%10d" unit_dict = {"pixels": "?", "mm":"?"} if self.imageTab.valid_scale: xlabel, ylabel = "X [mm]", "Y [mm]" xformat, yformat = "%10.6e", "%10.6e" unit_dict["pixels"] = float(self.imageTab.scale_pixel.text()) unit_dict["mm"] = float(self.imageTab.scale_mm.text()) header = "VIDEOTRACKER MADE THIS FILE!\n" header += str(self.imageTab.dico_video)+"\n" header += str(unit_dict)+"\n" header += self.imageTab.btn_algo.currentText()+"\n" header += repr(self.target_RGB)+"\n" header += "{};{};{}; num image".format(tlabel, xlabel, ylabel) fmt = (tformat, xformat, yformat, "%d") data = [] data.append(time) data.append(self.__target_pos[0].tolist()) data.append(self.__target_pos[1].tolist()) data.append(self.__target_pos[2].tolist()) data = np.array(data) fileName = fname[0] if not fileName.endswith(".csv"): fileName += ".csv" np.savetxt(fileName, data.transpose(), delimiter=";", header=header, fmt=fmt, encoding="utf8")
class Editor(QWidget): # {{{ toolbar_prefs_name = None data_changed = pyqtSignal() def __init__(self, parent=None, one_line_toolbar=False, toolbar_prefs_name=None): QWidget.__init__(self, parent) self.toolbar_prefs_name = toolbar_prefs_name or self.toolbar_prefs_name self.toolbar1 = QToolBar(self) self.toolbar2 = QToolBar(self) self.toolbar3 = QToolBar(self) for i in range(1, 4): t = getattr(self, 'toolbar%d' % i) t.setIconSize(QSize(18, 18)) self.editor = EditorWidget(self) self.editor.data_changed.connect(self.data_changed) self.set_base_url = self.editor.set_base_url self.set_html = self.editor.set_html self.tabs = QTabWidget(self) self.tabs.setTabPosition(self.tabs.South) self.wyswyg = QWidget(self.tabs) self.code_edit = QPlainTextEdit(self.tabs) self.source_dirty = False self.wyswyg_dirty = True self._layout = QVBoxLayout(self) self.wyswyg.layout = l = QVBoxLayout(self.wyswyg) self.setLayout(self._layout) l.setContentsMargins(0, 0, 0, 0) if one_line_toolbar: tb = QHBoxLayout() l.addLayout(tb) else: tb = l tb.addWidget(self.toolbar1) tb.addWidget(self.toolbar2) tb.addWidget(self.toolbar3) l.addWidget(self.editor) self._layout.addWidget(self.tabs) self.tabs.addTab(self.wyswyg, _('N&ormal view')) self.tabs.addTab(self.code_edit, _('&HTML source')) self.tabs.currentChanged[int].connect(self.change_tab) self.highlighter = Highlighter(self.code_edit.document()) self.layout().setContentsMargins(0, 0, 0, 0) if self.toolbar_prefs_name is not None: hidden = gprefs.get(self.toolbar_prefs_name) if hidden: self.hide_toolbars() # toolbar1 {{{ self.toolbar1.addAction(self.editor.action_undo) self.toolbar1.addAction(self.editor.action_redo) self.toolbar1.addAction(self.editor.action_select_all) self.toolbar1.addAction(self.editor.action_remove_format) self.toolbar1.addAction(self.editor.action_clear) self.toolbar1.addSeparator() for x in ('copy', 'cut', 'paste'): ac = getattr(self.editor, 'action_' + x) self.toolbar1.addAction(ac) self.toolbar1.addSeparator() self.toolbar1.addAction(self.editor.action_background) # }}} # toolbar2 {{{ for x in ('', 'un'): ac = getattr(self.editor, 'action_%sordered_list' % x) self.toolbar2.addAction(ac) self.toolbar2.addSeparator() for x in ('superscript', 'subscript', 'indent', 'outdent'): self.toolbar2.addAction(getattr(self.editor, 'action_' + x)) if x in ('subscript', 'outdent'): self.toolbar2.addSeparator() self.toolbar2.addAction(self.editor.action_block_style) w = self.toolbar2.widgetForAction(self.editor.action_block_style) if hasattr(w, 'setPopupMode'): w.setPopupMode(w.InstantPopup) self.toolbar2.addAction(self.editor.action_insert_link) self.toolbar2.addAction(self.editor.action_insert_hr) # }}} # toolbar3 {{{ for x in ('bold', 'italic', 'underline', 'strikethrough'): ac = getattr(self.editor, 'action_' + x) self.toolbar3.addAction(ac) self.toolbar3.addSeparator() for x in ('left', 'center', 'right', 'justified'): ac = getattr(self.editor, 'action_align_' + x) self.toolbar3.addAction(ac) self.toolbar3.addSeparator() self.toolbar3.addAction(self.editor.action_color) # }}} self.code_edit.textChanged.connect(self.code_dirtied) self.editor.page().contentsChanged.connect(self.wyswyg_dirtied) def set_minimum_height_for_editor(self, val): self.editor.setMinimumHeight(val) @property def html(self): self.tabs.setCurrentIndex(0) return self.editor.html @html.setter def html(self, v): self.editor.html = v def change_tab(self, index): # print 'reloading:', (index and self.wyswyg_dirty) or (not index and # self.source_dirty) if index == 1: # changing to code view if self.wyswyg_dirty: self.code_edit.setPlainText(self.editor.html) self.wyswyg_dirty = False elif index == 0: # changing to wyswyg if self.source_dirty: self.editor.html = unicode_type(self.code_edit.toPlainText()) self.source_dirty = False @property def tab(self): return 'code' if self.tabs.currentWidget( ) is self.code_edit else 'wyswyg' @tab.setter def tab(self, val): self.tabs.setCurrentWidget(self.code_edit if val == 'code' else self.wyswyg) def wyswyg_dirtied(self, *args): self.wyswyg_dirty = True def code_dirtied(self, *args): self.source_dirty = True def hide_toolbars(self): self.toolbar1.setVisible(False) self.toolbar2.setVisible(False) self.toolbar3.setVisible(False) def show_toolbars(self): self.toolbar1.setVisible(True) self.toolbar2.setVisible(True) self.toolbar3.setVisible(True) def toggle_toolbars(self): visible = self.toolbars_visible getattr(self, ('hide' if visible else 'show') + '_toolbars')() if self.toolbar_prefs_name is not None: gprefs.set(self.toolbar_prefs_name, visible) @property def toolbars_visible(self): return self.toolbar1.isVisible() or self.toolbar2.isVisible( ) or self.toolbar3.isVisible() @toolbars_visible.setter def toolbars_visible(self, val): getattr(self, ('show' if val else 'hide') + '_toolbars')() def set_readonly(self, what): self.editor.set_readonly(what) def hide_tabs(self): self.tabs.tabBar().setVisible(False)
def __init__(self, window, plugin, keystore, device_id): title = _("{} Settings").format(plugin.device) super(SettingsDialog, self).__init__(window, title) self.setMaximumWidth(540) devmgr = plugin.device_manager() config = devmgr.config handler = keystore.handler thread = keystore.thread hs_rows, hs_cols = (64, 128) def invoke_client(method, *args, **kw_args): unpair_after = kw_args.pop('unpair_after', False) def task(): client = devmgr.client_by_id(device_id) if not client: raise RuntimeError("Device not connected") if method: getattr(client, method)(*args, **kw_args) if unpair_after: devmgr.unpair_id(device_id) return client.features thread.add(task, on_success=update) def update(features): self.features = features set_label_enabled() if features.bootloader_hash: bl_hash = bh2u(features.bootloader_hash) bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) else: bl_hash = "N/A" noyes = [_("No"), _("Yes")] endis = [_("Enable Passphrases"), _("Disable Passphrases")] disen = [_("Disabled"), _("Enabled")] setchange = [_("Set a PIN"), _("Change PIN")] version = "%d.%d.%d" % (features.major_version, features.minor_version, features.patch_version) device_label.setText(features.label) pin_set_label.setText(noyes[features.pin_protection]) passphrases_label.setText(disen[features.passphrase_protection]) bl_hash_label.setText(bl_hash) label_edit.setText(features.label) device_id_label.setText(features.device_id) initialized_label.setText(noyes[features.initialized]) version_label.setText(version) clear_pin_button.setVisible(features.pin_protection) clear_pin_warning.setVisible(features.pin_protection) pin_button.setText(setchange[features.pin_protection]) pin_msg.setVisible(not features.pin_protection) passphrase_button.setText(endis[features.passphrase_protection]) language_label.setText(features.language) def set_label_enabled(): label_apply.setEnabled(label_edit.text() != self.features.label) def rename(): invoke_client('change_label', label_edit.text()) def toggle_passphrase(): title = _("Confirm Toggle Passphrase Protection") currently_enabled = self.features.passphrase_protection if currently_enabled: msg = _("After disabling passphrases, you can only pair this " "Electrum wallet if it had an empty passphrase. " "If its passphrase was not empty, you will need to " "create a new wallet with the install wizard. You " "can use this wallet again at any time by re-enabling " "passphrases and entering its passphrase.") else: msg = _("Your current Electrum wallet can only be used with " "an empty passphrase. You must create a separate " "wallet with the install wizard for other passphrases " "as each one generates a new set of addresses.") msg += "\n\n" + _("Are you sure you want to proceed?") if not self.question(msg, title=title): return invoke_client('toggle_passphrase', unpair_after=currently_enabled) def change_homescreen(): dialog = QFileDialog(self, _("Choose Homescreen")) filename, __ = dialog.getOpenFileName() if not filename: return # user cancelled if filename.endswith('.toif'): img = open(filename, 'rb').read() if img[:8] != b'TOIf\x90\x00\x90\x00': handler.show_error('File is not a TOIF file with size of \ 144x144') return else: from PIL import Image # FIXME im = Image.open(filename) if im.size != (128, 64): handler.show_error('Image must be 128 x 64 pixels') return im = im.convert('1') pix = im.load() img = bytearray(1024) for j in range(64): for i in range(128): if pix[i, j]: o = (i + j * 128) img[o // 8] |= (1 << (7 - o % 8)) img = bytes(img) invoke_client('change_homescreen', img) def clear_homescreen(): invoke_client('change_homescreen', b'\x00') def set_pin(): invoke_client('set_pin', remove=False) def clear_pin(): invoke_client('set_pin', remove=True) def wipe_device(): wallet = window.wallet if wallet and sum(wallet.get_balance()): title = _("Confirm Device Wipe") msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has bitcoins in it!") if not self.question( msg, title=title, icon=QMessageBox.Critical): return invoke_client('wipe_device', unpair_after=True) def slider_moved(): mins = timeout_slider.sliderPosition() timeout_minutes.setText(_("%2d minutes") % mins) def slider_released(): config.set_session_timeout(timeout_slider.sliderPosition() * 60) # Information tab info_tab = QWidget() info_layout = QVBoxLayout(info_tab) info_glayout = QGridLayout() info_glayout.setColumnStretch(2, 1) device_label = QLabel() pin_set_label = QLabel() passphrases_label = QLabel() version_label = QLabel() device_id_label = QLabel() bl_hash_label = QLabel() bl_hash_label.setWordWrap(True) language_label = QLabel() initialized_label = QLabel() rows = [ (_("Device Label"), device_label), (_("PIN set"), pin_set_label), (_("Passphrases"), passphrases_label), (_("Firmware Version"), version_label), (_("Device ID"), device_id_label), (_("Bootloader Hash"), bl_hash_label), (_("Language"), language_label), (_("Initialized"), initialized_label), ] for row_num, (label, widget) in enumerate(rows): info_glayout.addWidget(QLabel(label), row_num, 0) info_glayout.addWidget(widget, row_num, 1) info_layout.addLayout(info_glayout) # Settings tab settings_tab = QWidget() settings_layout = QVBoxLayout(settings_tab) settings_glayout = QGridLayout() # Settings tab - Label label_msg = QLabel( _("Name this {}. If you have multiple devices " "their labels help distinguish them.").format(plugin.device)) label_msg.setWordWrap(True) label_label = QLabel(_("Device Label")) label_edit = QLineEdit() label_edit.setMinimumWidth(150) label_edit.setMaxLength(plugin.MAX_LABEL_LEN) label_apply = QPushButton(_("Apply")) label_apply.clicked.connect(rename) label_edit.textChanged.connect(set_label_enabled) settings_glayout.addWidget(label_label, 0, 0) settings_glayout.addWidget(label_edit, 0, 1, 1, 2) settings_glayout.addWidget(label_apply, 0, 3) settings_glayout.addWidget(label_msg, 1, 1, 1, -1) pin_button = QPushButton() # Settings tab - PIN pin_label = QLabel(_("PIN Protection")) pin_button.clicked.connect(set_pin) settings_glayout.addWidget(pin_label, 2, 0) settings_glayout.addWidget(pin_button, 2, 1) pin_msg = QLabel( _("PIN protection is strongly recommended. " "A PIN is your only protection against someone " "stealing your bitcoins if they obtain physical " "access to your {}.").format(plugin.device)) pin_msg.setWordWrap(True) pin_msg.setStyleSheet("color: red") settings_glayout.addWidget(pin_msg, 3, 1, 1, -1) # Settings tab - Homescreen homescreen_label = QLabel(_("Homescreen")) homescreen_change_button = QPushButton(_("Change...")) homescreen_clear_button = QPushButton(_("Reset")) homescreen_change_button.clicked.connect(change_homescreen) try: import PIL except ImportError: homescreen_change_button.setDisabled(True) homescreen_change_button.setToolTip( _("Required package 'PIL' is not available - Please install \ it or use the OctoWallet website instead.")) homescreen_clear_button.clicked.connect(clear_homescreen) homescreen_msg = QLabel( _("You can set the homescreen on your " "device to personalize it. You must " "choose a {} x {} monochrome black and " "white image.").format(hs_rows, hs_cols)) homescreen_msg.setWordWrap(True) settings_glayout.addWidget(homescreen_label, 4, 0) settings_glayout.addWidget(homescreen_change_button, 4, 1) settings_glayout.addWidget(homescreen_clear_button, 4, 2) settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1) # Settings tab - Session Timeout timeout_label = QLabel(_("Session Timeout")) timeout_minutes = QLabel() timeout_slider = QSlider(Qt.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) timeout_slider.setTickPosition(QSlider.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QLabel( _("Clear the session after the specified period " "of inactivity. Once a session has timed out, " "your PIN and passphrase (if enabled) must be " "re-entered to use the device.")) timeout_msg.setWordWrap(True) timeout_slider.setSliderPosition(config.get_session_timeout() // 60) slider_moved() timeout_slider.valueChanged.connect(slider_moved) timeout_slider.sliderReleased.connect(slider_released) settings_glayout.addWidget(timeout_label, 6, 0) settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3) settings_glayout.addWidget(timeout_minutes, 6, 4) settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1) settings_layout.addLayout(settings_glayout) settings_layout.addStretch(1) # Advanced tab advanced_tab = QWidget() advanced_layout = QVBoxLayout(advanced_tab) advanced_glayout = QGridLayout() # Advanced tab - clear PIN clear_pin_button = QPushButton(_("Disable PIN")) clear_pin_button.clicked.connect(clear_pin) clear_pin_warning = QLabel( _("If you disable your PIN, anyone with physical access to your " "{} device can spend your bitcoins.").format(plugin.device)) clear_pin_warning.setWordWrap(True) clear_pin_warning.setStyleSheet("color: red") advanced_glayout.addWidget(clear_pin_button, 0, 2) advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5) # Advanced tab - toggle passphrase protection passphrase_button = QPushButton() passphrase_button.clicked.connect(toggle_passphrase) passphrase_msg = WWLabel(PASSPHRASE_HELP) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning.setStyleSheet("color: red") advanced_glayout.addWidget(passphrase_button, 3, 2) advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5) advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5) # Advanced tab - wipe device wipe_device_button = QPushButton(_("Wipe Device")) wipe_device_button.clicked.connect(wipe_device) wipe_device_msg = QLabel( _("Wipe the device, removing all data from it. The firmware " "is left unchanged.")) wipe_device_msg.setWordWrap(True) wipe_device_warning = QLabel( _("Only wipe a device if you have the recovery seed written down " "and the device wallet(s) are empty, otherwise the bitcoins " "will be lost forever.")) wipe_device_warning.setWordWrap(True) wipe_device_warning.setStyleSheet("color: red") advanced_glayout.addWidget(wipe_device_button, 6, 2) advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5) advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5) advanced_layout.addLayout(advanced_glayout) advanced_layout.addStretch(1) tabs = QTabWidget(self) tabs.addTab(info_tab, _("Information")) tabs.addTab(settings_tab, _("Settings")) tabs.addTab(advanced_tab, _("Advanced")) dialog_vbox = QVBoxLayout(self) dialog_vbox.addWidget(tabs) dialog_vbox.addLayout(Buttons(CloseButton(self))) # Update information invoke_client(None)