class Listspace(QSplitter, ViewManager): """ Class implementing the listspace viewmanager class. @signal changeCaption(str) emitted if a change of the caption is necessary @signal editorChanged(str) emitted when the current editor has changed @signal editorChangedEd(Editor) emitted when the current editor has changed @signal lastEditorClosed() emitted after the last editor window was closed @signal editorOpened(str) emitted after an editor window was opened @signal editorOpenedEd(Editor) emitted after an editor window was opened @signal editorClosed(str) emitted just before an editor window gets closed @signal editorClosedEd(Editor) emitted just before an editor window gets closed @signal editorRenamed(str) emitted after an editor was renamed @signal editorRenamedEd(Editor) emitted after an editor was renamed @signal editorSaved(str) emitted after an editor window was saved @signal editorSavedEd(Editor) emitted after an editor window was saved @signal checkActions(Editor) emitted when some actions should be checked for their status @signal cursorChanged(Editor) emitted after the cursor position of the active window has changed @signal breakpointToggled(Editor) emitted when a breakpoint is toggled. @signal bookmarkToggled(Editor) emitted when a bookmark is toggled. @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled. @signal previewStateChanged(bool) emitted to signal a change in the preview state @signal editorLanguageChanged(Editor) emitted to signal a change of an editors language @signal editorTextChanged(Editor) emitted to signal a change of an editor's text @signal editorLineChanged(str,int) emitted to signal a change of an editor's current line (line is given one based) """ changeCaption = pyqtSignal(str) editorChanged = pyqtSignal(str) editorChangedEd = pyqtSignal(Editor) lastEditorClosed = pyqtSignal() editorOpened = pyqtSignal(str) editorOpenedEd = pyqtSignal(Editor) editorClosed = pyqtSignal(str) editorClosedEd = pyqtSignal(Editor) editorRenamed = pyqtSignal(str) editorRenamedEd = pyqtSignal(Editor) editorSaved = pyqtSignal(str) editorSavedEd = pyqtSignal(Editor) checkActions = pyqtSignal(Editor) cursorChanged = pyqtSignal(Editor) breakpointToggled = pyqtSignal(Editor) bookmarkToggled = pyqtSignal(Editor) syntaxerrorToggled = pyqtSignal(Editor) previewStateChanged = pyqtSignal(bool) editorLanguageChanged = pyqtSignal(Editor) editorTextChanged = pyqtSignal(Editor) editorLineChanged = pyqtSignal(str, int) def __init__(self, parent): """ Constructor @param parent parent widget (QWidget) """ self.stacks = [] QSplitter.__init__(self, parent) ViewManager.__init__(self) self.setChildrenCollapsible(False) self.viewlist = QListWidget(self) policy = self.viewlist.sizePolicy() policy.setHorizontalPolicy(QSizePolicy.Ignored) self.viewlist.setSizePolicy(policy) self.addWidget(self.viewlist) self.viewlist.setContextMenuPolicy(Qt.CustomContextMenu) self.viewlist.currentRowChanged.connect(self.__showSelectedView) self.viewlist.customContextMenuRequested.connect(self.__showMenu) self.stackArea = QSplitter(self) self.stackArea.setChildrenCollapsible(False) self.addWidget(self.stackArea) self.stackArea.setOrientation(Qt.Vertical) stack = StackedWidget(self.stackArea) self.stackArea.addWidget(stack) self.stacks.append(stack) self.currentStack = stack stack.currentChanged.connect(self.__currentChanged) stack.installEventFilter(self) self.setSizes([int(self.width() * 0.2), int(self.width() * 0.8)]) # 20% for viewlist, 80% for the editors self.__inRemoveView = False self.__initMenu() self.contextMenuEditor = None self.contextMenuIndex = -1 def __initMenu(self): """ Private method to initialize the viewlist context menu. """ self.__menu = QMenu(self) self.__menu.addAction(UI.PixmapCache.getIcon("tabClose.png"), self.tr('Close'), self.__contextMenuClose) self.closeOthersMenuAct = self.__menu.addAction( UI.PixmapCache.getIcon("tabCloseOther.png"), self.tr("Close Others"), self.__contextMenuCloseOthers) self.__menu.addAction(self.tr('Close All'), self.__contextMenuCloseAll) self.__menu.addSeparator() self.saveMenuAct = self.__menu.addAction( UI.PixmapCache.getIcon("fileSave.png"), self.tr('Save'), self.__contextMenuSave) self.__menu.addAction(UI.PixmapCache.getIcon("fileSaveAs.png"), self.tr('Save As...'), self.__contextMenuSaveAs) self.__menu.addAction(UI.PixmapCache.getIcon("fileSaveAll.png"), self.tr('Save All'), self.__contextMenuSaveAll) self.__menu.addSeparator() self.openRejectionsMenuAct = self.__menu.addAction( self.tr("Open 'rejection' file"), self.__contextMenuOpenRejections) self.__menu.addSeparator() self.__menu.addAction(UI.PixmapCache.getIcon("print.png"), self.tr('Print'), self.__contextMenuPrintFile) self.__menu.addSeparator() self.copyPathAct = self.__menu.addAction( self.tr("Copy Path to Clipboard"), self.__contextMenuCopyPathToClipboard) def __showMenu(self, point): """ Private slot to handle the customContextMenuRequested signal of the viewlist. @param point position to open the menu at (QPoint) """ if self.editors: itm = self.viewlist.itemAt(point) if itm is not None: row = self.viewlist.row(itm) self.contextMenuEditor = self.editors[row] self.contextMenuIndex = row if self.contextMenuEditor: self.saveMenuAct.setEnabled( self.contextMenuEditor.isModified()) fileName = self.contextMenuEditor.getFileName() self.copyPathAct.setEnabled(bool(fileName)) if fileName: rej = "{0}.rej".format(fileName) self.openRejectionsMenuAct.setEnabled( os.path.exists(rej)) else: self.openRejectionsMenuAct.setEnabled(False) self.closeOthersMenuAct.setEnabled( self.viewlist.count() > 1) self.__menu.popup(self.viewlist.mapToGlobal(point)) def canCascade(self): """ Public method to signal if cascading of managed windows is available. @return flag indicating cascading of windows is available """ return False def canTile(self): """ Public method to signal if tiling of managed windows is available. @return flag indicating tiling of windows is available """ return False def canSplit(self): """ public method to signal if splitting of the view is available. @return flag indicating splitting of the view is available. """ return True def tile(self): """ Public method to tile the managed windows. """ pass def cascade(self): """ Public method to cascade the managed windows. """ pass def _removeAllViews(self): """ Protected method to remove all views (i.e. windows). """ self.viewlist.clear() for win in self.editors: for stack in self.stacks: if stack.hasEditor(win): stack.removeWidget(win) break win.closeIt() def _removeView(self, win): """ Protected method to remove a view (i.e. window). @param win editor window to be removed """ self.__inRemoveView = True ind = self.editors.index(win) itm = self.viewlist.takeItem(ind) if itm: del itm for stack in self.stacks: if stack.hasEditor(win): stack.removeWidget(win) break win.closeIt() self.__inRemoveView = False if ind > 0: ind -= 1 else: if len(self.editors) > 1: ind = 1 else: return stack.setCurrentWidget(stack.firstEditor()) self._showView(self.editors[ind].parent()) aw = self.activeWindow() fn = aw and aw.getFileName() or None if fn: self.changeCaption.emit(fn) self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, aw.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(aw) def _addView(self, win, fn=None, noName="", next=False): """ Protected method to add a view (i.e. window). @param win editor assembly to be added @param fn filename of this editor (string) @param noName name to be used for an unnamed editor (string) @param next flag indicating to add the view next to the current view (bool) """ editor = win.getEditor() if fn is None: if not noName: self.untitledCount += 1 noName = self.tr("Untitled {0}").format(self.untitledCount) self.viewlist.addItem(noName) editor.setNoName(noName) else: txt = os.path.basename(fn) if not QFileInfo(fn).isWritable(): txt = self.tr("{0} (ro)").format(txt) itm = QListWidgetItem(txt) itm.setToolTip(fn) self.viewlist.addItem(itm) self.currentStack.addWidget(win) self.currentStack.setCurrentWidget(win) editor.captionChanged.connect(self.__captionChange) editor.cursorLineChanged.connect(self.__cursorLineChanged) index = self.editors.index(editor) self.viewlist.setCurrentRow(index) editor.setFocus() if fn: self.changeCaption.emit(fn) self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(editor) def __captionChange(self, cap, editor): """ Private method to handle caption change signals from the editor. Updates the listwidget text to reflect the new caption information. @param cap Caption for the editor (string) @param editor Editor to update the caption for """ fn = editor.getFileName() if fn: self.setEditorName(editor, fn) def __cursorLineChanged(self, lineno): """ Private slot to handle a change of the current editor's cursor line. @param lineno line number of the current editor's cursor (zero based) """ editor = self.sender() if editor: fn = editor.getFileName() if fn: self.editorLineChanged.emit(fn, lineno + 1) def _showView(self, win, fn=None): """ Protected method to show a view (i.e. window). @param win editor assembly to be shown @param fn filename of this editor (string) """ editor = win.getEditor() for stack in self.stacks: if stack.hasEditor(editor): stack.setCurrentWidget(win) self.currentStack = stack break index = self.editors.index(editor) self.viewlist.setCurrentRow(index) editor.setFocus() fn = editor.getFileName() if fn: self.changeCaption.emit(fn) self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(editor) def __showSelectedView(self, row): """ Private slot called to show a view selected in the list. @param row row number of the item clicked on (integer) """ if row != -1: self._showView(self.editors[row].parent()) self._checkActions(self.editors[row]) def activeWindow(self): """ Public method to return the active (i.e. current) window. @return reference to the active editor """ return self.currentStack.currentWidget() def showWindowMenu(self, windowMenu): """ Public method to set up the viewmanager part of the Window menu. @param windowMenu reference to the window menu """ pass def _initWindowActions(self): """ Protected method to define the user interface actions for window handling. """ pass def setEditorName(self, editor, newName): """ Public method to change the displayed name of the editor. @param editor editor window to be changed @param newName new name to be shown (string) """ if newName: currentRow = self.viewlist.currentRow() index = self.editors.index(editor) txt = os.path.basename(newName) if not QFileInfo(newName).isWritable(): txt = self.tr("{0} (ro)").format(txt) itm = self.viewlist.item(index) itm.setText(txt) itm.setToolTip(newName) self.viewlist.setCurrentRow(currentRow) self.changeCaption.emit(newName) def _modificationStatusChanged(self, m, editor): """ Protected slot to handle the modificationStatusChanged signal. @param m flag indicating the modification status (boolean) @param editor editor window changed """ currentRow = self.viewlist.currentRow() index = self.editors.index(editor) keys = [] if m: keys.append("fileModified.png") if editor.hasSyntaxErrors(): keys.append("syntaxError22.png") elif editor.hasWarnings(): keys.append("warning22.png") if not keys: keys.append("empty.png") self.viewlist.item(index).setIcon(UI.PixmapCache.getCombinedIcon(keys)) self.viewlist.setCurrentRow(currentRow) self._checkActions(editor) def _syntaxErrorToggled(self, editor): """ Protected slot to handle the syntaxerrorToggled signal. @param editor editor that sent the signal """ currentRow = self.viewlist.currentRow() index = self.editors.index(editor) keys = [] if editor.isModified(): keys.append("fileModified.png") if editor.hasSyntaxErrors(): keys.append("syntaxError22.png") elif editor.hasWarnings(): keys.append("warning22.png") if not keys: keys.append("empty.png") self.viewlist.item(index).setIcon(UI.PixmapCache.getCombinedIcon(keys)) self.viewlist.setCurrentRow(currentRow) ViewManager._syntaxErrorToggled(self, editor) def addSplit(self): """ Public method used to split the current view. """ stack = StackedWidget(self.stackArea) stack.show() self.stackArea.addWidget(stack) self.stacks.append(stack) self.currentStack = stack stack.currentChanged.connect(self.__currentChanged) stack.installEventFilter(self) if self.stackArea.orientation() == Qt.Horizontal: size = self.stackArea.width() else: size = self.stackArea.height() self.stackArea.setSizes([int(size / len(self.stacks))] * len(self.stacks)) self.splitRemoveAct.setEnabled(True) self.nextSplitAct.setEnabled(True) self.prevSplitAct.setEnabled(True) def removeSplit(self): """ Public method used to remove the current split view. @return flag indicating successfull removal """ if len(self.stacks) > 1: stack = self.currentStack res = True savedEditors = stack.editors[:] for editor in savedEditors: res &= self.closeEditor(editor) if res: try: i = self.stacks.index(stack) except ValueError: return True if i == len(self.stacks) - 1: i -= 1 self.stacks.remove(stack) stack.close() self.currentStack = self.stacks[i] if len(self.stacks) == 1: self.splitRemoveAct.setEnabled(False) self.nextSplitAct.setEnabled(False) self.prevSplitAct.setEnabled(False) return True return False def getSplitOrientation(self): """ Public method to get the orientation of the split view. @return orientation of the split (Qt.Horizontal or Qt.Vertical) """ return self.stackArea.orientation() def setSplitOrientation(self, orientation): """ Public method used to set the orientation of the split view. @param orientation orientation of the split (Qt.Horizontal or Qt.Vertical) """ self.stackArea.setOrientation(orientation) def nextSplit(self): """ Public slot used to move to the next split. """ aw = self.activeWindow() _hasFocus = aw and aw.hasFocus() ind = self.stacks.index(self.currentStack) + 1 if ind == len(self.stacks): ind = 0 self.currentStack = self.stacks[ind] if _hasFocus: aw = self.activeWindow() if aw: aw.setFocus() index = self.editors.index(self.currentStack.currentWidget()) self.viewlist.setCurrentRow(index) def prevSplit(self): """ Public slot used to move to the previous split. """ aw = self.activeWindow() _hasFocus = aw and aw.hasFocus() ind = self.stacks.index(self.currentStack) - 1 if ind == -1: ind = len(self.stacks) - 1 self.currentStack = self.stacks[ind] if _hasFocus: aw = self.activeWindow() if aw: aw.setFocus() index = self.editors.index(self.currentStack.currentWidget()) self.viewlist.setCurrentRow(index) def __contextMenuClose(self): """ Private method to close the selected editor. """ if self.contextMenuEditor: self.closeEditorWindow(self.contextMenuEditor) def __contextMenuCloseOthers(self): """ Private method to close the other editors. """ index = self.contextMenuIndex for i in list(range(self.viewlist.count() - 1, index, -1)) + \ list(range(index - 1, -1, -1)): editor = self.editors[i] self.closeEditorWindow(editor) def __contextMenuCloseAll(self): """ Private method to close all editors. """ savedEditors = self.editors[:] for editor in savedEditors: self.closeEditorWindow(editor) def __contextMenuSave(self): """ Private method to save the selected editor. """ if self.contextMenuEditor: self.saveEditorEd(self.contextMenuEditor) def __contextMenuSaveAs(self): """ Private method to save the selected editor to a new file. """ if self.contextMenuEditor: self.saveAsEditorEd(self.contextMenuEditor) def __contextMenuSaveAll(self): """ Private method to save all editors. """ self.saveEditorsList(self.editors) def __contextMenuOpenRejections(self): """ Private slot to open a rejections file associated with the selected editor. """ if self.contextMenuEditor: fileName = self.contextMenuEditor.getFileName() if fileName: rej = "{0}.rej".format(fileName) if os.path.exists(rej): self.openSourceFile(rej) def __contextMenuPrintFile(self): """ Private method to print the selected editor. """ if self.contextMenuEditor: self.printEditor(self.contextMenuEditor) def __contextMenuCopyPathToClipboard(self): """ Private method to copy the file name of the selected editor to the clipboard. """ if self.contextMenuEditor: fn = self.contextMenuEditor.getFileName() if fn: cb = QApplication.clipboard() cb.setText(fn) def __currentChanged(self, index): """ Private slot to handle the currentChanged signal. @param index index of the current editor """ if index == -1 or not self.editors: return editor = self.activeWindow() if editor is None: return self._checkActions(editor) editor.setFocus() fn = editor.getFileName() if fn: self.changeCaption.emit(fn) if not self.__inRemoveView: self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(editor) cindex = self.editors.index(editor) self.viewlist.setCurrentRow(cindex) def eventFilter(self, watched, event): """ Public method called to filter the event queue. @param watched the QObject being watched @param event the event that occurred @return flag indicating, if we handled the event """ if event.type() == QEvent.MouseButtonPress and \ not event.button() == Qt.RightButton: switched = True if isinstance(watched, QStackedWidget): switched = watched is not self.currentStack self.currentStack = watched elif isinstance(watched, QScintilla.Editor.Editor): for stack in self.stacks: if stack.hasEditor(watched): switched = stack is not self.currentStack self.currentStack = stack break currentWidget = self.currentStack.currentWidget() if currentWidget: index = self.editors.index(currentWidget) self.viewlist.setCurrentRow(index) aw = self.activeWindow() if aw is not None: self._checkActions(aw) aw.setFocus() fn = aw.getFileName() if fn: self.changeCaption.emit(fn) if switched: self.editorChanged.emit(fn) self.editorLineChanged.emit( fn, aw.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(aw) return False
class SearcherWidget(QWidget): def __init__(self, wheres_the_fck_receipt: api_interface.WheresTheFckReceipt, parent=None): QWidget.__init__(self, parent) self.wheres_the_fck_receipt = wheres_the_fck_receipt # type: api_interface.WheresTheFckReceipt self.results = None # type List[api_interface.Result] self.current_preview_image = None # query self.query = QLineEdit() self.query.mousePressEvent = lambda _: self.query.selectAll() self.query.returnPressed.connect(self.search_button_clicked) self.limit_box = QSpinBox() self.limit_box.valueChanged.connect(self.search_button_clicked) self.cs_box = QCheckBox("Case Sensitive") self.cs_box.stateChanged.connect(self.search_button_clicked) search_button = QPushButton('Search') search_button.clicked.connect(self.search_button_clicked) query_bar_layout = QHBoxLayout() query_bar_layout.setContentsMargins(0, 0, 0, 0) query_bar_layout.addWidget(QLabel("Search Term")) query_bar_layout.addWidget(self.query) query_bar_layout.addWidget(QLabel("Max. Results")) query_bar_layout.addWidget(self.limit_box) query_bar_layout.addWidget(self.cs_box) query_bar_layout.addWidget(search_button) # the file_list self.match_list = MatcherTableWidget() self.match_list.setShowGrid(True) self.match_list.setAutoScroll(True) self.match_list.setSelectionMode(QAbstractItemView.SingleSelection) self.match_list.setSelectionBehavior(QAbstractItemView.SelectRows) self.match_list.itemSelectionChanged.connect(self.match_list_item_selection_changed) self.match_list.itemDoubleClicked.connect(self.match_list_double_clicked) self.match_list.setEditTriggers(QAbstractItemView.NoEditTriggers) self.preview = QLabel() self.preview_widget = QSplitter() self.preview_widget.setContentsMargins(0, 0, 0, 0) self.preview_widget.addWidget(self.match_list) self.preview_widget.addWidget(self.preview) # review settings settings = QSettings('WheresTheFckReceipt', 'WheresTheFckReceipt') self.query.setText(settings.value("query_text", "")) self.limit_box.setValue(settings.value("limit_box_value", 0)) self.cs_box.setChecked(bool(settings.value("cs_box_checked", False))) # my layout layout = QVBoxLayout() layout.addLayout(query_bar_layout) layout.addWidget(self.preview_widget) self.setLayout(layout) def match_list_double_clicked(self, mi): row = mi.row() result = self.results[row] self.open_file(result.get_path()) def open_file(self, filepath): if platform.system() == 'Darwin': # macOS subprocess.call(('open', filepath)) elif platform.system() == 'Windows': # Windows os.startfile(filepath) else: # linux variants subprocess.call(('xdg-open', filepath)) def match_list_item_selection_changed(self): selected_items = self.match_list.selectedItems() if len(selected_items) == 0: return curr_row = self.match_list.currentRow() result = self.results[curr_row] im = result.get_preview_image() if im is not None: self.current_preview_image = QtGui.QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QtGui.QImage.Format_RGB888).rgbSwapped() w = self.preview_widget.width() / 2.0 h = self.preview_widget.height() self.preview_widget.setSizes([w, w]) self.preview.setPixmap(QPixmap(self.current_preview_image).scaled(w, h, Qt.KeepAspectRatio)) else: self.preview.setText("Preview could not be loaded.") def splitter_moved(self, pos, index): w = self.preview.width() h = self.preview.height() self.preview.setPixmap(QPixmap(self.current_preview_image).scaled(w, h, Qt.KeepAspectRatio)) def search_button_clicked(self): self.preview.setText("No image selected.") settings = QSettings('WheresTheFckReceipt', 'WheresTheFckReceipt') settings.setValue("query_text", self.query.text()) settings.setValue("limit_box_value", self.limit_box.value()) settings.setValue("cs_box_checked", self.cs_box.isChecked()) del settings self.results = self.wheres_the_fck_receipt.search(self.query.text(), self.limit_box.value(), self.cs_box.isChecked()) self.match_list.clear() self.match_list.setColumnCount(3) self.match_list.setHorizontalHeaderLabels(['File', 'Page', 'Path']) header = self.match_list.horizontalHeader() header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) # header.setStretchLastSection(True) self.match_list.setRowCount(len(self.results)) for i in range(len(self.results)): result = self.results[i] path = result.get_path() self.match_list.setItem(i, 0, QTableWidgetItem(os.path.basename(path))) self.match_list.setItem(i, 1, QTableWidgetItem(str(result.get_page()))) self.match_list.setItem(i, 2, QTableWidgetItem(os.path.dirname(path))) self.query.setFocus() self.query.selectAll()
class TreeWindow(QMainWindow): """Class override for the main window. Contains main window views and controls. """ selectChanged = pyqtSignal() nodeModified = pyqtSignal(treenode.TreeNode) treeModified = pyqtSignal() winActivated = pyqtSignal(QMainWindow) winMinimized = pyqtSignal() winClosing = pyqtSignal(QMainWindow) def __init__(self, model, allActions, parent=None): """Initialize the main window. Arguments: model -- the initial data model allActions -- a dict containing the upper level actions parent -- the parent window, usually None """ super().__init__(parent) self.allActions = allActions.copy() self.allowCloseFlag = True self.winActions = {} self.toolbars = [] self.rightTabActList = [] self.setAttribute(Qt.WA_DeleteOnClose) self.setAcceptDrops(True) self.setStatusBar(QStatusBar()) self.setCaption() self.setupActions() self.setupMenus() self.setupToolbars() self.restoreToolbarPosition() self.treeView = treeview.TreeView(model, self.allActions) self.breadcrumbSplitter = QSplitter(Qt.Vertical) self.setCentralWidget(self.breadcrumbSplitter) self.breadcrumbView = breadcrumbview.BreadcrumbView(self.treeView) self.breadcrumbSplitter.addWidget(self.breadcrumbView) self.breadcrumbView.setVisible(globalref. genOptions['InitShowBreadcrumb']) self.treeSplitter = QSplitter() self.breadcrumbSplitter.addWidget(self.treeSplitter) self.treeStack = QStackedWidget() self.treeSplitter.addWidget(self.treeStack) self.treeStack.addWidget(self.treeView) self.treeView.shortcutEntered.connect(self.execShortcut) self.treeView.selectionModel().selectionChanged.connect(self. updateRightViews) self.treeFilterView = None self.rightTabs = QTabWidget() self.treeSplitter.addWidget(self.rightTabs) self.rightTabs.setTabPosition(QTabWidget.South) self.rightTabs.tabBar().setFocusPolicy(Qt.NoFocus) self.outputSplitter = QSplitter(Qt.Vertical) self.rightTabs.addTab(self.outputSplitter, _('Data Output')) parentOutputView = outputview.OutputView(self.treeView, False) parentOutputView.highlighted[str].connect(self.statusBar().showMessage) self.outputSplitter.addWidget(parentOutputView) childOutputView = outputview.OutputView(self.treeView, True) childOutputView.highlighted[str].connect(self.statusBar().showMessage) self.outputSplitter.addWidget(childOutputView) self.editorSplitter = QSplitter(Qt.Vertical) self.rightTabs.addTab(self.editorSplitter, _('Data Edit')) parentEditView = dataeditview.DataEditView(self.treeView, self.allActions, False) parentEditView.shortcutEntered.connect(self.execShortcut) parentEditView.focusOtherView.connect(self.focusNextView) parentEditView.inLinkSelectMode.connect(self.treeView. toggleNoMouseSelectMode) self.treeView.skippedMouseSelect.connect(parentEditView. internalLinkSelected) self.editorSplitter.addWidget(parentEditView) childEditView = dataeditview.DataEditView(self.treeView, self.allActions, True) childEditView.shortcutEntered.connect(self.execShortcut) childEditView.focusOtherView.connect(self.focusNextView) childEditView.inLinkSelectMode.connect(self.treeView. toggleNoMouseSelectMode) self.treeView.skippedMouseSelect.connect(childEditView. internalLinkSelected) parentEditView.hoverFocusActive.connect(childEditView.endEditor) childEditView.hoverFocusActive.connect(parentEditView.endEditor) parentEditView.inLinkSelectMode.connect(childEditView. updateInLinkSelectMode) childEditView.inLinkSelectMode.connect(parentEditView. updateInLinkSelectMode) self.editorSplitter.addWidget(childEditView) self.titleSplitter = QSplitter(Qt.Vertical) self.rightTabs.addTab(self.titleSplitter, _('Title List')) parentTitleView = titlelistview.TitleListView(self.treeView, False) parentTitleView.shortcutEntered.connect(self.execShortcut) self.titleSplitter.addWidget(parentTitleView) childTitleView = titlelistview.TitleListView(self.treeView, True) childTitleView.shortcutEntered.connect(self.execShortcut) self.titleSplitter.addWidget(childTitleView) self.rightTabs.currentChanged.connect(self.updateRightViews) self.updateFonts() def setExternalSignals(self): """Connect widow object signals to signals in this object. In a separate method to refresh after local control change. """ self.treeView.selectionModel().selectionChanged.connect(self. selectChanged) for i in range(2): self.editorSplitter.widget(i).nodeModified.connect(self. nodeModified) self.titleSplitter.widget(i).nodeModified.connect(self. nodeModified) self.titleSplitter.widget(i).treeModified.connect(self. treeModified) def updateActions(self, allActions): """Use new actions for menus, etc. when the local control changes. Arguments: allActions -- a dict containing the upper level actions """ # remove submenu actions that are children of the window self.removeAction(self.allActions['DataNodeType']) self.removeAction(self.allActions['FormatFontSize']) self.allActions = allActions.copy() self.allActions.update(self.winActions) self.menuBar().clear() self.setupMenus() self.addToolbarCommands() self.treeView.allActions = self.allActions for i in range(2): self.editorSplitter.widget(i).allActions = self.allActions def updateTreeNode(self, node): """Update all spots for the given node in the tree view. Arguments: node -- the node to be updated """ for spot in node.spotRefs: self.treeView.update(spot.index(self.treeView.model())) self.treeView.resizeColumnToContents(0) self.breadcrumbView.updateContents() def updateTree(self): """Update the full tree view. """ self.treeView.scheduleDelayedItemsLayout() self.breadcrumbView.updateContents() def updateRightViews(self, *args, outputOnly=False): """Update all right-hand views and breadcrumb view. Arguments: *args -- dummy arguments to collect args from signals outputOnly -- only update output views (not edit views) """ if globalref.mainControl.activeControl: self.rightTabActList[self.rightTabs. currentIndex()].setChecked(True) self.breadcrumbView.updateContents() splitter = self.rightTabs.currentWidget() if not outputOnly or isinstance(splitter.widget(0), outputview.OutputView): for i in range(2): splitter.widget(i).updateContents() def refreshDataEditViews(self): """Refresh the data in non-selected cells in curreent data edit views. """ splitter = self.rightTabs.currentWidget() if isinstance(splitter.widget(0), dataeditview.DataEditView): for i in range(2): splitter.widget(i).updateUnselectedCells() def updateCommandsAvail(self): """Set window commands available based on node selections. """ self.allActions['ViewPrevSelect'].setEnabled(len(self.treeView. selectionModel(). prevSpots) > 1) self.allActions['ViewNextSelect'].setEnabled(len(self.treeView. selectionModel(). nextSpots) > 0) def updateWinGenOptions(self): """Update tree and data edit windows based on general option changes. """ self.treeView.updateTreeGenOptions() for i in range(2): self.editorSplitter.widget(i).setMouseTracking(globalref. genOptions['EditorOnHover']) def updateFonts(self): """Update custom fonts in views. """ treeFont = QTextDocument().defaultFont() treeFontName = globalref.miscOptions['TreeFont'] if treeFontName: treeFont.fromString(treeFontName) self.treeView.setFont(treeFont) self.treeView.updateTreeGenOptions() if self.treeFilterView: self.treeFilterView.setFont(treeFont) ouputFont = QTextDocument().defaultFont() ouputFontName = globalref.miscOptions['OutputFont'] if ouputFontName: ouputFont.fromString(ouputFontName) editorFont = QTextDocument().defaultFont() editorFontName = globalref.miscOptions['EditorFont'] if editorFontName: editorFont.fromString(editorFontName) for i in range(2): self.outputSplitter.widget(i).setFont(ouputFont) self.editorSplitter.widget(i).setFont(editorFont) self.titleSplitter.widget(i).setFont(editorFont) def resetTreeModel(self, model): """Change the model assigned to the tree view. Arguments: model -- the new model to assign """ self.treeView.resetModel(model) self.treeView.selectionModel().selectionChanged.connect(self. updateRightViews) def activateAndRaise(self): """Activate this window and raise it to the front. """ self.activateWindow() self.raise_() def setCaption(self, pathObj=None, modified=False): """Change the window caption title based on the file name and path. Arguments: pathObj - a path object for the current file """ modFlag = '*' if modified else '' if pathObj: caption = '{0}{1} [{2}] - TreeLine'.format(str(pathObj.name), modFlag, str(pathObj.parent)) else: caption = '- TreeLine' self.setWindowTitle(caption) def filterView(self): """Create, show and return a filter view. """ self.removeFilterView() self.treeFilterView = treeview.TreeFilterView(self.treeView, self.allActions) self.treeFilterView.shortcutEntered.connect(self.execShortcut) self.treeView.selectionModel().selectionChanged.connect(self. treeFilterView. updateFromSelectionModel) for i in range(2): editView = self.editorSplitter.widget(i) editView.inLinkSelectMode.connect(self.treeFilterView. toggleNoMouseSelectMode) self.treeFilterView.skippedMouseSelect.connect(editView. internalLinkSelected) self.treeStack.addWidget(self.treeFilterView) self.treeStack.setCurrentWidget(self.treeFilterView) return self.treeFilterView def removeFilterView(self): """Hide and delete the current filter view. """ if self.treeFilterView != None: # check for None since False if empty self.treeStack.removeWidget(self.treeFilterView) globalref.mainControl.currentStatusBar().removeWidget(self. treeFilterView. messageLabel) self.treeFilterView.messageLabel.deleteLater() self.treeFilterView = None def rightParentView(self): """Return the current right-hand parent view if visible (or None). """ view = self.rightTabs.currentWidget().widget(0) if not view.isVisible() or view.height() == 0 or view.width() == 0: return None return view def rightChildView(self): """Return the current right-hand parent view if visible (or None). """ view = self.rightTabs.currentWidget().widget(1) if not view.isVisible() or view.height() == 0 or view.width() == 0: return None return view def focusNextView(self, forward=True): """Focus the next pane in the tab focus series. Called by a signal from the data edit views. Tab sequences tend to skip views without this. Arguments: forward -- forward in tab series if True """ reason = (Qt.TabFocusReason if forward else Qt.BacktabFocusReason) rightParent = self.rightParentView() rightChild = self.rightChildView() if (self.sender().isChildView == forward or (forward and rightChild == None) or (not forward and rightParent == None)): self.treeView.setFocus(reason) elif forward: rightChild.setFocus(reason) else: rightParent.setFocus(reason) def execShortcut(self, key): """Execute an action based on a shortcut key signal from a view. Arguments: key -- the QKeySequence shortcut """ keyDict = {action.shortcut().toString(): action for action in self.allActions.values()} try: action = keyDict[key.toString()] except KeyError: return if action.isEnabled(): action.trigger() def setupActions(self): """Add the actions for contols at the window level. These actions only affect an individual window, they're independent in multiple windows of the same file. """ viewExpandBranchAct = QAction(_('&Expand Full Branch'), self, statusTip=_('Expand all children of the selected nodes')) viewExpandBranchAct.triggered.connect(self.viewExpandBranch) self.winActions['ViewExpandBranch'] = viewExpandBranchAct viewCollapseBranchAct = QAction(_('&Collapse Full Branch'), self, statusTip=_('Collapse all children of the selected nodes')) viewCollapseBranchAct.triggered.connect(self.viewCollapseBranch) self.winActions['ViewCollapseBranch'] = viewCollapseBranchAct viewPrevSelectAct = QAction(_('&Previous Selection'), self, statusTip=_('Return to the previous tree selection')) viewPrevSelectAct.triggered.connect(self.viewPrevSelect) self.winActions['ViewPrevSelect'] = viewPrevSelectAct viewNextSelectAct = QAction(_('&Next Selection'), self, statusTip=_('Go to the next tree selection in history')) viewNextSelectAct.triggered.connect(self.viewNextSelect) self.winActions['ViewNextSelect'] = viewNextSelectAct viewRightTabGrp = QActionGroup(self) viewOutputAct = QAction(_('Show Data &Output'), viewRightTabGrp, statusTip=_('Show data output in right view'), checkable=True) self.winActions['ViewDataOutput'] = viewOutputAct viewEditAct = QAction(_('Show Data &Editor'), viewRightTabGrp, statusTip=_('Show data editor in right view'), checkable=True) self.winActions['ViewDataEditor'] = viewEditAct viewTitleAct = QAction(_('Show &Title List'), viewRightTabGrp, statusTip=_('Show title list in right view'), checkable=True) self.winActions['ViewTitleList'] = viewTitleAct self.rightTabActList = [viewOutputAct, viewEditAct, viewTitleAct] viewRightTabGrp.triggered.connect(self.viewRightTab) viewBreadcrumbAct = QAction(_('Show &Breadcrumb View'), self, statusTip=_('Toggle showing breadcrumb ancestor view'), checkable=True) viewBreadcrumbAct.setChecked(globalref. genOptions['InitShowBreadcrumb']) viewBreadcrumbAct.triggered.connect(self.viewBreadcrumb) self.winActions['ViewBreadcrumb'] = viewBreadcrumbAct viewChildPaneAct = QAction(_('&Show Child Pane'), self, statusTip=_('Toggle showing right-hand child views'), checkable=True) viewChildPaneAct.setChecked(globalref.genOptions['InitShowChildPane']) viewChildPaneAct.triggered.connect(self.viewShowChildPane) self.winActions['ViewShowChildPane'] = viewChildPaneAct viewDescendAct = QAction(_('Show Output &Descendants'), self, statusTip=_('Toggle showing output view indented descendants'), checkable=True) viewDescendAct.setChecked(globalref.genOptions['InitShowDescendants']) viewDescendAct.triggered.connect(self.viewDescendants) self.winActions['ViewShowDescend'] = viewDescendAct winCloseAct = QAction(_('&Close Window'), self, statusTip=_('Close this window')) winCloseAct.triggered.connect(self.close) self.winActions['WinCloseWindow'] = winCloseAct incremSearchStartAct = QAction(_('Start Incremental Search'), self) incremSearchStartAct.triggered.connect(self.incremSearchStart) self.addAction(incremSearchStartAct) self.winActions['IncremSearchStart'] = incremSearchStartAct incremSearchNextAct = QAction(_('Next Incremental Search'), self) incremSearchNextAct.triggered.connect(self.incremSearchNext) self.addAction(incremSearchNextAct) self.winActions['IncremSearchNext'] = incremSearchNextAct incremSearchPrevAct = QAction(_('Previous Incremental Search'), self) incremSearchPrevAct.triggered.connect(self.incremSearchPrev) self.addAction(incremSearchPrevAct) self.winActions['IncremSearchPrev'] = incremSearchPrevAct for name, action in self.winActions.items(): icon = globalref.toolIcons.getIcon(name.lower()) if icon: action.setIcon(icon) key = globalref.keyboardOptions[name] if not key.isEmpty(): action.setShortcut(key) self.allActions.update(self.winActions) def setupToolbars(self): """Add toolbars based on option settings. """ for toolbar in self.toolbars: self.removeToolBar(toolbar) self.toolbars = [] numToolbars = globalref.toolbarOptions['ToolbarQuantity'] iconSize = globalref.toolbarOptions['ToolbarSize'] for num in range(numToolbars): name = 'Toolbar{:d}'.format(num) toolbar = self.addToolBar(name) toolbar.setObjectName(name) toolbar.setIconSize(QSize(iconSize, iconSize)) self.toolbars.append(toolbar) self.addToolbarCommands() def addToolbarCommands(self): """Add toolbar commands for current actions. """ for toolbar, commandList in zip(self.toolbars, globalref. toolbarOptions['ToolbarCommands']): toolbar.clear() for command in commandList.split(','): if command: try: toolbar.addAction(self.allActions[command]) except KeyError: pass else: toolbar.addSeparator() def setupMenus(self): """Add menu items for actions. """ self.fileMenu = self.menuBar().addMenu(_('&File')) self.fileMenu.aboutToShow.connect(self.loadRecentMenu) self.fileMenu.addAction(self.allActions['FileNew']) self.fileMenu.addAction(self.allActions['FileOpen']) self.fileMenu.addAction(self.allActions['FileOpenSample']) self.fileMenu.addAction(self.allActions['FileImport']) self.fileMenu.addSeparator() self.fileMenu.addAction(self.allActions['FileSave']) self.fileMenu.addAction(self.allActions['FileSaveAs']) self.fileMenu.addAction(self.allActions['FileExport']) self.fileMenu.addAction(self.allActions['FileProperties']) self.fileMenu.addSeparator() self.fileMenu.addAction(self.allActions['FilePrintSetup']) self.fileMenu.addAction(self.allActions['FilePrintPreview']) self.fileMenu.addAction(self.allActions['FilePrint']) self.fileMenu.addAction(self.allActions['FilePrintPdf']) self.fileMenu.addSeparator() self.recentFileSep = self.fileMenu.addSeparator() self.fileMenu.addAction(self.allActions['FileQuit']) editMenu = self.menuBar().addMenu(_('&Edit')) editMenu.addAction(self.allActions['EditUndo']) editMenu.addAction(self.allActions['EditRedo']) editMenu.addSeparator() editMenu.addAction(self.allActions['EditCut']) editMenu.addAction(self.allActions['EditCopy']) editMenu.addSeparator() editMenu.addAction(self.allActions['EditPaste']) editMenu.addAction(self.allActions['EditPastePlain']) editMenu.addSeparator() editMenu.addAction(self.allActions['EditPasteChild']) editMenu.addAction(self.allActions['EditPasteBefore']) editMenu.addAction(self.allActions['EditPasteAfter']) editMenu.addSeparator() editMenu.addAction(self.allActions['EditPasteCloneChild']) editMenu.addAction(self.allActions['EditPasteCloneBefore']) editMenu.addAction(self.allActions['EditPasteCloneAfter']) nodeMenu = self.menuBar().addMenu(_('&Node')) nodeMenu.addAction(self.allActions['NodeRename']) nodeMenu.addSeparator() nodeMenu.addAction(self.allActions['NodeAddChild']) nodeMenu.addAction(self.allActions['NodeInsertBefore']) nodeMenu.addAction(self.allActions['NodeInsertAfter']) nodeMenu.addSeparator() nodeMenu.addAction(self.allActions['NodeDelete']) nodeMenu.addAction(self.allActions['NodeIndent']) nodeMenu.addAction(self.allActions['NodeUnindent']) nodeMenu.addSeparator() nodeMenu.addAction(self.allActions['NodeMoveUp']) nodeMenu.addAction(self.allActions['NodeMoveDown']) nodeMenu.addAction(self.allActions['NodeMoveFirst']) nodeMenu.addAction(self.allActions['NodeMoveLast']) dataMenu = self.menuBar().addMenu(_('&Data')) # add action's parent to get the sub-menu dataMenu.addMenu(self.allActions['DataNodeType'].parent()) # add the action to activate the shortcut key self.addAction(self.allActions['DataNodeType']) dataMenu.addAction(self.allActions['DataConfigType']) dataMenu.addAction(self.allActions['DataCopyType']) dataMenu.addAction(self.allActions['DataVisualConfig']) dataMenu.addSeparator() dataMenu.addAction(self.allActions['DataSortNodes']) dataMenu.addAction(self.allActions['DataNumbering']) dataMenu.addAction(self.allActions['DataRegenRefs']) dataMenu.addSeparator() dataMenu.addAction(self.allActions['DataCloneMatches']) dataMenu.addAction(self.allActions['DataDetachClones']) dataMenu.addSeparator() dataMenu.addAction(self.allActions['DataFlatCategory']) dataMenu.addAction(self.allActions['DataAddCategory']) dataMenu.addAction(self.allActions['DataSwapCategory']) toolsMenu = self.menuBar().addMenu(_('&Tools')) toolsMenu.addAction(self.allActions['ToolsFindText']) toolsMenu.addAction(self.allActions['ToolsFindCondition']) toolsMenu.addAction(self.allActions['ToolsFindReplace']) toolsMenu.addSeparator() toolsMenu.addAction(self.allActions['ToolsFilterText']) toolsMenu.addAction(self.allActions['ToolsFilterCondition']) toolsMenu.addSeparator() toolsMenu.addAction(self.allActions['ToolsSpellCheck']) toolsMenu.addSeparator() toolsMenu.addAction(self.allActions['ToolsGenOptions']) toolsMenu.addSeparator() toolsMenu.addAction(self.allActions['ToolsShortcuts']) toolsMenu.addAction(self.allActions['ToolsToolbars']) toolsMenu.addAction(self.allActions['ToolsFonts']) toolsMenu.addAction(self.allActions['ToolsColors']) formatMenu = self.menuBar().addMenu(_('Fo&rmat')) formatMenu.addAction(self.allActions['FormatBoldFont']) formatMenu.addAction(self.allActions['FormatItalicFont']) formatMenu.addAction(self.allActions['FormatUnderlineFont']) formatMenu.addSeparator() # add action's parent to get the sub-menu formatMenu.addMenu(self.allActions['FormatFontSize'].parent()) # add the action to activate the shortcut key self.addAction(self.allActions['FormatFontSize']) formatMenu.addAction(self.allActions['FormatFontColor']) formatMenu.addSeparator() formatMenu.addAction(self.allActions['FormatExtLink']) formatMenu.addAction(self.allActions['FormatIntLink']) formatMenu.addSeparator() formatMenu.addAction(self.allActions['FormatSelectAll']) formatMenu.addAction(self.allActions['FormatClearFormat']) viewMenu = self.menuBar().addMenu(_('&View')) viewMenu.addAction(self.allActions['ViewExpandBranch']) viewMenu.addAction(self.allActions['ViewCollapseBranch']) viewMenu.addSeparator() viewMenu.addAction(self.allActions['ViewPrevSelect']) viewMenu.addAction(self.allActions['ViewNextSelect']) viewMenu.addSeparator() viewMenu.addAction(self.allActions['ViewDataOutput']) viewMenu.addAction(self.allActions['ViewDataEditor']) viewMenu.addAction(self.allActions['ViewTitleList']) viewMenu.addSeparator() viewMenu.addAction(self.allActions['ViewBreadcrumb']) viewMenu.addAction(self.allActions['ViewShowChildPane']) viewMenu.addAction(self.allActions['ViewShowDescend']) self.windowMenu = self.menuBar().addMenu(_('&Window')) self.windowMenu.aboutToShow.connect(self.loadWindowMenu) self.windowMenu.addAction(self.allActions['WinNewWindow']) self.windowMenu.addAction(self.allActions['WinCloseWindow']) self.windowMenu.addSeparator() helpMenu = self.menuBar().addMenu(_('&Help')) helpMenu.addAction(self.allActions['HelpBasic']) helpMenu.addAction(self.allActions['HelpFull']) helpMenu.addSeparator() helpMenu.addAction(self.allActions['HelpAbout']) def viewExpandBranch(self): """Expand all children of the selected spots. """ QApplication.setOverrideCursor(Qt.WaitCursor) selectedSpots = self.treeView.selectionModel().selectedSpots() if not selectedSpots: selectedSpots = self.treeView.model().treeStructure.rootSpots() for spot in selectedSpots: self.treeView.expandBranch(spot) QApplication.restoreOverrideCursor() def viewCollapseBranch(self): """Collapse all children of the selected spots. """ QApplication.setOverrideCursor(Qt.WaitCursor) selectedSpots = self.treeView.selectionModel().selectedSpots() if not selectedSpots: selectedSpots = self.treeView.model().treeStructure.rootSpots() for spot in selectedSpots: self.treeView.collapseBranch(spot) QApplication.restoreOverrideCursor() def viewPrevSelect(self): """Return to the previous tree selection. """ self.treeView.selectionModel().restorePrevSelect() def viewNextSelect(self): """Go to the next tree selection in history. """ self.treeView.selectionModel().restoreNextSelect() def viewRightTab(self, action): """Show the tab in the right-hand view given by action. Arguments: action -- the action triggered in the action group """ if action == self.allActions['ViewDataOutput']: self.rightTabs.setCurrentWidget(self.outputSplitter) elif action == self.allActions['ViewDataEditor']: self.rightTabs.setCurrentWidget(self.editorSplitter) else: self.rightTabs.setCurrentWidget(self.titleSplitter) def viewBreadcrumb(self, checked): """Enable or disable the display of the breadcrumb view. Arguments: checked -- True if to be shown, False if to be hidden """ self.breadcrumbView.setVisible(checked) if checked: self.updateRightViews() def viewShowChildPane(self, checked): """Enable or disable the display of children in a split pane. Arguments: checked -- True if to be shown, False if to be hidden """ for tabNum in range(3): for splitNum in range(2): view = self.rightTabs.widget(tabNum).widget(splitNum) view.hideChildView = not checked self.updateRightViews() def viewDescendants(self, checked): """Set the output view to show indented descendants if checked. Arguments: checked -- True if to be shown, False if to be hidden """ self.outputSplitter.widget(1).showDescendants = checked self.updateRightViews() def incremSearchStart(self): """Start an incremental title search. """ if not self.treeFilterView: self.treeView.setFocus() self.treeView.incremSearchStart() def incremSearchNext(self): """Go to the next match in an incremental title search. """ if not self.treeFilterView: self.treeView.incremSearchNext() def incremSearchPrev(self): """Go to the previous match in an incremental title search. """ if not self.treeFilterView: self.treeView.incremSearchPrev() def loadRecentMenu(self): """Load recent file items to file menu before showing. """ for action in self.fileMenu.actions(): text = action.text() if len(text) > 1 and text[0] == '&' and '0' <= text[1] <= '9': self.fileMenu.removeAction(action) self.fileMenu.insertActions(self.recentFileSep, globalref.mainControl.recentFiles. getActions()) def loadWindowMenu(self): """Load window list items to window menu before showing. """ for action in self.windowMenu.actions(): text = action.text() if len(text) > 1 and text[0] == '&' and '0' <= text[1] <= '9': self.windowMenu.removeAction(action) self.windowMenu.addActions(globalref.mainControl.windowActions()) def saveWindowGeom(self): """Save window geometry parameters to history options. """ contentsRect = self.geometry() frameRect = self.frameGeometry() globalref.histOptions.changeValue('WindowXSize', contentsRect.width()) globalref.histOptions.changeValue('WindowYSize', contentsRect.height()) globalref.histOptions.changeValue('WindowXPos', contentsRect.x()) globalref.histOptions.changeValue('WindowYPos', contentsRect.y()) globalref.histOptions.changeValue('WindowTopMargin', contentsRect.y() - frameRect.y()) globalref.histOptions.changeValue('WindowOtherMargin', contentsRect.x() - frameRect.x()) try: upperWidth, lowerWidth = self.breadcrumbSplitter.sizes() crumbPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) globalref.histOptions.changeValue('CrumbSplitPercent', crumbPercent) leftWidth, rightWidth = self.treeSplitter.sizes() treePercent = int(100 * leftWidth / (leftWidth + rightWidth)) globalref.histOptions.changeValue('TreeSplitPercent', treePercent) upperWidth, lowerWidth = self.outputSplitter.sizes() outputPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) globalref.histOptions.changeValue('OutputSplitPercent', outputPercent) upperWidth, lowerWidth = self.editorSplitter.sizes() editorPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) globalref.histOptions.changeValue('EditorSplitPercent', editorPercent) upperWidth, lowerWidth = self.titleSplitter.sizes() titlePercent = int(100 * upperWidth / (upperWidth + lowerWidth)) globalref.histOptions.changeValue('TitleSplitPercent', titlePercent) except ZeroDivisionError: pass # skip if splitter sizes were never set tabNum = self.rightTabs.currentIndex() globalref.histOptions.changeValue('ActiveRightView', tabNum) def restoreWindowGeom(self, offset=0): """Restore window geometry from history options. Arguments: offset -- number of pixels to offset window, down and to right """ rect = QRect(globalref.histOptions['WindowXPos'], globalref.histOptions['WindowYPos'], globalref.histOptions['WindowXSize'], globalref.histOptions['WindowYSize']) if rect.x() == -1000 and rect.y() == -1000: # let OS position window the first time self.resize(rect.size()) else: if offset: rect.adjust(offset, offset, offset, offset) availRect = QApplication.primaryScreen().availableVirtualGeometry() topMargin = globalref.histOptions['WindowTopMargin'] otherMargin = globalref.histOptions['WindowOtherMargin'] # remove frame space from available rect availRect.adjust(otherMargin, topMargin, -otherMargin, -otherMargin) finalRect = rect.intersected(availRect) if finalRect.isEmpty(): rect.moveTo(0, 0) finalRect = rect.intersected(availRect) if finalRect.isValid(): self.setGeometry(finalRect) crumbWidth = int(self.breadcrumbSplitter.width() / 100 * globalref.histOptions['CrumbSplitPercent']) self.breadcrumbSplitter.setSizes([crumbWidth, self.breadcrumbSplitter.width() - crumbWidth]) treeWidth = int(self.treeSplitter.width() / 100 * globalref.histOptions['TreeSplitPercent']) self.treeSplitter.setSizes([treeWidth, self.treeSplitter.width() - treeWidth]) outHeight = int(self.outputSplitter.height() / 100.0 * globalref.histOptions['OutputSplitPercent']) self.outputSplitter.setSizes([outHeight, self.outputSplitter.height() - outHeight]) editHeight = int(self.editorSplitter.height() / 100.0 * globalref.histOptions['EditorSplitPercent']) self.editorSplitter.setSizes([editHeight, self.editorSplitter.height() - editHeight]) titleHeight = int(self.titleSplitter.height() / 100.0 * globalref.histOptions['TitleSplitPercent']) self.titleSplitter.setSizes([titleHeight, self.titleSplitter.height() - titleHeight]) self.rightTabs.setCurrentIndex(globalref. histOptions['ActiveRightView']) def resetWindowGeom(self): """Set all stored window geometry values back to default settings. """ globalref.histOptions.resetToDefaults(['WindowXPos', 'WindowYPos', 'WindowXSize', 'WindowYSize', 'CrumbSplitPercent', 'TreeSplitPercent', 'OutputSplitPercent', 'EditorSplitPercent', 'TitleSplitPercent', 'ActiveRightView']) def saveToolbarPosition(self): """Save the toolbar position to the toolbar options. """ toolbarPos = base64.b64encode(self.saveState().data()).decode('ascii') globalref.toolbarOptions.changeValue('ToolbarPosition', toolbarPos) globalref.toolbarOptions.writeFile() def restoreToolbarPosition(self): """Restore the toolbar position from the toolbar options. """ toolbarPos = globalref.toolbarOptions['ToolbarPosition'] if toolbarPos: self.restoreState(base64.b64decode(bytes(toolbarPos, 'ascii'))) def dragEnterEvent(self, event): """Accept drags of files to this window. Arguments: event -- the drag event object """ if event.mimeData().hasUrls(): event.accept() def dropEvent(self, event): """Open a file dropped onto this window. Arguments: event -- the drop event object """ fileList = event.mimeData().urls() if fileList: path = pathlib.Path(fileList[0].toLocalFile()) globalref.mainControl.openFile(path, checkModified=True) def changeEvent(self, event): """Detect an activation of the main window and emit a signal. Arguments: event -- the change event object """ super().changeEvent(event) if (event.type() == QEvent.ActivationChange and QApplication.activeWindow() == self): self.winActivated.emit(self) elif (event.type() == QEvent.WindowStateChange and globalref.genOptions['MinToSysTray'] and self.isMinimized()): self.winMinimized.emit() def closeEvent(self, event): """Signal that the view is closing and close if the flag allows it. Also save window status if necessary. Arguments: event -- the close event object """ self.winClosing.emit(self) if self.allowCloseFlag: event.accept() else: event.ignore()
class QWLoggerStd(QWidget): _name = 'QWLoggerStd' def __init__(self, **kwargs): QWidget.__init__(self, parent=None) self.log_level = kwargs.get('log_level', 'DEBUG') self.show_buttons = kwargs.get('show_buttons', True) self.instrument = kwargs.get('instrument', 'NonDefined') self.log_fname = log_file_name(kwargs.get('log_prefix', '.')) if self.instrument is None: self.instrument='None' if self.log_level=='DEBUG': print('%s.__init__ log_fname: %s' % (self._name, self.log_fname)) if self.log_fname is not None: #depth = 6 if self.log_fname[0]=='/' else 1 gu.create_path(self.log_fname, mode=0o0777) #print('Log file: %s' % log_fname) #cp.qwloggerstd = self #logger.debug('logging.DEBUG: ', logging.DEBUG) logger.debug('logging._levelToName: ', logging._levelToName) # {0: 'NOTSET', 50: 'CRITICAL', 20: 'INFO',... logger.debug('logging._nameToLevel: ', logging._nameToLevel) # {'NOTSET': 0, 'ERROR': 40, 'WARNING': 30,... self.dict_level_to_name = logging._levelToName self.dict_name_to_level = logging._nameToLevel self.level_names = list(logging._levelToName.values()) self.edi_txt = QTextEdit('ALL messages') #self.edi_err = QTextEdit('Error messages') self.edi_err = QWLoggerError() self.lab_level = QLabel('Log level:') self.but_close = QPushButton('&Close') self.but_save = QPushButton('&Save log-file') self.but_rand = QPushButton('&Random') self.cmb_level = QComboBox(self) self.cmb_level.addItems(self.level_names) self.cmb_level.setCurrentIndex(self.level_names.index(self.log_level)) self.hboxB = QHBoxLayout() self.hboxB.addStretch(4) self.hboxB.addWidget(self.lab_level) self.hboxB.addWidget(self.cmb_level) self.hboxB.addWidget(self.but_rand) self.hboxB.addStretch(1) self.hboxB.addWidget(self.but_save) self.hboxB.addWidget(self.but_close) self.vspl = QSplitter(Qt.Vertical) self.vspl.addWidget(self.edi_txt) self.vspl.addWidget(self.edi_err) self.vbox = QVBoxLayout() self.vbox.addWidget(self.vspl) self.vbox.addLayout(self.hboxB) self.setLayout(self.vbox) if self.show_buttons: self.connect_buttons() self.set_style() self.set_tool_tips() self.config_logger() def config_logger(self): levname = self.log_level level = self.dict_name_to_level.get(levname, logging.DEBUG) self.syslog = SysLog(instrument=self.instrument, level=level) for h in logger.handlers: print('XXX handler:', str(h)) tsfmt='%Y-%m-%dT%H:%M:%S' fmt = '%(levelname)s %(asctime)s %(name)s: %(message)s' if level==logging.DEBUG else\ '%(asctime)s %(levelname)s: %(message)s' #'%(asctime)s %(levelname)s %(name)s: %(message)s' #sys.stdout = sys.stderr = open('/dev/null', 'w') self.formatter = logging.Formatter(fmt, datefmt=tsfmt) self._myfilter = QWFilter(self) #self.syslog.syslog_handler.addFilter(self._myfilter) self.syslog.console_handler.addFilter(self._myfilter) def config_logger_v0(self, log_fname='control_gui.txt'): self.append_qwlogger('Start logger\nLog file: %s' % log_fname) levname = self.log_level level = self.dict_name_to_level.get(levname, logging.DEBUG) # e.g. logging.DEBUG #print('YYYYY config_logger levname:', levname, ' level:', level, ' logging.DEBUG:', logging.DEBUG) tsfmt='%Y-%m-%dT%H:%M:%S' fmt = '%(levelname)s %(name)s: %(message)s' if level==logging.DEBUG else\ '%(asctime)s %(levelname)s: %(message)s' #'%(asctime)s %(levelname)s %(name)s: %(message)s' #sys.stdout = sys.stderr = open('/dev/null', 'w') self.formatter = logging.Formatter(fmt, datefmt=tsfmt) #logger.addFilter(QWFilter(self)) # register self for callback from filter # TRICK: add filter to handler to intercept ALL messages fname = log_fname if log_fname is not None else '/var/tmp/control_gui_%s.log' % gu.get_login() do_save_log_file = (log_fname is not None) or (level==logging.DEBUG) self.handler = logging.FileHandler(fname, 'w') if do_save_log_file else\ logging.StreamHandler() #self.handler = logging.FileHandler(fname, 'w') #self.handler = logging.StreamHandler() self._myfilter = QWFilter(self) self.handler.addFilter(self._myfilter) #self.handler.setLevel(logging.NOTSET) # level self.handler.setFormatter(self.formatter) logger.addHandler(self.handler) self.set_level(levname) # pass level name print('logging.FileHandler file: %s' % fname) def set_level(self, level_name='DEBUG'): #self.append_qwlogger('Set logger layer: %s' % level_name) #logger.setLevel(level_name) # {0: 'NOTSET'} level = self.dict_name_to_level.get(level_name, logging.DEBUG) logger.setLevel(level) #msg = 'Set logger level %s of the list: %s' % (level_name, ', '.join(self.level_names)) #logger.debug(msg) logger.info('Set logger level %s' % level_name) def connect_buttons(self): self.but_close.clicked.connect(self.on_but_close) self.but_save.clicked.connect(self.on_but_save) self.but_rand.clicked.connect(self.on_but_rand) self.cmb_level.currentIndexChanged[int].connect(self.on_cmb_level) def disconnect_buttons(self): self.but_close.clicked.disconnect(self.on_but_close) self.but_save.clicked.disconnect(self.on_but_save) self.but_rand.clicked.disconnect(self.on_but_rand) self.cmb_level.currentIndexChanged[int].disconnect(self.on_cmb_level) def set_tool_tips(self): #self .setToolTip('This GUI is for browsing log messages') self.edi_txt .setToolTip('Window for ALL messages') self.edi_err .setToolTip('Window for ERROR messages') self.but_close .setToolTip('Close this window') self.but_save .setToolTip('Save logger content in file')#: '+os.path.basename(self.fname_log.value())) self.but_rand .setToolTip('Inject random message') self.cmb_level .setToolTip('Select logger level of messages to display') def set_style(self): self. setStyleSheet(style.styleBkgd) #self.lab_title.setStyleSheet(style.styleTitleBold) self.lab_level .setStyleSheet(style.styleTitle) self.but_close .setStyleSheet(style.styleButton) self.but_save .setStyleSheet(style.styleButton) self.but_rand .setStyleSheet(style.styleButton) self.cmb_level .setStyleSheet(style.styleButton) self.edi_txt .setReadOnly(True) self.edi_txt .setStyleSheet(style.styleWhiteFixed) #self.edi_err .setReadOnly(True) #self.edi_err .setStyleSheet(style.styleYellowish) #self.edi_txt .ensureCursorVisible() #self.lab_title.setAlignment(QtCore.Qt.AlignCenter) #self.titTitle.setBold() self.lab_level .setVisible(self.show_buttons) self.cmb_level .setVisible(self.show_buttons) self.but_save .setVisible(self.show_buttons) self.but_rand .setVisible(self.show_buttons) self.but_close .setVisible(self.show_buttons) #if not self.show_buttons: self.layout().setContentsMargins(2,2,2,2) self.edi_err.setMinimumHeight(50) self.edi_err.setTextColor(MSG_LEVEL_TO_TEXT_COLOR['<E>']) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) edi_err_hight = 100 self.vspl.setSizes((self.vspl.height()-edi_err_hight, edi_err_hight,)) #-------------------- def sizeHint(self): return QSize(300,300) #-------------------- #def setParent(self,parent): # self.parent = parent #def resizeEvent(self, e): #logger.debug('resizeEvent') #pass #def moveEvent(self, e): #logger.debug('moveEvent') #self.cp.posGUIMain = (self.pos().x(),self.pos().y()) #pass def closeEvent(self, e): logger.info('%s.closeEvent' % self._name) #self.save_log_total_in_file() # It will be saved at closing of GUIMain #self.syslog.syslog_handler.removeFilter(self._myfilter) self.syslog.console_handler.removeFilter(self._myfilter) #self.handler.removeFilter(self._myfilter) #self.handler.close() QWidget.closeEvent(self, e) #logging.shutdown() #print('Exit QWLoggerStd.closeEvent') def on_but_close(self): logger.debug('on_but_close') self.close() def on_but_save(self): logger.debug('on_but_save:') self.save_log_in_file() def on_but_rand(self): levels = self.level_names level_name = levels[randint(0, len(levels)-1)] self.append_qwlogger('===> Inject in logger random message of level %s' % level_name) ind = self.dict_name_to_level[level_name] logger.log(ind, 'This is a random message of level %s' % level_name) def on_cmb_level(self): selected = str(self.cmb_level.currentText()) msg = 'on_cmb_level set %s %s' % (self.lab_level.text(), selected) logger.debug(msg) #logger.log(0,msg) self.log_level = selected self.set_level(selected) #self.edi_txt.setText('Start logging messages in QWLoggerStd') #logger.getLogContent()) def save_log_in_file(self): logger.info('save_log_in_file ' + self.log_fname) resp = QFileDialog.getSaveFileName(self, caption = 'Select the file to save log', directory = self.log_fname, filter = '*.txt' ) logger.debug('save_log_in_file resp: %s' % str(resp)) path, ftype = resp if path == '': logger.debug('Saving is cancelled.') return self.log_fname = path logger.info('Output file: ' + path) #logger.save_log_in_file(path) #def save_log_total_in_file(self): # logger.info('save_log_total_in_file' + self.fname_log_total, self._name) # logger.save_log_total_in_file(self.fname_log_total) def set_msg_style(self, levelname): #print('QWLoggerStd.set_msg_style for level %s' % levelname) textcolor = MSG_LEVEL_TO_TEXT_COLOR.get(levelname, Qt.magenta) self.edi_txt.setTextColor(textcolor) #self.edi_txt.setTextBackgroundColor(bkgdcolor) #bkgdcolor = MSG_LEVEL_TO_BKGD_COLOR.get(levelname, Qt.magenta) def append_qwlogger(self, msg='...'): self.edi_txt.append(msg) self.scroll_down_txt() def add_separator(self, sep='\n\n\n\n\n%s'%(50*'_')): self.append_qwlogger(msg=sep) def append_qwlogger_err(self, msg='...'): self.edi_err.append(msg) self.edi_err.scroll_down() def add_separator_err(self, sep='\n\n\n\n\n%s'%(50*'_')): self.append_qwlogger_err(msg=sep) def scroll_down_txt(self): #logger.debug('scroll_down_txt') self.edi_txt.moveCursor(QTextCursor.End) self.edi_txt.repaint() if __name__ == "__main__": def key_usage(self): return 'Keys:'\ '\n ESC - exit'\ '\n A - add separator in main logger window'\ '\n S - add separator in error logger window'\ '\n' def keyPressEvent(self, e): #logger.info('keyPressEvent, key=', e.key()) if e.key() == Qt.Key_Escape: self.close() elif e.key() == Qt.Key_S: self.add_separator_err() elif e.key() == Qt.Key_A: self.add_separator() else: logger.info(self.key_usage())
class EntryView(BaseTransactionView): def _setup(self): self._setupUi() self.etable = EntryTable(self.model.etable, view=self.tableView) self.efbar = EntryFilterBar(model=self.model.filter_bar, view=self.filterBar) self.bgraph = Chart(self.model.bargraph, view=self.barGraphView) self.lgraph = Chart(self.model.balgraph, view=self.lineGraphView) self._setupColumns( ) # Can only be done after the model has been connected self.reconciliationButton.clicked.connect( self.model.toggle_reconciliation_mode) def _setupUi(self): self.resize(483, 423) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) self.filterBar = RadioBox(self) sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.filterBar.sizePolicy().hasHeightForWidth()) self.filterBar.setSizePolicy(sizePolicy) self.horizontalLayout.addWidget(self.filterBar) self.horizontalLayout.addItem(horizontalSpacer()) self.reconciliationButton = QPushButton(tr("Reconciliation")) self.reconciliationButton.setCheckable(True) self.horizontalLayout.addWidget(self.reconciliationButton) self.verticalLayout.addLayout(self.horizontalLayout) self.splitterView = QSplitter() self.splitterView.setOrientation(Qt.Vertical) self.splitterView.setChildrenCollapsible(False) self.tableView = TableView(self) self.tableView.setAcceptDrops(True) self.tableView.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed) self.tableView.setDragEnabled(True) self.tableView.setDragDropMode(QAbstractItemView.InternalMove) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setSortingEnabled(True) self.tableView.horizontalHeader().setHighlightSections(False) self.tableView.horizontalHeader().setMinimumSectionSize(18) self.tableView.verticalHeader().setVisible(False) self.tableView.verticalHeader().setDefaultSectionSize(18) self.splitterView.addWidget(self.tableView) self.graphView = QStackedWidget(self) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.graphView.sizePolicy().hasHeightForWidth()) self.graphView.setSizePolicy(sizePolicy) self.graphView.setMinimumSize(0, 200) self.lineGraphView = LineGraphView() self.graphView.addWidget(self.lineGraphView) self.barGraphView = BarGraphView() self.graphView.addWidget(self.barGraphView) self.splitterView.addWidget(self.graphView) self.graphView.setCurrentIndex(1) self.splitterView.setStretchFactor(0, 1) self.splitterView.setStretchFactor(1, 0) self.verticalLayout.addWidget(self.splitterView) def _setupColumns(self): h = self.tableView.horizontalHeader() h.setSectionsMovable(True) # column drag & drop reorder # --- QWidget override def setFocus(self): self.etable.view.setFocus() # --- Public def fitViewsForPrint(self, viewPrinter): hidden = self.model.mainwindow.hidden_areas viewPrinter.fitTable(self.etable) if PaneArea.BottomGraph not in hidden: viewPrinter.fit(self.graphView.currentWidget(), 300, 150, expandH=True, expandV=True) def restoreSubviewsSize(self): graphHeight = self.model.graph_height_to_restore if graphHeight: splitterHeight = self.splitterView.height() sizes = [splitterHeight - graphHeight, graphHeight] self.splitterView.setSizes(sizes) # --- model --> view def refresh_reconciliation_button(self): if self.model.can_toggle_reconciliation_mode: self.reconciliationButton.setEnabled(True) self.reconciliationButton.setChecked( self.model.reconciliation_mode) else: self.reconciliationButton.setEnabled(False) self.reconciliationButton.setChecked(False) def show_bar_graph(self): self.graphView.setCurrentIndex(1) def show_line_graph(self): self.graphView.setCurrentIndex(0) def update_visibility(self): hidden = self.model.mainwindow.hidden_areas self.graphView.setHidden(PaneArea.BottomGraph in hidden)
class EntryView(BaseTransactionView): def _setup(self): self._setupUi() self.etable = EntryTable(self.model.etable, view=self.tableView) self.efbar = EntryFilterBar(model=self.model.filter_bar, view=self.filterBar) self.bgraph = Chart(self.model.bargraph, view=self.barGraphView) self.lgraph = Chart(self.model.balgraph, view=self.lineGraphView) self._setupColumns() # Can only be done after the model has been connected self.reconciliationButton.clicked.connect(self.model.toggle_reconciliation_mode) def _setupUi(self): self.resize(483, 423) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) self.filterBar = RadioBox(self) sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.filterBar.sizePolicy().hasHeightForWidth()) self.filterBar.setSizePolicy(sizePolicy) self.horizontalLayout.addWidget(self.filterBar) self.horizontalLayout.addItem(horizontalSpacer()) self.reconciliationButton = QPushButton(tr("Reconciliation")) self.reconciliationButton.setCheckable(True) self.horizontalLayout.addWidget(self.reconciliationButton) self.verticalLayout.addLayout(self.horizontalLayout) self.splitterView = QSplitter() self.splitterView.setOrientation(Qt.Vertical) self.splitterView.setChildrenCollapsible(False) self.tableView = TableView(self) self.tableView.setAcceptDrops(True) self.tableView.setEditTriggers(QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed) self.tableView.setDragEnabled(True) self.tableView.setDragDropMode(QAbstractItemView.InternalMove) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setSortingEnabled(True) self.tableView.horizontalHeader().setHighlightSections(False) self.tableView.horizontalHeader().setMinimumSectionSize(18) self.tableView.verticalHeader().setVisible(False) self.tableView.verticalHeader().setDefaultSectionSize(18) self.splitterView.addWidget(self.tableView) self.graphView = QStackedWidget(self) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.graphView.sizePolicy().hasHeightForWidth()) self.graphView.setSizePolicy(sizePolicy) self.graphView.setMinimumSize(0, 200) self.lineGraphView = LineGraphView() self.graphView.addWidget(self.lineGraphView) self.barGraphView = BarGraphView() self.graphView.addWidget(self.barGraphView) self.splitterView.addWidget(self.graphView) self.graphView.setCurrentIndex(1) self.splitterView.setStretchFactor(0, 1) self.splitterView.setStretchFactor(1, 0) self.verticalLayout.addWidget(self.splitterView) def _setupColumns(self): h = self.tableView.horizontalHeader() h.setSectionsMovable(True) # column drag & drop reorder # --- QWidget override def setFocus(self): self.etable.view.setFocus() # --- Public def fitViewsForPrint(self, viewPrinter): hidden = self.model.mainwindow.hidden_areas viewPrinter.fitTable(self.etable) if PaneArea.BottomGraph not in hidden: viewPrinter.fit(self.graphView.currentWidget(), 300, 150, expandH=True, expandV=True) def restoreSubviewsSize(self): graphHeight = self.model.graph_height_to_restore if graphHeight: splitterHeight = self.splitterView.height() sizes = [splitterHeight-graphHeight, graphHeight] self.splitterView.setSizes(sizes) # --- model --> view def refresh_reconciliation_button(self): if self.model.can_toggle_reconciliation_mode: self.reconciliationButton.setEnabled(True) self.reconciliationButton.setChecked(self.model.reconciliation_mode) else: self.reconciliationButton.setEnabled(False) self.reconciliationButton.setChecked(False) def show_bar_graph(self): self.graphView.setCurrentIndex(1) def show_line_graph(self): self.graphView.setCurrentIndex(0) def update_visibility(self): hidden = self.model.mainwindow.hidden_areas self.graphView.setHidden(PaneArea.BottomGraph in hidden)
class Listspace(QSplitter, ViewManager): """ Class implementing the listspace viewmanager class. @signal changeCaption(str) emitted if a change of the caption is necessary @signal editorChanged(str) emitted when the current editor has changed @signal editorChangedEd(Editor) emitted when the current editor has changed @signal lastEditorClosed() emitted after the last editor window was closed @signal editorOpened(str) emitted after an editor window was opened @signal editorOpenedEd(Editor) emitted after an editor window was opened @signal editorClosed(str) emitted just before an editor window gets closed @signal editorClosedEd(Editor) emitted just before an editor window gets closed @signal editorRenamed(str) emitted after an editor was renamed @signal editorRenamedEd(Editor) emitted after an editor was renamed @signal editorSaved(str) emitted after an editor window was saved @signal editorSavedEd(Editor) emitted after an editor window was saved @signal checkActions(Editor) emitted when some actions should be checked for their status @signal cursorChanged(Editor) emitted after the cursor position of the active window has changed @signal breakpointToggled(Editor) emitted when a breakpoint is toggled. @signal bookmarkToggled(Editor) emitted when a bookmark is toggled. @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled. @signal previewStateChanged(bool) emitted to signal a change in the preview state @signal editorLanguageChanged(Editor) emitted to signal a change of an editors language @signal editorTextChanged(Editor) emitted to signal a change of an editor's text @signal editorLineChanged(str,int) emitted to signal a change of an editor's current line (line is given one based) """ changeCaption = pyqtSignal(str) editorChanged = pyqtSignal(str) editorChangedEd = pyqtSignal(Editor) lastEditorClosed = pyqtSignal() editorOpened = pyqtSignal(str) editorOpenedEd = pyqtSignal(Editor) editorClosed = pyqtSignal(str) editorClosedEd = pyqtSignal(Editor) editorRenamed = pyqtSignal(str) editorRenamedEd = pyqtSignal(Editor) editorSaved = pyqtSignal(str) editorSavedEd = pyqtSignal(Editor) checkActions = pyqtSignal(Editor) cursorChanged = pyqtSignal(Editor) breakpointToggled = pyqtSignal(Editor) bookmarkToggled = pyqtSignal(Editor) syntaxerrorToggled = pyqtSignal(Editor) previewStateChanged = pyqtSignal(bool) editorLanguageChanged = pyqtSignal(Editor) editorTextChanged = pyqtSignal(Editor) editorLineChanged = pyqtSignal(str, int) def __init__(self, parent): """ Constructor @param parent parent widget (QWidget) """ self.stacks = [] QSplitter.__init__(self, parent) ViewManager.__init__(self) self.setChildrenCollapsible(False) self.viewlist = QListWidget(self) policy = self.viewlist.sizePolicy() policy.setHorizontalPolicy(QSizePolicy.Ignored) self.viewlist.setSizePolicy(policy) self.addWidget(self.viewlist) self.viewlist.setContextMenuPolicy(Qt.CustomContextMenu) self.viewlist.currentRowChanged.connect(self.__showSelectedView) self.viewlist.customContextMenuRequested.connect(self.__showMenu) self.stackArea = QSplitter(self) self.stackArea.setChildrenCollapsible(False) self.addWidget(self.stackArea) self.stackArea.setOrientation(Qt.Vertical) stack = StackedWidget(self.stackArea) self.stackArea.addWidget(stack) self.stacks.append(stack) self.currentStack = stack stack.currentChanged.connect(self.__currentChanged) stack.installEventFilter(self) self.setSizes([int(self.width() * 0.2), int(self.width() * 0.8)]) # 20% for viewlist, 80% for the editors self.__inRemoveView = False self.__initMenu() self.contextMenuEditor = None self.contextMenuIndex = -1 def __initMenu(self): """ Private method to initialize the viewlist context menu. """ self.__menu = QMenu(self) self.__menu.addAction( UI.PixmapCache.getIcon("tabClose.png"), self.tr('Close'), self.__contextMenuClose) self.closeOthersMenuAct = self.__menu.addAction( UI.PixmapCache.getIcon("tabCloseOther.png"), self.tr("Close Others"), self.__contextMenuCloseOthers) self.__menu.addAction( self.tr('Close All'), self.__contextMenuCloseAll) self.__menu.addSeparator() self.saveMenuAct = self.__menu.addAction( UI.PixmapCache.getIcon("fileSave.png"), self.tr('Save'), self.__contextMenuSave) self.__menu.addAction( UI.PixmapCache.getIcon("fileSaveAs.png"), self.tr('Save As...'), self.__contextMenuSaveAs) self.__menu.addAction( UI.PixmapCache.getIcon("fileSaveAll.png"), self.tr('Save All'), self.__contextMenuSaveAll) self.__menu.addSeparator() self.openRejectionsMenuAct = self.__menu.addAction( self.tr("Open 'rejection' file"), self.__contextMenuOpenRejections) self.__menu.addSeparator() self.__menu.addAction( UI.PixmapCache.getIcon("print.png"), self.tr('Print'), self.__contextMenuPrintFile) self.__menu.addSeparator() self.copyPathAct = self.__menu.addAction( self.tr("Copy Path to Clipboard"), self.__contextMenuCopyPathToClipboard) def __showMenu(self, point): """ Private slot to handle the customContextMenuRequested signal of the viewlist. @param point position to open the menu at (QPoint) """ if self.editors: itm = self.viewlist.itemAt(point) if itm is not None: row = self.viewlist.row(itm) self.contextMenuEditor = self.editors[row] self.contextMenuIndex = row if self.contextMenuEditor: self.saveMenuAct.setEnabled( self.contextMenuEditor.isModified()) fileName = self.contextMenuEditor.getFileName() self.copyPathAct.setEnabled(bool(fileName)) if fileName: rej = "{0}.rej".format(fileName) self.openRejectionsMenuAct.setEnabled( os.path.exists(rej)) else: self.openRejectionsMenuAct.setEnabled(False) self.closeOthersMenuAct.setEnabled( self.viewlist.count() > 1) self.__menu.popup(self.viewlist.mapToGlobal(point)) def canCascade(self): """ Public method to signal if cascading of managed windows is available. @return flag indicating cascading of windows is available """ return False def canTile(self): """ Public method to signal if tiling of managed windows is available. @return flag indicating tiling of windows is available """ return False def canSplit(self): """ public method to signal if splitting of the view is available. @return flag indicating splitting of the view is available. """ return True def tile(self): """ Public method to tile the managed windows. """ pass def cascade(self): """ Public method to cascade the managed windows. """ pass def _removeAllViews(self): """ Protected method to remove all views (i.e. windows). """ self.viewlist.clear() for win in self.editors: for stack in self.stacks: if stack.hasEditor(win): stack.removeWidget(win) break win.closeIt() def _removeView(self, win): """ Protected method to remove a view (i.e. window). @param win editor window to be removed """ self.__inRemoveView = True ind = self.editors.index(win) itm = self.viewlist.takeItem(ind) if itm: del itm for stack in self.stacks: if stack.hasEditor(win): stack.removeWidget(win) break win.closeIt() self.__inRemoveView = False if ind > 0: ind -= 1 else: if len(self.editors) > 1: ind = 1 else: return stack.setCurrentWidget(stack.firstEditor()) self._showView(self.editors[ind].parent()) aw = self.activeWindow() fn = aw and aw.getFileName() or None if fn: self.changeCaption.emit(fn) self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, aw.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(aw) def _addView(self, win, fn=None, noName="", next=False): """ Protected method to add a view (i.e. window). @param win editor assembly to be added @param fn filename of this editor (string) @param noName name to be used for an unnamed editor (string) @param next flag indicating to add the view next to the current view (bool) """ editor = win.getEditor() if fn is None: if not noName: self.untitledCount += 1 noName = self.tr("Untitled {0}").format(self.untitledCount) self.viewlist.addItem(noName) editor.setNoName(noName) else: txt = os.path.basename(fn) if not QFileInfo(fn).isWritable(): txt = self.tr("{0} (ro)").format(txt) itm = QListWidgetItem(txt) itm.setToolTip(fn) self.viewlist.addItem(itm) self.currentStack.addWidget(win) self.currentStack.setCurrentWidget(win) editor.captionChanged.connect(self.__captionChange) editor.cursorLineChanged.connect(self.__cursorLineChanged) index = self.editors.index(editor) self.viewlist.setCurrentRow(index) editor.setFocus() if fn: self.changeCaption.emit(fn) self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(editor) def __captionChange(self, cap, editor): """ Private method to handle caption change signals from the editor. Updates the listwidget text to reflect the new caption information. @param cap Caption for the editor (string) @param editor Editor to update the caption for """ fn = editor.getFileName() if fn: self.setEditorName(editor, fn) def __cursorLineChanged(self, lineno): """ Private slot to handle a change of the current editor's cursor line. @param lineno line number of the current editor's cursor (zero based) """ editor = self.sender() if editor: fn = editor.getFileName() if fn: self.editorLineChanged.emit(fn, lineno + 1) def _showView(self, win, fn=None): """ Protected method to show a view (i.e. window). @param win editor assembly to be shown @param fn filename of this editor (string) """ editor = win.getEditor() for stack in self.stacks: if stack.hasEditor(editor): stack.setCurrentWidget(win) self.currentStack = stack break index = self.editors.index(editor) self.viewlist.setCurrentRow(index) editor.setFocus() fn = editor.getFileName() if fn: self.changeCaption.emit(fn) self.editorChanged.emit(fn) self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(editor) def __showSelectedView(self, row): """ Private slot called to show a view selected in the list. @param row row number of the item clicked on (integer) """ if row != -1: self._showView(self.editors[row].parent()) self._checkActions(self.editors[row]) def activeWindow(self): """ Public method to return the active (i.e. current) window. @return reference to the active editor """ return self.currentStack.currentWidget() def showWindowMenu(self, windowMenu): """ Public method to set up the viewmanager part of the Window menu. @param windowMenu reference to the window menu """ pass def _initWindowActions(self): """ Protected method to define the user interface actions for window handling. """ pass def setEditorName(self, editor, newName): """ Public method to change the displayed name of the editor. @param editor editor window to be changed @param newName new name to be shown (string) """ if newName: currentRow = self.viewlist.currentRow() index = self.editors.index(editor) txt = os.path.basename(newName) if not QFileInfo(newName).isWritable(): txt = self.tr("{0} (ro)").format(txt) itm = self.viewlist.item(index) itm.setText(txt) itm.setToolTip(newName) self.viewlist.setCurrentRow(currentRow) self.changeCaption.emit(newName) def _modificationStatusChanged(self, m, editor): """ Protected slot to handle the modificationStatusChanged signal. @param m flag indicating the modification status (boolean) @param editor editor window changed """ currentRow = self.viewlist.currentRow() index = self.editors.index(editor) keys = [] if m: keys.append("fileModified.png") if editor.hasSyntaxErrors(): keys.append("syntaxError22.png") elif editor.hasWarnings(): keys.append("warning22.png") if not keys: keys.append("empty.png") self.viewlist.item(index).setIcon( UI.PixmapCache.getCombinedIcon(keys)) self.viewlist.setCurrentRow(currentRow) self._checkActions(editor) def _syntaxErrorToggled(self, editor): """ Protected slot to handle the syntaxerrorToggled signal. @param editor editor that sent the signal """ currentRow = self.viewlist.currentRow() index = self.editors.index(editor) keys = [] if editor.isModified(): keys.append("fileModified.png") if editor.hasSyntaxErrors(): keys.append("syntaxError22.png") elif editor.hasWarnings(): keys.append("warning22.png") if not keys: keys.append("empty.png") self.viewlist.item(index).setIcon( UI.PixmapCache.getCombinedIcon(keys)) self.viewlist.setCurrentRow(currentRow) ViewManager._syntaxErrorToggled(self, editor) def addSplit(self): """ Public method used to split the current view. """ stack = StackedWidget(self.stackArea) stack.show() self.stackArea.addWidget(stack) self.stacks.append(stack) self.currentStack = stack stack.currentChanged.connect(self.__currentChanged) stack.installEventFilter(self) if self.stackArea.orientation() == Qt.Horizontal: size = self.stackArea.width() else: size = self.stackArea.height() self.stackArea.setSizes( [int(size / len(self.stacks))] * len(self.stacks)) self.splitRemoveAct.setEnabled(True) self.nextSplitAct.setEnabled(True) self.prevSplitAct.setEnabled(True) def removeSplit(self): """ Public method used to remove the current split view. @return flag indicating successfull removal """ if len(self.stacks) > 1: stack = self.currentStack res = True savedEditors = stack.editors[:] for editor in savedEditors: res &= self.closeEditor(editor) if res: try: i = self.stacks.index(stack) except ValueError: return True if i == len(self.stacks) - 1: i -= 1 self.stacks.remove(stack) stack.close() self.currentStack = self.stacks[i] if len(self.stacks) == 1: self.splitRemoveAct.setEnabled(False) self.nextSplitAct.setEnabled(False) self.prevSplitAct.setEnabled(False) return True return False def getSplitOrientation(self): """ Public method to get the orientation of the split view. @return orientation of the split (Qt.Horizontal or Qt.Vertical) """ return self.stackArea.orientation() def setSplitOrientation(self, orientation): """ Public method used to set the orientation of the split view. @param orientation orientation of the split (Qt.Horizontal or Qt.Vertical) """ self.stackArea.setOrientation(orientation) def nextSplit(self): """ Public slot used to move to the next split. """ aw = self.activeWindow() _hasFocus = aw and aw.hasFocus() ind = self.stacks.index(self.currentStack) + 1 if ind == len(self.stacks): ind = 0 self.currentStack = self.stacks[ind] if _hasFocus: aw = self.activeWindow() if aw: aw.setFocus() index = self.editors.index(self.currentStack.currentWidget()) self.viewlist.setCurrentRow(index) def prevSplit(self): """ Public slot used to move to the previous split. """ aw = self.activeWindow() _hasFocus = aw and aw.hasFocus() ind = self.stacks.index(self.currentStack) - 1 if ind == -1: ind = len(self.stacks) - 1 self.currentStack = self.stacks[ind] if _hasFocus: aw = self.activeWindow() if aw: aw.setFocus() index = self.editors.index(self.currentStack.currentWidget()) self.viewlist.setCurrentRow(index) def __contextMenuClose(self): """ Private method to close the selected editor. """ if self.contextMenuEditor: self.closeEditorWindow(self.contextMenuEditor) def __contextMenuCloseOthers(self): """ Private method to close the other editors. """ index = self.contextMenuIndex for i in list(range(self.viewlist.count() - 1, index, -1)) + \ list(range(index - 1, -1, -1)): editor = self.editors[i] self.closeEditorWindow(editor) def __contextMenuCloseAll(self): """ Private method to close all editors. """ savedEditors = self.editors[:] for editor in savedEditors: self.closeEditorWindow(editor) def __contextMenuSave(self): """ Private method to save the selected editor. """ if self.contextMenuEditor: self.saveEditorEd(self.contextMenuEditor) def __contextMenuSaveAs(self): """ Private method to save the selected editor to a new file. """ if self.contextMenuEditor: self.saveAsEditorEd(self.contextMenuEditor) def __contextMenuSaveAll(self): """ Private method to save all editors. """ self.saveEditorsList(self.editors) def __contextMenuOpenRejections(self): """ Private slot to open a rejections file associated with the selected editor. """ if self.contextMenuEditor: fileName = self.contextMenuEditor.getFileName() if fileName: rej = "{0}.rej".format(fileName) if os.path.exists(rej): self.openSourceFile(rej) def __contextMenuPrintFile(self): """ Private method to print the selected editor. """ if self.contextMenuEditor: self.printEditor(self.contextMenuEditor) def __contextMenuCopyPathToClipboard(self): """ Private method to copy the file name of the selected editor to the clipboard. """ if self.contextMenuEditor: fn = self.contextMenuEditor.getFileName() if fn: cb = QApplication.clipboard() cb.setText(fn) def __currentChanged(self, index): """ Private slot to handle the currentChanged signal. @param index index of the current editor """ if index == -1 or not self.editors: return editor = self.activeWindow() if editor is None: return self._checkActions(editor) editor.setFocus() fn = editor.getFileName() if fn: self.changeCaption.emit(fn) if not self.__inRemoveView: self.editorChanged.emit(fn) self.editorLineChanged.emit( fn, editor.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(editor) cindex = self.editors.index(editor) self.viewlist.setCurrentRow(cindex) def eventFilter(self, watched, event): """ Public method called to filter the event queue. @param watched the QObject being watched @param event the event that occurred @return flag indicating, if we handled the event """ if event.type() == QEvent.MouseButtonPress and \ not event.button() == Qt.RightButton: switched = True if isinstance(watched, QStackedWidget): switched = watched is not self.currentStack self.currentStack = watched elif isinstance(watched, QScintilla.Editor.Editor): for stack in self.stacks: if stack.hasEditor(watched): switched = stack is not self.currentStack self.currentStack = stack break currentWidget = self.currentStack.currentWidget() if currentWidget: index = self.editors.index(currentWidget) self.viewlist.setCurrentRow(index) aw = self.activeWindow() if aw is not None: self._checkActions(aw) aw.setFocus() fn = aw.getFileName() if fn: self.changeCaption.emit(fn) if switched: self.editorChanged.emit(fn) self.editorLineChanged.emit( fn, aw.getCursorPosition()[0] + 1) else: self.changeCaption.emit("") self.editorChangedEd.emit(aw) return False
class WebTab(QWidget): class SavedTab(object): def __init__(self, webTab=None): self.title = '' self.url = QUrl() self.icon = QIcon() self.history = QByteArray() self.isPinned = False self.zoomLevel = 1 self.parentTab = -1 self.childTabs = [] self.sessionData = {} if webTab: self.setWebTab(webTab) def __getstate__(self): result = dict(self.__dict__) result['url'] = result['url'].toEncoded() data = QByteArray() ds = QDataStream(data, QIODevice.WriteOnly) ds.writeQVariant(self.icon) result['icon'] = data.data() return result def __setstate__(self, state): for key, val in state.items(): if key == 'url': self.__dict__[key] = QUrl.fromEncoded(val) elif key == 'icon': ds = QDataStream(QByteArray(val)) self.__dict__[key] = ds.readQVariant() else: self.__dict__[key] = val def setWebTab(self, webTab): self.title = webTab.title() self.url = webTab.url() self.icon = webTab.icon() self.history = webTab.historyData() self.isPinned = webTab.isPinned() self.zoomLevel = webTab.zoomLevel() if webTab.parentTab(): self.parentTab = webTab.parentTab().tabIndex() else: self.parentTab = -1 self.childTabs = [ tab.tabIndex() for tab in webTab.childTabs() ] self.sessionData = webTab.sessionData() def isValid(self): return not self.url.isEmpty() or not self.history.isEmpty() def clear(self): self.title = '' self.url = QUrl() self.icon = QIcon() self.history = QByteArray() self.isPinned = False self.zoomLevel = 1 self.parentTab = -1 self.childTabs = [] self.sessionData = {} # type AddChildBehavior AppendChild = 0 PrependChild = 1 s_addChildBehavior = AppendChild def __init__(self, parent=None): super(WebTab, self).__init__(parent) self.setObjectName('webtab') self._tabBar = None self._window = None self._parentTab = None self._childTabs = [] self._sessionData = {} self._savedTab = self.SavedTab() self._isPinned = False self._isCurrentTab = False self._webView = TabbedWebView(self) self._webView.setPage(WebPage()) self._webView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.setFocusProxy(self._webView) self._locationBar = LocationBar(self) self._locationBar.setWebView(self._webView) self._tabIcon = TabIcon(self) self._tabIcon.setWebTab(self) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._layout.addWidget(self._webView) viewWidget = QWidget(self) viewWidget.setLayout(self._layout) self._splitter = QSplitter(Qt.Vertical, self) self._splitter.setChildrenCollapsible(False) self._splitter.addWidget(viewWidget) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._splitter) self.setLayout(layout) self._notificationWidget = QWidget(self) self._notificationWidget.setAutoFillBackground(True) pal = self._notificationWidget.palette() pal.setColor(QPalette.Window, pal.window().color().darker(110)) self._notificationWidget.setPalette(pal) nlayout = QVBoxLayout(self._notificationWidget) nlayout.setSizeConstraint(QLayout.SetMinAndMaxSize) nlayout.setContentsMargins(0, 0, 0, 0) nlayout.setSpacing(1) self._webView.showNotification.connect(self.showNotification) self._webView.loadFinished.connect(self.loadFinished) self._webView.titleChanged.connect(self.titleWasChanged) self._webView.titleChanged.connect(self.titleChanged) self._webView.iconChanged.connect(self.iconChanged) self._webView.backgroundActivityChanged.connect(self.backgroundActivityChanged) self._webView.loadStarted.connect(lambda: self.loadingChanged.emit(True)) self._webView.loadFinished.connect(lambda: self.loadingChanged.emit(False)) def pageChanged(page): page.audioMutedChanged.connect(self.playingChanged) page.recentlyAudibleChanged.connect(self.mutedChanged) pageChanged(self._webView.page()) self._webView.pageChanged.connect(pageChanged) def tabIconResized(): if self._tabBar: self._tabBar.update() self._tabIcon.resized.connect(tabIconResized) def browserWindow(self): ''' @return BrowserWindow ''' return self._window def webView(self): ''' @return TabbedWebView ''' return self._webView def locationBar(self): ''' @return LocationBar ''' return self._locationBar def tabIcon(self): ''' @return TabIcon ''' return self._tabIcon def parentTab(self): ''' @return WebTab ''' return self._parentTab def setParentTab(self, tab): if self._isPinned or self._parentTab == tab: return if tab and tab.isPinned(): return if self._parentTab: index = self._parentTab._childTabs.index(self) if index >= 0: self._parentTab._childTabs.pop(index) self._parentTab.childTabRemoved.emit(self, index) self._parentTab = tab if tab: self._parentTab = None tab.addChildTab(self) else: self.parentTabChanged.emit(self._parentTab) def addChildTab(self, tab, index=-1): if self._isPinned or not tab or tab.isPinned(): return oldParent = tab._parentTab tab._parentTab = self if oldParent: index = oldParent._childTabs.index(tab) if index >= 0: oldParent._childTabs.pop(index) oldParent.childTabRemoved.emit(tab, index) if index < 0 or index > len(self._childTabs): index = 0 if self.addChildBehavior() == self.AppendChild: index = len(self._childTabs) else: # PrependChild index = 0 self._childTabs.insert(index, tab) self.childTabAdded.emit(tab, index) tab.parentTabChanged.emit(self) def childTabs(self): ''' @return QVector<WebTab*> ''' return self._childTabs def sessionData(self): ''' @return {} ''' return self._sessionData def setSessionData(self, key, value): self._sessionData[key] = value def url(self): if self.isRestored(): if self._webView.url().isEmpty() and self._webView.isLoading(): return self._webView.page().requestedUrl() return self._webView.url() else: return self._savedTab.url def title(self, allowEmpty=False): if self.isRestored(): return self._webView.title(allowEmpty) else: return self._savedTab.title def icon(self, allowNull=False): if self.isRestored(): return self._webView.icon(allowNull) if allowNull or not self._savedTab.icon.isNull(): return self._savedTab.icon return IconProvider.emptyWebIcon() def history(self): ''' @return QWebEngineHistory ''' return self._webView.history() def zoomLevel(self): return self._webView.zoomLevel() def setZoomLevel(self, level): self._webView.setZoomLevel(level) def detach(self): assert(self._window) assert(self._tabBar) # Remove from tab tree self.removeFromTabTree() # Remove icon from tab self._tabBar.setTabButton(self.tabIndex(), self._tabBar.iconButtonPosition(), None) self._tabIcon.setParent(self) # Remove the tab from tabbar self._window.tabWidget().removeTab(self.tabIndex()) self.setParent(None) # Remove the locationbar from window self._locationBar.setParent(self) # Detach TabbedWindow self._webView.setBrowserWindow(None) if self._isCurrentTab: self._isCurrentTab = False self.currentTabChanged.emit(self._isCurrentTab) self._tabBar.currentChanged.disconnect(self.onCurrentChanged) self._window = None self._tabBar = None def onCurrentChanged(self, index): wasCurrent = self._isCurrentTab self._isCurrentTab = index == self.tabIndex() if wasCurrent != self._isCurrentTab: self.currentTabChanged.emit(self._isCurrentTab) def attach(self, window): self._window = window self._tabBar = self._window.tabWidget().tabBar() self._webView.setBrowserWindow(self._window) self._locationBar.setBrowserWindow(self._window) self._tabBar.setTabText(self.tabIndex(), self.title()) self._tabBar.setTabButton(self.tabIndex(), self._tabBar.iconButtonPosition(), self._tabIcon) QTimer.singleShot(0, self._tabIcon.updateIcon) self.onCurrentChanged(self._tabBar.currentIndex()) self._tabBar.currentChanged.connect(self.onCurrentChanged) def historyData(self): ''' @return QByteArray ''' if self.isRestored(): historyArray = QByteArray() stream = QDataStream(historyArray, QIODevice.WriteOnly) history = self._webView.history() stream << history return historyArray else: return self._savedTab.history def stop(self): self._webView.stop() def reload(self): self._webView.reload() def load(self, request): ''' @param: requset LoadRequest ''' if self.isRestored(): self.tabActivated() QTimer.singleShot(0, lambda: self.load(request)) else: self._webView.load(request) def unload(self): self._savedTab = self.SavedTab(self) self.restoredChanged.emit(self.isRestored()) self._webView.setPage(WebPage()) self._webView.setFocus() def isLoading(self): return self._webView.isloading() def isPinned(self): return self._isPinned def setPinned(self, state): if self._isPinned == state: return if state: self.removeFromTabTree() self._isPinned = state self.pinnedChanged.emit(self._isPinned) def togglePinned(self): assert(self._tabBar) assert(self._window) self.setPinned(not self.isPinned()) self._window.tabWidget().pinUnPinTab(self.tabIndex(), self.title()) def isMuted(self): return self._webView.page().isAudioMuted() def isPlaying(self): return self._webView.page().recentlyAudible() def setMuted(self, muted): self._webView.page().setAudioMuted(muted) def toggleMuted(self): self.setMuted(not self.isMuted()) def backgroundActivity(self): return self._webView.backgroundActivity() def tabIndex(self): index = -1 if self._tabBar: index = self._tabBar.tabWidget().indexOf(self) return index def isCurrentTab(self): return self._isCurrentTab def makeCurrentTab(self): if self._tabBar: self._tabBar.tabWidget().setCurrentIndex(self.tabIndex()) def closeTab(self): if self._tabBar: self._tabBar.tabWidget().closeTab(self.tabIndex()) def moveTab(self, to): if self._tabBar: self._tabBar.tabWidget().moveTab(self.tabIndex(), to) def haveInspector(self): return self._splitter.count() > 1 and self._splitter.widget(1).inherits('WebInspector') def showWebInspector(self, inspectElement=False): if not WebInspector.isEnabled() or self.haveInspector(): return inspector = WebInspector(self) inspector.setView(self._webView) if inspectElement: inspector.inspectElement() height = inspector.sizeHint().height() self._splitter.addWidget(inspector) self._splitter.setSizes((self._splitter.height() - height, height)) def toggleWebInspector(self): if not self.haveInspector(): self.showWebInspector() else: self._splitter.widget(1).destroy() # TODO: del? def showSearchToolBar(self, searchText=''): index = 1 toolBar = None if self._layout.count() == 1: toolBar = SearchToolBar(self._webView, self) self._layout.insertWidget(index, toolBar) if self._layout.count() == 2: assert(isinstance(self._layout.itemAt(index).widget(), SearchToolBar)) toolBar = self._layout.itemAt(index).widget() assert(toolBar) if not searchText: toolBar.setText(searchText) toolBar.focusSearchLine() def isRestored(self): return not self._savedTab.isValid() def restoreTab(self, tab): ''' @param: tab SavedTab ''' assert(self._tabBar) self.setPinned(tab.isPinned) self._sessionData = tab.sessionData if not self.isPinned() and gVar.appSettings.loadTabsOnActivation: self._savedTab = tab self.restoredChanged.emit(self.isRestored()) index = self.tabIndex() self._tabBar.setTabText(index, tab.title) self._locationBar.showUrl(tab.url) self._tabIcon.updateIcon() else: # This is called only on restore session and restoring tabs # immediately crashes QtWebEngine, waiting after initialization is # complete fixes it QTimer.singleShot(1000, lambda: self.p_restoreTab(tab)) def p_restoreTab(self, tab): ''' @param: tab SavedTab ''' self.p_restoreTabByUrl(tab.url, tab.history, tab.zoomLevel) def p_restoreTabByUrl(self, url, history, zoomLevel): self._webView.load(url) # Restoring history of internal pages crashes QtWebEngine 5.8 blacklistedSchemes = ['view-source', 'chrome'] if (url.scheme() not in blacklistedSchemes): stream = QDataStream(history) stream >> self._webView.history() self._webView.setZoomLevel(zoomLevel) self._webView.setFocus() def tabActivated(self): if self.isRestored(): return def _onTabActivated(): if self.isRestored(): return self.p_restoreTab(self._savedTab) self._savedTab.clear() self.restoredChanged.emit(self.isRestored()) QTimer.singleShot(0, _onTabActivated) def addChildBehavior(self): ''' @return AddChildBehavior ''' return self.s_addChildBehavior def setAddChildBehavior(self, behavior): self.s_addChildBehavior = behavior # Q_SLOTS @pyqtSlot(QWidget) def showNotification(self, notif): self._notificationWidget.setParent(self) self._notificationWidget.raise_() self._notificationWidget.setFixedWidth(self.width()) self._notificationWidget.layout().addWidget(notif) self._notificationWidget.show() notif.show() @pyqtSlot() def loadFinished(self): self.titleWasChanged(self._webView.title()) # Q_SIGNALS titleChanged = pyqtSignal(str) # title iconChanged = pyqtSignal(QIcon) # icon pinnedChanged = pyqtSignal(bool) # pinned restoredChanged = pyqtSignal(bool) # restored currentTabChanged = pyqtSignal(bool) # current loadingChanged = pyqtSignal(bool) # loading mutedChanged = pyqtSignal(bool) # muted playingChanged = pyqtSignal(bool) # playing backgroundActivityChanged = pyqtSignal(bool) # activity parentTabChanged = pyqtSignal('PyQt_PyObject') # WebTab* childTabAdded = pyqtSignal('PyQt_PyObject', int) # WebTab*, index childTabRemoved = pyqtSignal('PyQt_PyObject', int) # WebTab*, index def titleWasChanged(self, title): if not self._tabBar or not self._window or not title: return if self._isCurrentTab: self._window.setWindowTitle('%s - Demo' % title) self._tabBar.setTabText(self.tabIndex(), title) # override def resizeEvent(self, event): QWidget.resizeEvent(self, event) self._notificationWidget.setFixedWidth(self.width()) def removeFromTabTree(self): parentTab = self._parentTab parentIndex = -1 if parentTab: parentIndex = parentTab._childTabs.index(self) self.setParentTab(None) idx = 0 while self._childTabs: child = self._childTabs[0] child.setParentTab(None) if parentTab: parentTab.addChildTab(child, parentIndex + idx) idx += 1
class GuiRingPlayerStats(QSplitter): def __init__(self, config, querylist, mainwin, debug=True): QSplitter.__init__(self, None) self.debug = debug self.conf = config self.main_window = mainwin self.sql = querylist self.liststore = [ ] # gtk.ListStore[] stores the contents of the grids self.listcols = [ ] # gtk.TreeViewColumn[][] stores the columns in the grids self.MYSQL_INNODB = 2 self.PGSQL = 3 self.SQLITE = 4 # create new db connection to avoid conflicts with other threads self.db = Database.Database(self.conf, sql=self.sql) self.cursor = self.db.cursor settings = {} settings.update(self.conf.get_db_parameters()) settings.update(self.conf.get_import_parameters()) settings.update(self.conf.get_default_paths()) # text used on screen stored here so that it can be configured self.filterText = { 'handhead': _('Hand Breakdown for all levels listed above') } filters_display = { "Heroes": True, "Sites": True, "Games": True, "Currencies": True, "Limits": True, "LimitSep": True, "LimitType": True, "Type": True, "Seats": True, "SeatSep": True, "Dates": True, "Groups": True, "GroupsAll": True, "Button1": True, "Button2": True } self.filters = Filters.Filters(self.db, display=filters_display) self.filters.registerButton1Name(_("Filters")) self.filters.registerButton1Callback(self.showDetailFilter) self.filters.registerButton2Name(_("Refresh Stats")) self.filters.registerButton2Callback(self.refreshStats) scroll = QScrollArea() scroll.setWidget(self.filters) # ToDo: store in config # ToDo: create popup to adjust column config # columns to display, keys match column name returned by sql, values in tuple are: # is column displayed(summary then position), column heading, xalignment, formatting, celltype self.columns = self.conf.get_gui_cash_stat_params() # Detail filters: This holds the data used in the popup window, extra values are # added at the end of these lists during processing # sql test, screen description, min, max self.handtests = [ # already in filter class : ['h.seats', 'Number of Players', 2, 10] ['gt.maxSeats', 'Size of Table', 2, 10], ['h.playersVpi', 'Players who VPI', 0, 10], ['h.playersAtStreet1', 'Players at Flop', 0, 10], ['h.playersAtStreet2', 'Players at Turn', 0, 10], ['h.playersAtStreet3', 'Players at River', 0, 10], ['h.playersAtStreet4', 'Players at Street7', 0, 10], ['h.playersAtShowdown', 'Players at Showdown', 0, 10], ['h.street0Raises', 'Bets to See Flop', 0, 5], ['h.street1Raises', 'Bets to See Turn', 0, 5], ['h.street2Raises', 'Bets to See River', 0, 5], ['h.street3Raises', 'Bets to See Street7', 0, 5], ['h.street4Raises', 'Bets to See Showdown', 0, 5] ] self.cardstests = [ [Card.DATABASE_FILTERS['pair'], _('Pocket pairs')], [Card.DATABASE_FILTERS['suited'], _('Suited')], [ Card.DATABASE_FILTERS['suited_connectors'], _('Suited connectors') ], [Card.DATABASE_FILTERS['offsuit'], _('Offsuit')], [ Card.DATABASE_FILTERS['offsuit_connectors'], _('Offsuit connectors') ], ] self.stats_frame = None self.stats_vbox = None self.detailFilters = [] # the data used to enhance the sql select self.cardsFilters = [] self.stats_frame = QFrame() self.stats_frame.setLayout(QVBoxLayout()) self.stats_vbox = QSplitter(Qt.Vertical) self.stats_frame.layout().addWidget(self.stats_vbox) self.addWidget(scroll) self.addWidget(self.stats_frame) self.setStretchFactor(0, 0) self.setStretchFactor(1, 1) # Make sure Hand column is not displayed. hand_column = (x for x in self.columns if x[0] == 'hand').next() hand_column[colshowsumm] = hand_column[colshowposn] = False # If rfi and steal both on for summaries, turn rfi off. rfi_column = (x for x in self.columns if x[0] == 'rfi').next() steals_column = (x for x in self.columns if x[0] == 'steals').next() if rfi_column[colshowsumm] and steals_column[colshowsumm]: rfi_column[colshowsumm] = False # If rfi and steal both on for position breakdowns, turn steals off. if rfi_column[colshowposn] and steals_column[colshowposn]: steals_column[colshowposn] = False def refreshStats(self, checkState): self.liststore = [] self.listcols = [] self.stats_frame.layout().removeWidget(self.stats_vbox) self.stats_vbox.setParent(None) self.stats_vbox = QSplitter(Qt.Vertical) self.stats_frame.layout().addWidget(self.stats_vbox) self.fillStatsFrame(self.stats_vbox) if self.liststore: topsize = self.stats_vbox.widget(0).sizeHint().height() self.stats_vbox.setSizes( [topsize, self.stats_vbox.height() - topsize]) self.stats_vbox.setStretchFactor(0, 0) self.stats_vbox.setStretchFactor(1, 1) def fillStatsFrame(self, vbox): sites = self.filters.getSites() heroes = self.filters.getHeroes() siteids = self.filters.getSiteIds() limits = self.filters.getLimits() seats = self.filters.getSeats() groups = self.filters.getGroups() dates = self.filters.getDates() games = self.filters.getGames() currencies = self.filters.getCurrencies() sitenos = [] playerids = [] # Which sites are selected? for site in sites: sitenos.append(siteids[site]) _hname = Charset.to_utf8(heroes[site]) result = self.db.get_player_id(self.conf, site, _hname) if result is not None: playerids.append(int(result)) if not sitenos: #Should probably pop up here. print _("No sites selected - defaulting to PokerStars") sitenos = [2] if not playerids: print _("No player ids found") return if not limits: print _("No limits found") return self.createStatsTable(vbox, playerids, sitenos, limits, seats, groups, dates, games, currencies) def createStatsTable(self, vbox, playerids, sitenos, limits, seats, groups, dates, games, currencies): startTime = time() show_detail = True # # Display summary table at top of page # # 3rd parameter passes extra flags, currently includes: # # holecards - whether to display card breakdown (True/False) # # numhands - min number hands required when displaying all players # # gridnum - index for grid data structures flags = [False, self.filters.getNumHands(), 0] self.addGrid(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates, games, currencies) if 'allplayers' in groups: # can't currently do this combination so skip detailed table show_detail = False if show_detail: # Separator frame = QWidget() vbox2 = QVBoxLayout() vbox2.setContentsMargins(0, 0, 0, 0) frame.setLayout(vbox2) vbox.addWidget(frame) heading = QLabel(self.filterText['handhead']) heading.setAlignment(Qt.AlignHCenter) vbox2.addWidget(heading) # Detailed table flags[0] = True flags[2] = 1 self.addGrid(vbox2, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates, games, currencies) self.db.rollback() print( _("Stats page displayed in %4.2f seconds") % (time() - startTime)) def addGrid(self, vbox, query, flags, playerids, sitenos, limits, seats, groups, dates, games, currencies): sqlrow = 0 if not flags: holecards, grid = False, 0 else: holecards, grid = flags[0], flags[2] tmp = self.sql.query[query] tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, groups, dates, games, currencies) self.cursor.execute(tmp) result = self.cursor.fetchall() colnames = [desc[0].lower() for desc in self.cursor.description] # pre-fetch some constant values: colshow = colshowsumm if 'posn' in groups: colshow = colshowposn self.cols_to_show = [x for x in self.columns if x[colshow]] hgametypeid_idx = colnames.index('hgametypeid') assert len(self.liststore) == grid, "len(self.liststore)=" + str( len(self.liststore)) + " grid-1=" + str(grid) view = QTableView() self.liststore.append( QStandardItemModel(0, len(self.cols_to_show), view)) self.liststore[grid].setSortRole(Qt.UserRole) view.setModel(self.liststore[grid]) view.verticalHeader().hide() vbox.addWidget(view) self.listcols.append([]) # Create header row eg column: ("game", True, "Game", 0.0, "%s") for col, column in enumerate(self.cols_to_show): if column[colalias] == 'game' and holecards: s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading] else: s = column[colheading] self.listcols[grid].append(s) self.liststore[grid].setHorizontalHeaderLabels(self.listcols[grid]) rows = len(result) # +1 for title row while sqlrow < rows: treerow = [] for col, column in enumerate(self.cols_to_show): if column[colalias] in colnames: value = result[sqlrow][colnames.index(column[colalias])] if column[colalias] == 'plposition': if value == 'B': value = 'BB' elif value == 'S': value = 'SB' elif value == '0': value = 'Btn' else: if column[colalias] == 'game': if holecards: value = Card.decodeStartHandValue( result[sqlrow][colnames.index('category')], result[sqlrow][hgametypeid_idx]) else: minbb = result[sqlrow][colnames.index( 'minbigblind')] maxbb = result[sqlrow][colnames.index( 'maxbigblind')] value = result[sqlrow][colnames.index('limittype')] + ' ' \ + result[sqlrow][colnames.index('category')].title() + ' ' \ + result[sqlrow][colnames.index('name')] + ' ' \ + result[sqlrow][colnames.index('currency')] + ' ' if 100 * int(minbb / 100.0) != minbb: value += '%.2f' % (minbb / 100.0) else: value += '%.0f' % (minbb / 100.0) if minbb != maxbb: if 100 * int(maxbb / 100.0) != maxbb: value += ' - %.2f' % (maxbb / 100.0) else: value += ' - %.0f' % (maxbb / 100.0) ante = result[sqlrow][colnames.index('ante')] if ante > 0: value += ' ante: %.2f' % (ante / 100.0) if result[sqlrow][colnames.index('fast')] == 1: value += ' ' + fast_names[result[sqlrow][ colnames.index('name')]] else: continue item = QStandardItem('') sortValue = -1e9 if value is not None and value != -999: item = QStandardItem(column[colformat] % value) if column[colalias] == 'game' and holecards: if result[sqlrow][colnames.index( 'category')] == 'holdem': sortValue = 1000 * ranks[value[0]] + 10 * ranks[ value[1]] + (1 if len(value) == 3 and value[2] == 's' else 0) else: sortValue = -1 elif column[colalias] in ('game', 'pname'): sortValue = value elif column[colalias] == 'plposition': sortValue = [ 'BB', 'SB', 'Btn', '1', '2', '3', '4', '5', '6', '7' ].index(value) else: sortValue = float(value) item.setData(sortValue, Qt.UserRole) item.setEditable(False) if col != 0: item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) if column[colalias] != 'game': item.setToolTip('<big>%s for %s</big><br/><i>%s</i>' % (column[colheading], treerow[0].text(), onlinehelp[column[colheading]])) treerow.append(item) self.liststore[grid].appendRow(treerow) sqlrow += 1 view.resizeColumnsToContents() view.setSortingEnabled( True ) # do this after resizing columns, otherwise it leaves room for the sorting triangle in every heading view.resizeColumnToContents( 0 ) # we want room for the sorting triangle in column 0 where it starts. view.resizeRowsToContents() def refineQuery(self, query, flags, playerids, sitenos, limits, seats, groups, dates, games, currencies): having = '' if not flags: holecards = False numhands = 0 else: holecards = flags[0] numhands = flags[1] colshow = colshowsumm if 'posn' in groups: colshow = colshowposn pname_column = (x for x in self.columns if x[0] == 'pname').next() if 'allplayers' in groups: nametest = "(hp.playerId)" if holecards or 'posn' in groups: pname = "'all players'" # set flag in self.columns to not show player name column pname_column[colshow] = False # can't do this yet (re-write doing more maths in python instead of sql?) if numhands: nametest = "(-1)" else: pname = "p.name" # set flag in self.columns to show player name column pname_column[colshow] = True if numhands: having = ' and count(1) > %d ' % (numhands, ) else: if playerids: nametest = str(tuple(playerids)) nametest = nametest.replace("L", "") nametest = nametest.replace(",)", ")") else: nametest = "1 = 2" pname = "p.name" # set flag in self.columns to not show player name column pname_column[colshow] = False query = query.replace("<player_test>", nametest) query = query.replace("<playerName>", pname) query = query.replace("<havingclause>", having) gametest = "" for m in self.filters.display.items(): if m[0] == 'Games' and m[1]: if len(games) > 0: gametest = str(tuple(games)) gametest = gametest.replace("L", "") gametest = gametest.replace(",)", ")") gametest = gametest.replace("u'", "'") gametest = "and gt.category in %s" % gametest else: gametest = "and gt.category IS NULL" query = query.replace("<game_test>", gametest) currencytest = str(tuple(currencies)) currencytest = currencytest.replace(",)", ")") currencytest = currencytest.replace("u'", "'") currencytest = "AND gt.currency in %s" % currencytest query = query.replace("<currency_test>", currencytest) sitetest = "" for m in self.filters.display.items(): if m[0] == 'Sites' and m[1]: if len(sitenos) > 0: sitetest = str(tuple(sitenos)) sitetest = sitetest.replace("L", "") sitetest = sitetest.replace(",)", ")") sitetest = sitetest.replace("u'", "'") sitetest = "and gt.siteId in %s" % sitetest else: sitetest = "and gt.siteId IS NULL" query = query.replace("<site_test>", sitetest) if seats: query = query.replace( '<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to'])) if 'seats' in groups: query = query.replace('<groupbyseats>', ',h.seats') query = query.replace('<orderbyseats>', ',h.seats') else: query = query.replace('<groupbyseats>', '') query = query.replace('<orderbyseats>', '') else: query = query.replace('<seats_test>', 'between 0 and 100') query = query.replace('<groupbyseats>', '') query = query.replace('<orderbyseats>', '') bbtest = self.filters.get_limits_where_clause(limits) query = query.replace("<gtbigBlind_test>", bbtest) if holecards: # re-use level variables for hole card query query = query.replace("<hgametypeId>", "hp.startcards") query = query.replace( "<orderbyhgametypeId>", ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 " + " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 " + " end desc ") else: query = query.replace("<orderbyhgametypeId>", "") groupLevels = 'limits' not in groups if groupLevels: query = query.replace( "<hgametypeId>", "p.name" ) # need to use p.name for sqlite posn stats to work else: query = query.replace("<hgametypeId>", "h.gametypeId") # process self.detailFilters (a list of tuples) flagtest = '' if self.detailFilters: for f in self.detailFilters: if len(f) == 3: # X between Y and Z flagtest += ' and %s between %s and %s ' % (f[0], str( f[1]), str(f[2])) query = query.replace("<flagtest>", flagtest) if self.cardsFilters: cardstests = [] for cardFilter in self.cardsFilters: cardstests.append(cardFilter) cardstests = ''.join(('and (', ' or '.join(cardstests), ')')) else: cardstests = '' query = query.replace("<cardstest>", cardstests) # allow for differences in sql cast() function: if self.db.backend == self.MYSQL_INNODB: query = query.replace("<signed>", 'signed ') else: query = query.replace("<signed>", '') # Filter on dates query = query.replace( "<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'") # Group by position? plposition_column = (x for x in self.columns if x[0] == 'plposition').next() if 'posn' in groups: query = query.replace("<position>", "hp.position") plposition_column[colshow] = True else: query = query.replace("<position>", "gt.base") plposition_column[colshow] = False return (query) def showDetailFilter(self, checkState): detailDialog = QDialog(self.main_window) detailDialog.setWindowTitle(_("Detailed Filters")) handbox = QVBoxLayout() detailDialog.setLayout(handbox) label = QLabel(_("Hand Filters:")) handbox.addWidget(label) label.setAlignment(Qt.AlignCenter) grid = QGridLayout() handbox.addLayout(grid) for row, htest in enumerate(self.handtests): cb = QCheckBox() lbl_from = QLabel(htest[1]) lbl_tween = QLabel(_('between')) lbl_to = QLabel(_('and')) sb1 = QSpinBox() sb1.setRange(0, 10) sb1.setValue(htest[2]) sb2 = QSpinBox() sb2.setRange(2, 10) sb2.setValue(htest[3]) for df in self.detailFilters: if df[0] == htest[0]: cb.setChecked(True) break grid.addWidget(cb, row, 0) grid.addWidget(lbl_from, row, 1, Qt.AlignLeft) grid.addWidget(lbl_tween, row, 2) grid.addWidget(sb1, row, 3) grid.addWidget(lbl_to, row, 4) grid.addWidget(sb2, row, 5) htest[4:7] = [cb, sb1, sb2] label = QLabel(_('Restrict to hand types:')) handbox.addWidget(label) for ctest in self.cardstests: hbox = QHBoxLayout() handbox.addLayout(hbox) cb = QCheckBox() if ctest[0] in self.cardsFilters: cb.setChecked(True) label = QLabel(ctest[1]) hbox.addWidget(cb) hbox.addWidget(label) ctest[2:3] = [cb] btnBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) handbox.addWidget(btnBox) btnBox.accepted.connect(detailDialog.accept) btnBox.rejected.connect(detailDialog.reject) response = detailDialog.exec_() if response: self.detailFilters = [] for ht in self.handtests: if ht[4].isChecked(): self.detailFilters.append( (ht[0], ht[5].value(), ht[6].value())) ht[2], ht[3] = ht[5].value(), ht[6].value() self.cardsFilters = [] for ct in self.cardstests: if ct[2].isChecked(): self.cardsFilters.append(ct[0]) self.refreshStats(None)