Beispiel #1
0
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
Beispiel #2
0
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()
Beispiel #3
0
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()
Beispiel #4
0
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())
Beispiel #5
0
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)
Beispiel #6
0
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)
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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)