class WorkspaceStatus(BaseWorkspace): def setupWorkspace(self): self.pile = QStackedWidget(self) self.wLayout.addWidget(self.pile) self.dlgs = weakref.WeakValueDictionary() self.pile.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding) self.push(DefaultStatusWidget(), 'default') def push(self, widget, name): idx = self.pile.addWidget(widget) self.pile.setCurrentIndex(idx) self.dlgs[name] = widget return idx, widget def clear(self, name): if name in self.dlgs: w = self.dlgs[name] idx = self.pile.indexOf(w) if idx: self.pile.removeWidget(w) def pushProgress(self, name): return self.push(DefaultProgressDialog(), name) async def pushRunDialog(self, dialog, name): dialog.setEnabled(True) idx, w = self.push(dialog, name) await runDialogAsync(dialog)
class E5SideBar(QWidget): """ Class implementing a sidebar with a widget area, that is hidden or shown, if the current tab is clicked again. """ Version = 1 North = 0 East = 1 South = 2 West = 3 def __init__(self, orientation=None, delay=200, parent=None): """ Constructor @param orientation orientation of the sidebar widget (North, East, South, West) @param delay value for the expand/shrink delay in milliseconds (integer) @param parent parent widget (QWidget) """ super(E5SideBar, self).__init__(parent) self.__tabBar = QTabBar() self.__tabBar.setDrawBase(True) self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setUsesScrollButtons(True) self.__tabBar.setDrawBase(False) self.__stackedWidget = QStackedWidget(self) self.__stackedWidget.setContentsMargins(0, 0, 0, 0) self.__autoHideButton = QToolButton() self.__autoHideButton.setCheckable(True) self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) self.__autoHideButton.setChecked(True) self.__autoHideButton.setToolTip( self.tr("Deselect to activate automatic collapsing")) self.barLayout = QBoxLayout(QBoxLayout.LeftToRight) self.barLayout.setContentsMargins(0, 0, 0, 0) self.layout = QBoxLayout(QBoxLayout.TopToBottom) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.barLayout.addWidget(self.__autoHideButton) self.barLayout.addWidget(self.__tabBar) self.layout.addLayout(self.barLayout) self.layout.addWidget(self.__stackedWidget) self.setLayout(self.layout) # initialize the delay timer self.__actionMethod = None self.__delayTimer = QTimer(self) self.__delayTimer.setSingleShot(True) self.__delayTimer.setInterval(delay) self.__delayTimer.timeout.connect(self.__delayedAction) self.__minimized = False self.__minSize = 0 self.__maxSize = 0 self.__bigSize = QSize() self.splitter = None self.splitterSizes = [] self.__hasFocus = False # flag storing if this widget or any child has the focus self.__autoHide = False self.__tabBar.installEventFilter(self) self.__orientation = E5SideBar.North if orientation is None: orientation = E5SideBar.North self.setOrientation(orientation) self.__tabBar.currentChanged[int].connect( self.__stackedWidget.setCurrentIndex) e5App().focusChanged.connect(self.__appFocusChanged) self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled) def setSplitter(self, splitter): """ Public method to set the splitter managing the sidebar. @param splitter reference to the splitter (QSplitter) """ self.splitter = splitter self.splitter.splitterMoved.connect(self.__splitterMoved) self.splitter.setChildrenCollapsible(False) index = self.splitter.indexOf(self) self.splitter.setCollapsible(index, False) def __splitterMoved(self, pos, index): """ Private slot to react on splitter moves. @param pos new position of the splitter handle (integer) @param index index of the splitter handle (integer) """ if self.splitter: self.splitterSizes = self.splitter.sizes() def __delayedAction(self): """ Private slot to handle the firing of the delay timer. """ if self.__actionMethod is not None: self.__actionMethod() def setDelay(self, delay): """ Public method to set the delay value for the expand/shrink delay in milliseconds. @param delay value for the expand/shrink delay in milliseconds (integer) """ self.__delayTimer.setInterval(delay) def delay(self): """ Public method to get the delay value for the expand/shrink delay in milliseconds. @return value for the expand/shrink delay in milliseconds (integer) """ return self.__delayTimer.interval() def __cancelDelayTimer(self): """ Private method to cancel the current delay timer. """ self.__delayTimer.stop() self.__actionMethod = None def shrink(self): """ Public method to record a shrink request. """ self.__delayTimer.stop() self.__actionMethod = self.__shrinkIt self.__delayTimer.start() def __shrinkIt(self): """ Private method to shrink the sidebar. """ self.__minimized = True self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() if self.splitter: self.splitterSizes = self.splitter.sizes() self.__stackedWidget.hide() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.setFixedHeight(self.__tabBar.minimumSizeHint().height()) else: self.setFixedWidth(self.__tabBar.minimumSizeHint().width()) self.__actionMethod = None def expand(self): """ Public method to record a expand request. """ self.__delayTimer.stop() self.__actionMethod = self.__expandIt self.__delayTimer.start() def __expandIt(self): """ Private method to expand the sidebar. """ self.__minimized = False self.__stackedWidget.show() self.resize(self.__bigSize) if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = max(self.__minSize, self.minimumSizeHint().height()) self.setMinimumHeight(minSize) self.setMaximumHeight(self.__maxSize) else: minSize = max(self.__minSize, self.minimumSizeHint().width()) self.setMinimumWidth(minSize) self.setMaximumWidth(self.__maxSize) if self.splitter: self.splitter.setSizes(self.splitterSizes) self.__actionMethod = None def isMinimized(self): """ Public method to check the minimized state. @return flag indicating the minimized state (boolean) """ return self.__minimized def isAutoHiding(self): """ Public method to check, if the auto hide function is active. @return flag indicating the state of auto hiding (boolean) """ return self.__autoHide def eventFilter(self, obj, evt): """ Public method to handle some events for the tabbar. @param obj reference to the object (QObject) @param evt reference to the event object (QEvent) @return flag indicating, if the event was handled (boolean) """ if obj == self.__tabBar: if evt.type() == QEvent.MouseButtonPress: pos = evt.pos() for i in range(self.__tabBar.count()): if self.__tabBar.tabRect(i).contains(pos): break if i == self.__tabBar.currentIndex(): if self.isMinimized(): self.expand() else: self.shrink() return True elif self.isMinimized(): self.expand() elif evt.type() == QEvent.Wheel: if qVersion() >= "5.0.0": delta = evt.angleDelta().y() else: delta = evt.delta() if delta > 0: self.prevTab() else: self.nextTab() return True return QWidget.eventFilter(self, obj, evt) def addTab(self, widget, iconOrLabel, label=None): """ Public method to add a tab to the sidebar. @param widget reference to the widget to add (QWidget) @param iconOrLabel reference to the icon or the label text of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.addTab(iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.addTab(iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.addWidget(widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def insertTab(self, index, widget, iconOrLabel, label=None): """ Public method to insert a tab into the sidebar. @param index the index to insert the tab at (integer) @param widget reference to the widget to insert (QWidget) @param iconOrLabel reference to the icon or the labeltext of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.insertTab(index, iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.insertTab(index, iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.insertWidget(index, widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def removeTab(self, index): """ Public method to remove a tab. @param index the index of the tab to remove (integer) """ self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index)) self.__tabBar.removeTab(index) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def clear(self): """ Public method to remove all tabs. """ while self.count() > 0: self.removeTab(0) def prevTab(self): """ Public slot used to show the previous tab. """ ind = self.currentIndex() - 1 if ind == -1: ind = self.count() - 1 self.setCurrentIndex(ind) self.currentWidget().setFocus() def nextTab(self): """ Public slot used to show the next tab. """ ind = self.currentIndex() + 1 if ind == self.count(): ind = 0 self.setCurrentIndex(ind) self.currentWidget().setFocus() def count(self): """ Public method to get the number of tabs. @return number of tabs in the sidebar (integer) """ return self.__tabBar.count() def currentIndex(self): """ Public method to get the index of the current tab. @return index of the current tab (integer) """ return self.__stackedWidget.currentIndex() def setCurrentIndex(self, index): """ Public slot to set the current index. @param index the index to set as the current index (integer) """ self.__tabBar.setCurrentIndex(index) self.__stackedWidget.setCurrentIndex(index) if self.isMinimized(): self.expand() def currentWidget(self): """ Public method to get a reference to the current widget. @return reference to the current widget (QWidget) """ return self.__stackedWidget.currentWidget() def setCurrentWidget(self, widget): """ Public slot to set the current widget. @param widget reference to the widget to become the current widget (QWidget) """ self.__stackedWidget.setCurrentWidget(widget) self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex()) if self.isMinimized(): self.expand() def indexOf(self, widget): """ Public method to get the index of the given widget. @param widget reference to the widget to get the index of (QWidget) @return index of the given widget (integer) """ return self.__stackedWidget.indexOf(widget) def isTabEnabled(self, index): """ Public method to check, if a tab is enabled. @param index index of the tab to check (integer) @return flag indicating the enabled state (boolean) """ return self.__tabBar.isTabEnabled(index) def setTabEnabled(self, index, enabled): """ Public method to set the enabled state of a tab. @param index index of the tab to set (integer) @param enabled enabled state to set (boolean) """ self.__tabBar.setTabEnabled(index, enabled) def orientation(self): """ Public method to get the orientation of the sidebar. @return orientation of the sidebar (North, East, South, West) """ return self.__orientation def setOrientation(self, orient): """ Public method to set the orientation of the sidebar. @param orient orientation of the sidebar (North, East, South, West) """ if orient == E5SideBar.North: self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.TopToBottom) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.East: self.__tabBar.setShape(QTabBar.RoundedEast) self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.RightToLeft) self.layout.setAlignment(self.barLayout, Qt.AlignTop) elif orient == E5SideBar.South: self.__tabBar.setShape(QTabBar.RoundedSouth) self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.BottomToTop) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.West: self.__tabBar.setShape(QTabBar.RoundedWest) self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.LeftToRight) self.layout.setAlignment(self.barLayout, Qt.AlignTop) self.__orientation = orient def tabIcon(self, index): """ Public method to get the icon of a tab. @param index index of the tab (integer) @return icon of the tab (QIcon) """ return self.__tabBar.tabIcon(index) def setTabIcon(self, index, icon): """ Public method to set the icon of a tab. @param index index of the tab (integer) @param icon icon to be set (QIcon) """ self.__tabBar.setTabIcon(index, icon) def tabText(self, index): """ Public method to get the text of a tab. @param index index of the tab (integer) @return text of the tab (string) """ return self.__tabBar.tabText(index) def setTabText(self, index, text): """ Public method to set the text of a tab. @param index index of the tab (integer) @param text text to set (string) """ self.__tabBar.setTabText(index, text) def tabToolTip(self, index): """ Public method to get the tooltip text of a tab. @param index index of the tab (integer) @return tooltip text of the tab (string) """ return self.__tabBar.tabToolTip(index) def setTabToolTip(self, index, tip): """ Public method to set the tooltip text of a tab. @param index index of the tab (integer) @param tip tooltip text to set (string) """ self.__tabBar.setTabToolTip(index, tip) def tabWhatsThis(self, index): """ Public method to get the WhatsThis text of a tab. @param index index of the tab (integer) @return WhatsThis text of the tab (string) """ return self.__tabBar.tabWhatsThis(index) def setTabWhatsThis(self, index, text): """ Public method to set the WhatsThis text of a tab. @param index index of the tab (integer) @param text WhatsThis text to set (string) """ self.__tabBar.setTabWhatsThis(index, text) def widget(self, index): """ Public method to get a reference to the widget associated with a tab. @param index index of the tab (integer) @return reference to the widget (QWidget) """ return self.__stackedWidget.widget(index) def saveState(self): """ Public method to save the state of the sidebar. @return saved state as a byte array (QByteArray) """ if len(self.splitterSizes) == 0: if self.splitter: self.splitterSizes = self.splitter.sizes() self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) stream.writeUInt16(self.Version) stream.writeBool(self.__minimized) stream << self.__bigSize stream.writeUInt16(self.__minSize) stream.writeUInt16(self.__maxSize) stream.writeUInt16(len(self.splitterSizes)) for size in self.splitterSizes: stream.writeUInt16(size) stream.writeBool(self.__autoHide) return data def restoreState(self, state): """ Public method to restore the state of the sidebar. @param state byte array containing the saved state (QByteArray) @return flag indicating success (boolean) """ if state.isEmpty(): return False if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = self.layout.minimumSize().height() maxSize = self.maximumHeight() else: minSize = self.layout.minimumSize().width() maxSize = self.maximumWidth() data = QByteArray(state) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) stream.readUInt16() # version minimized = stream.readBool() if minimized and not self.__minimized: self.shrink() stream >> self.__bigSize self.__minSize = max(stream.readUInt16(), minSize) self.__maxSize = max(stream.readUInt16(), maxSize) count = stream.readUInt16() self.splitterSizes = [] for i in range(count): self.splitterSizes.append(stream.readUInt16()) self.__autoHide = stream.readBool() self.__autoHideButton.setChecked(not self.__autoHide) if not minimized: self.expand() return True ####################################################################### ## methods below implement the autohide functionality ####################################################################### def __autoHideToggled(self, checked): """ Private slot to handle the toggling of the autohide button. @param checked flag indicating the checked state of the button (boolean) """ self.__autoHide = not checked if self.__autoHide: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOn.png")) else: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) def __appFocusChanged(self, old, now): """ Private slot to handle a change of the focus. @param old reference to the widget, that lost focus (QWidget or None) @param now reference to the widget having the focus (QWidget or None) """ self.__hasFocus = self.isAncestorOf(now) if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() elif self.__autoHide and self.__hasFocus and self.isMinimized(): self.expand() def enterEvent(self, event): """ Protected method to handle the mouse entering this widget. @param event reference to the event (QEvent) """ if self.__autoHide and self.isMinimized(): self.expand() else: self.__cancelDelayTimer() def leaveEvent(self, event): """ Protected method to handle the mouse leaving this widget. @param event reference to the event (QEvent) """ if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() else: self.__cancelDelayTimer() def shutdown(self): """ Public method to shut down the object. This method does some preparations so the object can be deleted properly. It disconnects from the focusChanged signal in order to avoid trouble later on. """ e5App().focusChanged.disconnect(self.__appFocusChanged)
class TabBarWindow(TabWindow): """Implementation which uses a separate QTabBar and QStackedWidget. The Tab bar is placed next to the menu bar to save real estate.""" def __init__(self, app, **kwargs): super().__init__(app, **kwargs) def _setupUi(self): self.setWindowTitle(self.app.NAME) self.resize(640, 480) self.tabBar = QTabBar() self.verticalLayout = QVBoxLayout() self.verticalLayout.setContentsMargins(0, 0, 0, 0) self._setupActions() self._setupMenu() self.centralWidget = QWidget(self) self.setCentralWidget(self.centralWidget) self.stackedWidget = QStackedWidget() self.centralWidget.setLayout(self.verticalLayout) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.addWidget(self.menubar, 0, Qt.AlignTop) self.horizontalLayout.addWidget(self.tabBar, 0, Qt.AlignTop) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout.addWidget(self.stackedWidget) self.tabBar.currentChanged.connect(self.showWidget) self.tabBar.tabCloseRequested.connect(self.onTabCloseRequested) self.stackedWidget.currentChanged.connect(self.updateMenuBar) self.stackedWidget.widgetRemoved.connect(self.onRemovedWidget) self.tabBar.setTabsClosable(True) self.restoreGeometry() def addTab(self, page, title, switch=True): stack_index = self.stackedWidget.insertWidget(-1, page) tab_index = self.tabBar.addTab(title) if isinstance(page, DirectoriesDialog): self.tabBar.setTabButton(tab_index, QTabBar.RightSide, None) if switch: # switch to the added tab immediately upon creation self.setTabIndex(tab_index) self.stackedWidget.setCurrentWidget(page) return stack_index @pyqtSlot(int) def showWidget(self, index): if index >= 0 and index <= self.stackedWidget.count() - 1: self.stackedWidget.setCurrentIndex(index) # if not self.tabBar.isTabVisible(index): self.setTabVisible(index, True) def indexOfWidget(self, widget): # Warning: this may return -1 if widget is not a child of stackedwidget return self.stackedWidget.indexOf(widget) def setCurrentIndex(self, tab_index): # The signal will handle switching the stackwidget's widget self.setTabIndex(tab_index) # self.stackedWidget.setCurrentWidget(self.stackedWidget.widget(tab_index)) @pyqtSlot(int) def setTabIndex(self, index): if index is None: return self.tabBar.setCurrentIndex(index) def setTabVisible(self, index, value): return self.tabBar.setTabVisible(index, value) @pyqtSlot(int) def onRemovedWidget(self, index): self.removeTab(index) @pyqtSlot(int) def removeTab(self, index): # No need to remove the widget here: # self.stackedWidget.removeWidget(self.stackedWidget.widget(index)) return self.tabBar.removeTab(index) @pyqtSlot(int) def removeWidget(self, widget): return self.stackedWidget.removeWidget(widget) def isTabVisible(self, index): return self.tabBar.isTabVisible(index) def getCurrentIndex(self): return self.stackedWidget.currentIndex() def getWidgetAtIndex(self, index): return self.stackedWidget.widget(index) def getCount(self): return self.stackedWidget.count() @pyqtSlot() def toggleTabBar(self): value = self.sender().isChecked() self.actionToggleTabs.setChecked(value) self.tabBar.setVisible(value) @pyqtSlot(int) def onTabCloseRequested(self, index): current_widget = self.getWidgetAtIndex(index) if isinstance(current_widget, DirectoriesDialog): # On MacOS, the tab has a close button even though we explicitely # set it to None in order to hide it. This should prevent # the "Directories" tab from closing by mistake. return current_widget.close() self.stackedWidget.removeWidget(current_widget)
class FontWindow(BaseWindow): def __init__(self, font, parent=None): super().__init__(parent) self._font = None self._infoWindow = None self._featuresWindow = None self._groupsWindow = None self._kerningWindow = None self._metricsWindow = None self.toolBar = ToolBar(self) self.toolBar.setTools(t() for t in QApplication.instance().drawingTools()) self.glyphCellView = GlyphCellView(self) self.glyphCellView.glyphActivated.connect(self.openGlyphTab) self.glyphCellView.glyphsDropped.connect(self._orderChanged) self.glyphCellView.selectionChanged.connect(self._selectionChanged) self.glyphCellView.setAcceptDrops(True) self.glyphCellView.setCellRepresentationName("TruFont.GlyphCell") self.glyphCellView.setFrameShape(self.glyphCellView.NoFrame) self.glyphCellView.setFocus() self.tabWidget = TabWidget(self) self.tabWidget.setAutoHide(True) self.tabWidget.setHeroFirstTab(True) self.tabWidget.addTab(self.tr("Font")) self.stackWidget = QStackedWidget(self) self.stackWidget.addWidget(self.glyphCellView) self.tabWidget.currentTabChanged.connect(self._tabChanged) self.tabWidget.tabRemoved.connect( lambda index: self.stackWidget.removeWidget(self.stackWidget.widget(index)) ) self.stackWidget.currentChanged.connect(self._widgetChanged) self.propertiesView = PropertiesView(font, self) self.propertiesView.hide() self.statusBar = StatusBar(self) self.statusBar.setMinimumSize(32) self.statusBar.setMaximumSize(128) self.statusBar.sizeChanged.connect(self._sizeChanged) self.setFont_(font) app = QApplication.instance() app.dispatcher.addObserver( self, "_drawingToolRegistered", "drawingToolRegistered" ) app.dispatcher.addObserver( self, "_drawingToolUnregistered", "drawingToolUnregistered" ) app.dispatcher.addObserver( self, "_glyphViewGlyphsChanged", "glyphViewGlyphsChanged" ) layout = QHBoxLayout(self) layout.addWidget(self.toolBar) vLayout = QVBoxLayout() vLayout.addWidget(self.tabWidget) pageWidget = PageWidget() pageWidget.addWidget(self.stackWidget) pageWidget.addWidget(self.statusBar) vLayout.addWidget(pageWidget) layout.addLayout(vLayout) layout.addWidget(self.propertiesView) layout.setContentsMargins(0, 2, 0, 0) layout.setSpacing(2) elements = [ ("Ctrl+D", self.deselect), (platformSpecific.closeKeySequence(), self.closeGlyphTab), # XXX: does this really not warrant widget focus? (QKeySequence.Delete, self.delete), ("Shift+" + QKeySequence(QKeySequence.Delete).toString(), self.delete), ("Z", lambda: self.zoom(1)), ("X", lambda: self.zoom(-1)), ] e = platformSpecific.altDeleteSequence() if e is not None: elements.append((e, self.delete)) e = platformSpecific.altRedoSequence() if e is not None: elements.append((e, self.redo)) for keys, callback in elements: shortcut = QShortcut(QKeySequence(keys), self) shortcut.activated.connect(callback) self.installEventFilter(PreviewEventFilter(self)) self.readSettings() self.propertiesView.activeLayerModified.connect(self._activeLayerModified) self.statusBar.sizeChanged.connect(self.writeSettings) def readSettings(self): geometry = settings.fontWindowGeometry() if geometry: self.restoreGeometry(geometry) cellSize = settings.glyphCellSize() self.statusBar.setSize(cellSize) hidden = settings.propertiesHidden() if not hidden: self.properties() def writeSettings(self): settings.setFontWindowGeometry(self.saveGeometry()) settings.setGlyphCellSize(self.glyphCellView.cellSize()[0]) settings.setPropertiesHidden(self.propertiesView.isHidden()) def menuBar(self): return self.layout().menuBar() def setMenuBar(self, menuBar): self.layout().setMenuBar(menuBar) def setupMenu(self, menuBar): app = QApplication.instance() fileMenu = menuBar.fetchMenu(Entries.File) fileMenu.fetchAction(Entries.File_New) fileMenu.fetchAction(Entries.File_Open) fileMenu.fetchMenu(Entries.File_Open_Recent) if not platformSpecific.mergeOpenAndImport(): fileMenu.fetchAction(Entries.File_Import) fileMenu.addSeparator() fileMenu.fetchAction(Entries.File_Save, self.saveFile) fileMenu.fetchAction(Entries.File_Save_As, self.saveFileAs) fileMenu.fetchAction(Entries.File_Save_All) fileMenu.fetchAction(Entries.File_Reload, self.reloadFile) fileMenu.addSeparator() fileMenu.fetchAction(Entries.File_Export, self.exportFile) fileMenu.fetchAction(Entries.File_Exit) editMenu = menuBar.fetchMenu(Entries.Edit) self._undoAction = editMenu.fetchAction(Entries.Edit_Undo, self.undo) self._redoAction = editMenu.fetchAction(Entries.Edit_Redo, self.redo) editMenu.addSeparator() cut = editMenu.fetchAction(Entries.Edit_Cut, self.cut) copy = editMenu.fetchAction(Entries.Edit_Copy, self.copy) copyComponent = editMenu.fetchAction( Entries.Edit_Copy_As_Component, self.copyAsComponent ) paste = editMenu.fetchAction(Entries.Edit_Paste, self.paste) self._clipboardActions = (cut, copy, copyComponent, paste) editMenu.fetchAction(Entries.Edit_Select_All, self.selectAll) # editMenu.fetchAction(Entries.Edit_Deselect, self.deselect) editMenu.fetchAction(Entries.Edit_Find, self.findGlyph) editMenu.addSeparator() editMenu.fetchAction(Entries.Edit_Settings) viewMenu = menuBar.fetchMenu(Entries.View) viewMenu.fetchAction(Entries.View_Zoom_In, lambda: self.zoom(1)) viewMenu.fetchAction(Entries.View_Zoom_Out, lambda: self.zoom(-1)) viewMenu.fetchAction(Entries.View_Reset_Zoom, self.resetZoom) viewMenu.addSeparator() viewMenu.fetchAction(Entries.View_Next_Tab, lambda: self.tabOffset(1)) viewMenu.fetchAction(Entries.View_Previous_Tab, lambda: self.tabOffset(-1)) viewMenu.fetchAction(Entries.View_Next_Glyph, lambda: self.glyphOffset(1)) viewMenu.fetchAction(Entries.View_Previous_Glyph, lambda: self.glyphOffset(-1)) viewMenu.fetchAction(Entries.View_Layer_Up, lambda: self.layerOffset(-1)) viewMenu.fetchAction(Entries.View_Layer_Down, lambda: self.layerOffset(1)) viewMenu.addSeparator() viewMenu.fetchAction(Entries.View_Show_Points) viewMenu.fetchAction(Entries.View_Show_Metrics) viewMenu.fetchAction(Entries.View_Show_Images) viewMenu.fetchAction(Entries.View_Show_Guidelines) fontMenu = menuBar.fetchMenu(Entries.Font) fontMenu.fetchAction(Entries.Font_Font_Info, self.fontInfo) fontMenu.fetchAction(Entries.Font_Font_Features, self.fontFeatures) fontMenu.addSeparator() fontMenu.fetchAction(Entries.Font_Add_Glyphs, self.addGlyphs) fontMenu.fetchAction(Entries.Font_Sort, self.sortGlyphs) # glyphMenu = menuBar.fetchMenu(self.tr("&Glyph")) # self._layerAction = glyphMenu.fetchAction( # self.tr("&Layer Actions…"), self.layerActions, "L") menuBar.fetchMenu(Entries.Scripts) windowMenu = menuBar.fetchMenu(Entries.Window) windowMenu.fetchAction(Entries.Window_Groups, self.groups) windowMenu.fetchAction(Entries.Window_Kerning, self.kerning) windowMenu.fetchAction(Entries.Window_Metrics, self.metrics) windowMenu.fetchAction(Entries.Window_Scripting) windowMenu.fetchAction(Entries.Window_Properties, self.properties) windowMenu.addSeparator() action = windowMenu.fetchAction(Entries.Window_Output) action.setEnabled(app.outputWindow is not None) helpMenu = menuBar.fetchMenu(Entries.Help) helpMenu.fetchAction(Entries.Help_Documentation) helpMenu.fetchAction(Entries.Help_Report_An_Issue) helpMenu.addSeparator() helpMenu.fetchAction(Entries.Help_About) self._updateGlyphActions() # -------------- # Custom methods # -------------- def font_(self): return self._font def setFont_(self, font): if self._font is not None: self._font.removeObserver(self, "Font.Changed") self._font.removeObserver(self, "Font.GlyphOrderChanged") self._font.removeObserver(self, "Font.SortDescriptorChanged") self._font = font self.setWindowTitle(self.fontTitle()) if font is None: return self._updateGlyphsFromGlyphOrder() font.addObserver(self, "_fontChanged", "Font.Changed") font.addObserver(self, "_glyphOrderChanged", "Font.GlyphOrderChanged") font.addObserver(self, "_sortDescriptorChanged", "Font.SortDescriptorChanged") def fontTitle(self): if self._font is None: return None path = self._font.path or self._font.binaryPath if path is not None: return os.path.basename(path.rstrip(os.sep)) return self.tr("Untitled") def isGlyphTab(self): return bool(self.stackWidget.currentIndex()) def openGlyphTab(self, glyph): # if a tab with this glyph exists already, switch to it for index in range(self.stackWidget.count()): if not index: continue view = self.stackWidget.widget(index) if list(view.glyphs()) == [glyph]: self.tabWidget.setCurrentTab(index) return # spawn widget = GlyphCanvasView(self) widget.setInputNames([glyph.name]) widget.activeGlyphChanged.connect(self._selectionChanged) widget.glyphNamesChanged.connect(self._namesChanged) widget.pointSizeModified.connect(self.statusBar.setSize) widget.toolModified.connect(self.toolBar.setCurrentTool) # add self.tabWidget.addTab(_textForGlyphs([glyph])) self.stackWidget.addWidget(widget) # activate self.tabWidget.setCurrentTab(-1) def closeGlyphTab(self): index = self.stackWidget.currentIndex() if index: self.tabWidget.removeTab(index) def maybeSaveBeforeExit(self): if self._font.dirty: ret = CloseMessageBox.getCloseDocument(self, self.fontTitle()) if ret == QMessageBox.Save: self.saveFile() return True elif ret == QMessageBox.Discard: return True return False return True # ------------- # Notifications # ------------- # app def _drawingToolRegistered(self, notification): toolClass = notification.data["tool"] index = self.stackWidget.currentIndex() parent = self.stackWidget.currentWidget() if index else None self.toolBar.addTool(toolClass(parent=parent)) def _drawingToolUnregistered(self, notification): toolClass = notification.data["tool"] for tool in self.toolBar.tools(): if isinstance(tool, toolClass): self.toolBar.removeTool(tool) return raise ValueError(f"couldn't find tool to unregister: {toolClass}") def _glyphViewGlyphsChanged(self, notification): self._updateGlyphActions() # widgets def _activeLayerModified(self): if self.isGlyphTab(): widget = self.stackWidget.currentWidget() index = self.sender().currentIndex().row() layers = self._font.layers layer = layers[layers.layerOrder[index]] currentGlyph = widget.activeGlyph() # XXX: adjust TLayer.get and use it if currentGlyph.name in layer: glyph = layer[currentGlyph.name] else: glyph = layer.newGlyph(currentGlyph.name) widget.setActiveGlyph(glyph) def _namesChanged(self): sender = self.sender() index = self.stackWidget.indexOf(sender) self.tabWidget.setTabName(index, _textForGlyphs(sender.glyphs())) def _sizeChanged(self): size = self.statusBar.size() if self.isGlyphTab(): widget = self.stackWidget.currentWidget() widget.setPointSize(size) else: self.glyphCellView.setCellSize(size) def _tabChanged(self, index): self.statusBar.setShouldPropagateSize(not index) # we need to hide, then setParent, then show self.stackWidget.currentWidget().hide() newWidget = self.stackWidget.widget(index) if index: for tool in self.toolBar.tools(): tool.setParent(newWidget) self.stackWidget.setCurrentIndex(index) newWidget.setFocus(Qt.OtherFocusReason) def _toolChanged(self, tool): widget = self.stackWidget.currentWidget() ok = widget.setCurrentTool(tool) # the glyph view NAKed the change (in mouseDown) # set back the current tool in the toolbar if not ok: self.toolBar.setCurrentTool(widget.currentTool()) def _widgetChanged(self, index): # update current glyph self._updateCurrentGlyph() # update undo/redo self._updateGlyphActions() # update slider if self.isGlyphTab(): lo, hi, unit = 0, 900000, " pt" widget = self.stackWidget.currentWidget() size = widget.pointSize() else: lo, hi, unit = 32, 128, None size = self.glyphCellView.cellSize()[0] self.statusBar.setMinimumSize(lo) self.statusBar.setMaximumSize(hi) self.statusBar.setSize(size) self.statusBar.setUnit(unit) self.statusBar.setTextVisible(not self.isGlyphTab()) # update and connect setCurrentTool try: self.toolBar.currentToolChanged.disconnect() except TypeError: pass if not index: return widget = self.stackWidget.currentWidget() widget.setCurrentTool(self.toolBar.currentTool()) self.toolBar.currentToolChanged.connect(self._toolChanged) def _orderChanged(self): # TODO: reimplement when we start showing glyph subsets glyphs = self.glyphCellView.glyphs() self._font.glyphOrder = [glyph.name for glyph in glyphs] def _selectionChanged(self): if self.isGlyphTab(): activeGlyph = self.stackWidget.currentWidget().activeGlyph() else: activeGlyph = self.glyphCellView.lastSelectedGlyph() # selection text # TODO: this should probably be internal to the label selection = self.glyphCellView.selection() if selection is not None: count = len(selection) if count == 1: glyph = self.glyphCellView.glyphsForIndexes(selection)[0] text = "%s " % glyph.name else: text = "" if count: text = self.tr(f"{text}(%n selected)", n=count) else: text = "" self.statusBar.setText(text) # currentGlyph app = QApplication.instance() app.setCurrentGlyph(activeGlyph) # actions self._updateGlyphActions() # defcon def _fontChanged(self, notification): font = notification.object self.setWindowModified(font.dirty) def _glyphOrderChanged(self, notification): self._updateGlyphsFromGlyphOrder() def _updateGlyphsFromGlyphOrder(self): font = self._font glyphOrder = font.glyphOrder if glyphOrder: glyphCount = 0 glyphs = [] for glyphName in glyphOrder: if glyphName in font: glyph = font[glyphName] glyphCount += 1 else: glyph = font.get(glyphName, asTemplate=True) glyphs.append(glyph) if glyphCount < len(font): # if some glyphs in the font are not present in the glyph # order, loop again to add them at the end for glyph in font: if glyph not in glyphs: glyphs.append(glyph) font.disableNotifications(observer=self) font.glyphOrder = [glyph.name for glyph in glyphs] font.enableNotifications(observer=self) else: glyphs = list(font) font.disableNotifications(observer=self) font.glyphOrder = [glyph.name for glyph in glyphs] font.enableNotifications(observer=self) self.glyphCellView.setGlyphs(glyphs) def _sortDescriptorChanged(self, notification): font = notification.object descriptors = notification.data["newValue"] if descriptors is None: return if descriptors[0]["type"] == "glyphSet": glyphNames = descriptors[0]["glyphs"] else: glyphNames = font.unicodeData.sortGlyphNames(font.keys(), descriptors) font.glyphOrder = glyphNames # ------------ # Menu methods # ------------ # File def saveFile(self, path=None, ufoFormatVersion=3): if path is None and self._font.path is None: self.saveFileAs() else: if path is None: path = self._font.path self._font.save(path, ufoFormatVersion) def saveFileAs(self): fileFormats = OrderedDict( [ (self.tr("UFO Font version 3 {}").format("(*.ufo)"), 3), (self.tr("UFO Font version 2 {}").format("(*.ufo)"), 2), ] ) state = settings.saveFileDialogState() path = self._font.path or self._font.binaryPath if path: directory = os.path.dirname(path) else: directory = ( None if state else QStandardPaths.standardLocations(QStandardPaths.DocumentsLocation)[ 0 ] ) # TODO: switch to directory dlg on platforms that need it dialog = QFileDialog( self, self.tr("Save File"), directory, ";;".join(fileFormats.keys()) ) if state: dialog.restoreState(state) dialog.setAcceptMode(QFileDialog.AcceptSave) if directory: dialog.setDirectory(directory) ok = dialog.exec_() settings.setSaveFileDialogState(dialog.saveState()) if ok: nameFilter = dialog.selectedNameFilter() path = dialog.selectedFiles()[0] if not os.path.basename(path).endswith(".ufo"): path += ".ufo" self.saveFile(path, fileFormats[nameFilter]) app = QApplication.instance() app.setCurrentFile(self._font.path) self.setWindowTitle(self.fontTitle()) # return ok def reloadFile(self): font = self._font path = font.path or font.binaryPath if not font.dirty or path is None: return if not ReloadMessageBox.getReloadDocument(self, self.fontTitle()): return if font.path is not None: font.reloadInfo() font.reloadKerning() font.reloadGroups() font.reloadFeatures() font.reloadLib() font.reloadGlyphs(font.keys()) font.dirty = False else: # TODO: we should do this in-place font_ = font.__class__().new() font_.extract(font.binaryPath) self.setFont_(font_) def exportFile(self): params, ok = ExportDialog.getExportParameters(self, self._font) if not ok: return baseName = params["baseName"] directory = params["exportDirectory"] compression = set(map(str.lower, params["compression"])) for format in map(str.lower, params["formats"]): fileName = f"{baseName}.{format}" path = os.path.join(directory, fileName) try: self._font.export(path, format, compression=compression) except Exception as e: msg = ( self.tr("This font’s feature file contains an error.") if isinstance(e, FeatureLibError) else None ) errorReports.showCriticalException(e, message=msg) # Edit def undo(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() else: glyph = widget.lastSelectedGlyph() glyph.undo() def redo(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() else: glyph = widget.lastSelectedGlyph() glyph.redo() def cut(self): self.copy() widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() deleteUISelection(glyph) else: glyphs = widget.glyphs() for index in widget.selection(): glyph = glyphs[index] glyph.clear() def copy(self): font = self._font widget = self.stackWidget.currentWidget() clipboard = QApplication.clipboard() mimeData = QMimeData() if self.isGlyphTab(): glyph = widget.activeGlyph() copyGlyph = glyph.getRepresentation("TruFont.FilterSelection") packGlyphs = (copyGlyph,) else: glyphs = self.glyphCellView.glyphs() packGlyphs = ( glyphs[index] for index in sorted(self.glyphCellView.selection()) ) svgGlyphs = [] pickled = [] for i, glyph in enumerate(packGlyphs): pickled.append(glyph.serialize(blacklist=("name", "unicodes"))) pen = SVGPathPen(font) glyph.draw(pen) col = i % 5 row = i // 5 g = '<g transform="matrix(1,0,0,-1,{:f},{:f})"><path d="{}"/></g>'.format( font.info.unitsPerEm * col, font.info.unitsPerEm * row, pen.getCommands(), ) svgGlyphs.append(g) mimeData.setData("application/x-trufont-glyph-data", pickle.dumps(pickled)) svg = """\ <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg version="1.0" xmlns="http://www.w3.org/2000/svg"> %s </svg> """ % "\n".join( svgGlyphs ) mimeData.setData("image/svg+xml", svg.encode("utf-8")) clipboard.setMimeData(mimeData) def copyAsComponent(self): if self.isGlyphTab(): pass else: glyphs = self.glyphCellView.glyphs() pickled = [] for index in self.glyphCellView.selection(): glyph = glyphs[index] componentGlyph = glyph.__class__() componentGlyph.width = glyph.width component = componentGlyph.instantiateComponent() component.baseGlyph = glyph.name pickled.append(componentGlyph.serialize()) clipboard = QApplication.clipboard() mimeData = QMimeData() mimeData.setData("application/x-trufont-glyph-data", pickle.dumps(pickled)) clipboard.setMimeData(mimeData) def paste(self): isGlyphTab = self.isGlyphTab() widget = self.stackWidget.currentWidget() if isGlyphTab: glyphs = (widget.activeGlyph(),) else: selection = self.glyphCellView.selection() glyphs = widget.glyphsForIndexes(selection) clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-trufont-glyph-data"): data = pickle.loads(mimeData.data("application/x-trufont-glyph-data")) if len(data) == len(glyphs): for pickled, glyph in zip(data, glyphs): if isGlyphTab: pasteGlyph = glyph.__class__() pasteGlyph.deserialize(pickled) # TODO: if we serialize selected state, we don't need # to do this pasteGlyph.selected = True if ( len(pasteGlyph) or len(pasteGlyph.components) or len(pasteGlyph.anchors) ): glyph.beginUndoGroup() glyph.holdNotifications() count = len(glyph) pen = glyph.getPointPen() # contours, components pasteGlyph.drawPoints(pen) for contour in glyph[count:]: contour.selected = True # anchors for anchor in pasteGlyph.anchors: glyph.appendAnchor(dict(anchor)) # guidelines for guideline in pasteGlyph.guidelines: glyph.appendGuideline(dict(guideline)) glyph.releaseHeldNotifications() glyph.endUndoGroup() else: glyph.deserialize(pickled) return if mimeData.hasFormat("image/svg+xml"): if len(glyphs) == 1: glyph = glyphs[0] try: svgPath = SVGPath.fromstring(mimeData.data("image/svg+xml")) except Exception: pass else: glyph.beginUndoGroup() if not isGlyphTab: glyph.clear() svgPath.draw(glyph.getPen()) glyph.endUndoGroup() return if mimeData.hasText(): if len(glyphs) == 1: glyph = glyphs[0] otherGlyph = glyph.__class__() text = mimeData.text() try: readGlyphFromString(text, otherGlyph, otherGlyph.getPointPen()) except Exception: try: svgPath = SVGPath.fromstring(text) svgPath.draw(otherGlyph.getPen()) except Exception: return glyph.beginUndoGroup() if not isGlyphTab: glyph.clear() otherGlyph.drawPoints(glyph.getPointPen()) glyph.endUndoGroup() def selectAll(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() if glyph.selected: for anchor in glyph.anchors: anchor.selected = True for component in glyph.components: component.selected = True else: glyph.selected = True else: widget.selectAll() def deselect(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False else: widget.setSelection(set()) def delete(self): modifiers = QApplication.keyboardModifiers() widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() # TODO: fuse more the two methods, they're similar and delete is # Cut except not putting in the clipboard if modifiers & Qt.AltModifier: deleteUISelection(glyph) else: preserveShape = not modifiers & Qt.ShiftModifier removeUIGlyphElements(glyph, preserveShape) else: erase = modifiers & Qt.ShiftModifier if self._proceedWithDeletion(erase): glyphs = widget.glyphsForIndexes(widget.selection()) for glyph in glyphs: font = glyph.font for layer in font.layers: if glyph.name in layer: defaultLayer = layer[glyph.name] == glyph if defaultLayer and not erase: # TODO: clear in glyph.template setter? glyph.clear() glyph.template = True else: del layer[glyph.name] def findGlyph(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() newGlyph, ok = FindDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: widget.setActiveGlyph(newGlyph) else: pass # XXX # View def zoom(self, step): if self.isGlyphTab(): widget = self.stackWidget.currentWidget() newScale = widget.scale() * pow(1.2, step) widget.zoom(newScale) self.statusBar.setSize(widget.pointSize()) else: value = self.statusBar.size() newValue = value + 10 * step self.statusBar.setSize(newValue) def resetZoom(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): widget.fitScaleBBox() else: settings.removeGlyphCellSize() cellSize = settings.glyphCellSize() self.statusBar.setSize(cellSize) def tabOffset(self, value): tab = self.tabWidget.currentTab() newTab = (tab + value) % len(self.tabWidget.tabs()) self.tabWidget.setCurrentTab(newTab) def glyphOffset(self, value): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): currentGlyph = widget.activeGlyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + value) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] widget.setActiveGlyph(glyph) else: lastSelectedCell = widget.lastSelectedCell() if lastSelectedCell is None: return newIndex = lastSelectedCell + value if newIndex < 0 or newIndex >= len(widget.glyphs()): return widget.setSelection({newIndex}) def layerOffset(self, value): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): currentGlyph = widget.activeGlyph() layerSet, layer = currentGlyph.layerSet, currentGlyph.layer if None in (layerSet, layer): return index = layerSet.layerOrder.index(layer.name) newIndex = (index + value) % len(layerSet) layer_ = layerSet[layerSet.layerOrder[newIndex]] if layer_ == layer: return # XXX: fix get # glyph = layer_.get(currentGlyph.name) if currentGlyph.name in layer_: glyph = layer_[currentGlyph.name] else: glyph = layer_.newGlyph(currentGlyph.name) widget.setActiveGlyph(glyph) # Font def fontInfo(self): # If a window is already opened, bring it to the front, else spawn one. # TODO: see about using widget.setAttribute(Qt.WA_DeleteOnClose) # otherwise it seems we're just leaking memory after each close... # (both raise_ and show allocate memory instead of using the hidden # widget it seems) if self._infoWindow is not None and self._infoWindow.isVisible(): self._infoWindow.raise_() else: self._infoWindow = FontInfoWindow(self._font, self) self._infoWindow.show() def fontFeatures(self): # TODO: see up here if self._featuresWindow is not None and self._featuresWindow.isVisible(): self._featuresWindow.raise_() else: self._featuresWindow = FontFeaturesWindow(self._font, self) self._featuresWindow.show() def addGlyphs(self): glyphs = self.glyphCellView.glyphs() newGlyphNames, params, ok = AddGlyphsDialog.getNewGlyphNames(self, glyphs) if ok: sortFont = params.pop("sortFont") for name in newGlyphNames: glyph = self._font.get(name, **params) if glyph is not None: glyphs.append(glyph) self.glyphCellView.setGlyphs(glyphs) if sortFont: # TODO: when the user add chars from a glyphSet and no others, # should we try to sort according to that glyphSet? # The above would probably warrant some rearchitecturing. # kick-in the sort mechanism self._font.sortDescriptor = self._font.sortDescriptor def sortGlyphs(self): sortDescriptor, ok = SortDialog.getDescriptor(self, self._font.sortDescriptor) if ok: self._font.sortDescriptor = sortDescriptor # Window def groups(self): # TODO: see up here if self._groupsWindow is not None and self._groupsWindow.isVisible(): self._groupsWindow.raise_() else: self._groupsWindow = GroupsWindow(self._font, self) self._groupsWindow.show() def kerning(self): # TODO: see up here if self._kerningWindow is not None and self._kerningWindow.isVisible(): self._kerningWindow.raise_() else: self._kerningWindow = KerningWindow(self._font, self) self._kerningWindow.show() def metrics(self): # TODO: see up here if self._metricsWindow is not None and self._metricsWindow.isVisible(): self._metricsWindow.raise_() else: self._metricsWindow = MetricsWindow(self._font) # XXX: need proper, fast windowForFont API! self._metricsWindow._fontWindow = self self.destroyed.connect(self._metricsWindow.close) self._metricsWindow.show() # TODO: default string kicks-in on the window before this. Figure out # how to make a clean interface selection = self.glyphCellView.selection() if selection: glyphs = self.glyphCellView.glyphsForIndexes(selection) self._metricsWindow.setGlyphs(glyphs) def properties(self): shouldBeVisible = self.propertiesView.isHidden() self.propertiesView.setVisible(shouldBeVisible) self.writeSettings() # update methods def _setGlyphPreview(self, value): index = self.stackWidget.currentIndex() if index: widget = self.stackWidget.currentWidget() widget.setPreviewEnabled(value) def _updateCurrentGlyph(self): # TODO: refactor this pattern... widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() else: glyph = widget.lastSelectedGlyph() if glyph is not None: app = QApplication.instance() app.setCurrentGlyph(glyph) def _updateGlyphActions(self): if not hasattr(self, "_undoAction"): return widget = self.stackWidget.currentWidget() if self.isGlyphTab(): currentGlyph = widget.activeGlyph() else: currentGlyph = widget.lastSelectedGlyph() # disconnect eventual signal of previous glyph objects = ((self._undoAction, self.undo), (self._redoAction, self.redo)) for action, slot in objects: try: action.disconnect() except TypeError: pass action.triggered.connect(slot) # now update status if currentGlyph is None: self._undoAction.setEnabled(False) self._redoAction.setEnabled(False) else: undoManager = currentGlyph.undoManager self._undoAction.setEnabled(currentGlyph.canUndo()) undoManager.canUndoChanged.connect(self._undoAction.setEnabled) self._redoAction.setEnabled(currentGlyph.canRedo()) undoManager.canRedoChanged.connect(self._redoAction.setEnabled) # and other actions for action in self._clipboardActions: action.setEnabled(currentGlyph is not None) # helper def _proceedWithDeletion(self, erase=False): if not self.glyphCellView.selection(): return tr = self.tr("Delete") if erase else self.tr("Clear") text = self.tr("Do you want to %s selected glyphs?") % tr.lower() closeDialog = QMessageBox( QMessageBox.Question, "", self.tr("%s glyphs") % tr, QMessageBox.Yes | QMessageBox.No, self, ) closeDialog.setInformativeText(text) closeDialog.setModal(True) ret = closeDialog.exec_() if ret == QMessageBox.Yes: return True return False # ---------- # Qt methods # ---------- def setWindowTitle(self, title): if platformSpecific.appNameInTitle(): title += " – TruFont" super().setWindowTitle(f"[*]{title}") def sizeHint(self): return QSize(1270, 800) def moveEvent(self, event): self.writeSettings() resizeEvent = moveEvent def showEvent(self, event): app = QApplication.instance() data = dict(font=self._font, window=self) app.postNotification("fontWindowWillOpen", data) super().showEvent(event) app.postNotification("fontWindowOpened", data) def closeEvent(self, event): ok = self.maybeSaveBeforeExit() if ok: app = QApplication.instance() data = dict(font=self._font, window=self) app.postNotification("fontWindowWillClose", data) self._font.removeObserver(self, "Font.Changed") app = QApplication.instance() app.dispatcher.removeObserver(self, "drawingToolRegistered") app.dispatcher.removeObserver(self, "drawingToolUnregistered") app.dispatcher.removeObserver(self, "glyphViewGlyphsChanged") event.accept() else: event.ignore() def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentFontWindow(self) self._updateCurrentGlyph() return super().event(event) def paintEvent(self, event): painter = QPainter(self) painter.fillRect(event.rect(), QColor(212, 212, 212))
class SubTabWidget(QWidget): _tabChanged = pyqtSignal(int, name = "tabChanged") def __init__(self, subtitleData, videoWidget, parent = None): super(SubTabWidget, self).__init__(parent) self._subtitleData = subtitleData self.__initTabWidget(videoWidget) def __initTabWidget(self, videoWidget): settings = SubSettings() mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(0, 0, 0, 0) mainLayout.setSpacing(0) #TabBar self.tabBar = QTabBar(self) # Splitter (bookmarks + pages) self.splitter = QSplitter(self) self.splitter.setObjectName("sidebar_splitter") self._toolbox = ToolBox(self._subtitleData, self) self._toolbox.setObjectName("sidebar") self._toolbox.setMinimumWidth(100) self._toolbox.addTool(Details(self._subtitleData, self)) self._toolbox.addTool(Synchronizer(videoWidget, self._subtitleData, self)) self._toolbox.addTool(History(self)) self.rightWidget = QWidget() rightLayout = QGridLayout() rightLayout.setContentsMargins(0, 0, 0, 0) self.rightWidget.setLayout(rightLayout) self._mainTab = FileList(_("Subtitles"), self._subtitleData, self) self.pages = QStackedWidget(self) rightLayout.addWidget(self.pages, 0, 0) self.tabBar.addTab(self._mainTab.name) self.pages.addWidget(self._mainTab) self.splitter.addWidget(self._toolbox) self.splitter.addWidget(self.rightWidget) self.__drawSplitterHandle(1) # Setting widgets mainLayout.addWidget(self.tabBar) mainLayout.addWidget(self.splitter) # Widgets settings self.tabBar.setMovable(True) self.tabBar.setTabsClosable(True) self.tabBar.setExpanding(False) # Don't resize left panel if it's not needed leftWidgetIndex = self.splitter.indexOf(self._toolbox) rightWidgetIndex = self.splitter.indexOf(self.rightWidget) self.splitter.setStretchFactor(leftWidgetIndex, 0) self.splitter.setStretchFactor(rightWidgetIndex, 1) self.splitter.setCollapsible(leftWidgetIndex, False) self.splitter.setSizes([250]) # Some signals self.tabBar.currentChanged.connect(self.showTab) self.tabBar.tabCloseRequested.connect(self.closeTab) self.tabBar.tabMoved.connect(self.moveTab) self._mainTab.requestOpen.connect(self.openTab) self._mainTab.requestRemove.connect(self.removeFile) self.tabChanged.connect(lambda i: self._toolbox.setContentFor(self.tab(i))) self.setLayout(mainLayout) def __addTab(self, filePath): """Returns existing tab index. Creates a new one if it isn't opened and returns its index otherwise.""" for i in range(self.tabBar.count()): widget = self.pages.widget(i) if not widget.isStatic and filePath == widget.filePath: return i tab = SubtitleEditor(filePath, self._subtitleData, self) newIndex = self.tabBar.addTab(self._createTabName(tab.name, tab.history.isClean())) tab.history.cleanChanged.connect( lambda clean: self._cleanStateForFileChanged(filePath, clean)) self.pages.addWidget(tab) return newIndex def __drawSplitterHandle(self, index): splitterHandle = self.splitter.handle(index) splitterLayout = QVBoxLayout(splitterHandle) splitterLayout.setSpacing(0) splitterLayout.setContentsMargins(0, 0, 0, 0) line = QFrame(splitterHandle) line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) splitterLayout.addWidget(line) splitterHandle.setLayout(splitterLayout) def _createTabName(self, name, cleanState): if cleanState is True: return name else: return "%s +" % name def _cleanStateForFileChanged(self, filePath, cleanState): page = self.tabByPath(filePath) if page is not None: for i in range(self.tabBar.count()): if self.tabBar.tabText(i)[:len(page.name)] == page.name: self.tabBar.setTabText(i, self._createTabName(page.name, cleanState)) return def saveWidgetState(self, settings): settings.setState(self.splitter, self.splitter.saveState()) settings.setHidden(self._toolbox, self._toolbox.isHidden()) def restoreWidgetState(self, settings): self.showPanel(not settings.getHidden(self._toolbox)) splitterState = settings.getState(self.splitter) if not splitterState.isEmpty(): self.splitter.restoreState(settings.getState(self.splitter)) @pyqtSlot(str, bool) def openTab(self, filePath, background=False): if self._subtitleData.fileExists(filePath): tabIndex = self.__addTab(filePath) if background is False: self.showTab(tabIndex) else: log.error(_("SubtitleEditor not created for %s!" % filePath)) @pyqtSlot(str) def removeFile(self, filePath): tab = self.tabByPath(filePath) command = RemoveFile(filePath) if tab is not None: index = self.pages.indexOf(tab) if self.closeTab(index): self._subtitleData.execute(command) else: self._subtitleData.execute(command) @pyqtSlot(int) def closeTab(self, index): tab = self.tab(index) if tab.canClose(): widgetToRemove = self.pages.widget(index) self.tabBar.removeTab(index) self.pages.removeWidget(widgetToRemove) widgetToRemove.deleteLater() return True return False def count(self): return self.tabBar.count() def currentIndex(self): return self.tabBar.currentIndex() def currentPage(self): return self.pages.currentWidget() @pyqtSlot(int, int) def moveTab(self, fromIndex, toIndex): fromWidget = self.pages.widget(fromIndex) toWidget = self.pages.widget(toIndex) if fromWidget.isStatic or toWidget.isStatic: self.tabBar.blockSignals(True) # signals would cause infinite recursion self.tabBar.moveTab(toIndex, fromIndex) self.tabBar.blockSignals(False) return else: self.pages.removeWidget(fromWidget) self.pages.removeWidget(toWidget) if fromIndex < toIndex: self.pages.insertWidget(fromIndex, toWidget) self.pages.insertWidget(toIndex, fromWidget) else: self.pages.insertWidget(toIndex, fromWidget) self.pages.insertWidget(fromIndex, toWidget) # Hack # Qt changes tabs during mouse drag and dropping. The next line is added # to prevent it. self.showTab(self.tabBar.currentIndex()) @pyqtSlot(int) def showTab(self, index): showWidget = self.pages.widget(index) if showWidget: self.pages.setCurrentWidget(showWidget) self.tabBar.blockSignals(True) self.tabBar.setCurrentIndex(index) self.tabBar.blockSignals(False) # Try to update current tab. showWidget.updateTab() self._tabChanged.emit(index) def showPanel(self, val): if val is True: self._toolbox.show() else: self._toolbox.hide() def togglePanel(self): if self._toolbox.isHidden(): self._toolbox.show() else: self._toolbox.hide() def tab(self, index): return self.pages.widget(index) def tabByPath(self, path): for i in range(self.pages.count()): page = self.tab(i) if not page.isStatic and page.filePath == path: return page return None @property def fileList(self): return self._mainTab
class E5SideBar(QWidget): """ Class implementing a sidebar with a widget area, that is hidden or shown, if the current tab is clicked again. """ Version = 1 North = 0 East = 1 South = 2 West = 3 def __init__(self, orientation=None, delay=200, parent=None): """ Constructor @param orientation orientation of the sidebar widget (North, East, South, West) @param delay value for the expand/shrink delay in milliseconds (integer) @param parent parent widget (QWidget) """ super(E5SideBar, self).__init__(parent) self.__tabBar = QTabBar() self.__tabBar.setDrawBase(True) self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setUsesScrollButtons(True) self.__tabBar.setDrawBase(False) self.__stackedWidget = QStackedWidget(self) self.__stackedWidget.setContentsMargins(0, 0, 0, 0) self.__autoHideButton = QToolButton() self.__autoHideButton.setCheckable(True) self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) self.__autoHideButton.setChecked(True) self.__autoHideButton.setToolTip( self.tr("Deselect to activate automatic collapsing")) self.barLayout = QBoxLayout(QBoxLayout.LeftToRight) self.barLayout.setContentsMargins(0, 0, 0, 0) self.layout = QBoxLayout(QBoxLayout.TopToBottom) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.barLayout.addWidget(self.__autoHideButton) self.barLayout.addWidget(self.__tabBar) self.layout.addLayout(self.barLayout) self.layout.addWidget(self.__stackedWidget) self.setLayout(self.layout) # initialize the delay timer self.__actionMethod = None self.__delayTimer = QTimer(self) self.__delayTimer.setSingleShot(True) self.__delayTimer.setInterval(delay) self.__delayTimer.timeout.connect(self.__delayedAction) self.__minimized = False self.__minSize = 0 self.__maxSize = 0 self.__bigSize = QSize() self.splitter = None self.splitterSizes = [] self.__hasFocus = False # flag storing if this widget or any child has the focus self.__autoHide = False self.__tabBar.installEventFilter(self) self.__orientation = E5SideBar.North if orientation is None: orientation = E5SideBar.North self.setOrientation(orientation) self.__tabBar.currentChanged[int].connect( self.__stackedWidget.setCurrentIndex) e5App().focusChanged[QWidget, QWidget].connect(self.__appFocusChanged) self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled) def setSplitter(self, splitter): """ Public method to set the splitter managing the sidebar. @param splitter reference to the splitter (QSplitter) """ self.splitter = splitter self.splitter.splitterMoved.connect(self.__splitterMoved) self.splitter.setChildrenCollapsible(False) index = self.splitter.indexOf(self) self.splitter.setCollapsible(index, False) def __splitterMoved(self, pos, index): """ Private slot to react on splitter moves. @param pos new position of the splitter handle (integer) @param index index of the splitter handle (integer) """ if self.splitter: self.splitterSizes = self.splitter.sizes() def __delayedAction(self): """ Private slot to handle the firing of the delay timer. """ if self.__actionMethod is not None: self.__actionMethod() def setDelay(self, delay): """ Public method to set the delay value for the expand/shrink delay in milliseconds. @param delay value for the expand/shrink delay in milliseconds (integer) """ self.__delayTimer.setInterval(delay) def delay(self): """ Public method to get the delay value for the expand/shrink delay in milliseconds. @return value for the expand/shrink delay in milliseconds (integer) """ return self.__delayTimer.interval() def __cancelDelayTimer(self): """ Private method to cancel the current delay timer. """ self.__delayTimer.stop() self.__actionMethod = None def shrink(self): """ Public method to record a shrink request. """ self.__delayTimer.stop() self.__actionMethod = self.__shrinkIt self.__delayTimer.start() def __shrinkIt(self): """ Private method to shrink the sidebar. """ self.__minimized = True self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() if self.splitter: self.splitterSizes = self.splitter.sizes() self.__stackedWidget.hide() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.setFixedHeight(self.__tabBar.minimumSizeHint().height()) else: self.setFixedWidth(self.__tabBar.minimumSizeHint().width()) self.__actionMethod = None def expand(self): """ Public method to record a expand request. """ self.__delayTimer.stop() self.__actionMethod = self.__expandIt self.__delayTimer.start() def __expandIt(self): """ Private method to expand the sidebar. """ self.__minimized = False self.__stackedWidget.show() self.resize(self.__bigSize) if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = max(self.__minSize, self.minimumSizeHint().height()) self.setMinimumHeight(minSize) self.setMaximumHeight(self.__maxSize) else: minSize = max(self.__minSize, self.minimumSizeHint().width()) self.setMinimumWidth(minSize) self.setMaximumWidth(self.__maxSize) if self.splitter: self.splitter.setSizes(self.splitterSizes) self.__actionMethod = None def isMinimized(self): """ Public method to check the minimized state. @return flag indicating the minimized state (boolean) """ return self.__minimized def isAutoHiding(self): """ Public method to check, if the auto hide function is active. @return flag indicating the state of auto hiding (boolean) """ return self.__autoHide def eventFilter(self, obj, evt): """ Public method to handle some events for the tabbar. @param obj reference to the object (QObject) @param evt reference to the event object (QEvent) @return flag indicating, if the event was handled (boolean) """ if obj == self.__tabBar: if evt.type() == QEvent.MouseButtonPress: pos = evt.pos() for i in range(self.__tabBar.count()): if self.__tabBar.tabRect(i).contains(pos): break if i == self.__tabBar.currentIndex(): if self.isMinimized(): self.expand() else: self.shrink() return True elif self.isMinimized(): self.expand() elif evt.type() == QEvent.Wheel: if qVersion() >= "5.0.0": delta = evt.angleDelta().y() else: delta = evt.delta() if delta > 0: self.prevTab() else: self.nextTab() return True return QWidget.eventFilter(self, obj, evt) def addTab(self, widget, iconOrLabel, label=None): """ Public method to add a tab to the sidebar. @param widget reference to the widget to add (QWidget) @param iconOrLabel reference to the icon or the label text of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.addTab(iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.addTab(iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.addWidget(widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def insertTab(self, index, widget, iconOrLabel, label=None): """ Public method to insert a tab into the sidebar. @param index the index to insert the tab at (integer) @param widget reference to the widget to insert (QWidget) @param iconOrLabel reference to the icon or the labeltext of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.insertTab(index, iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.insertTab(index, iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.insertWidget(index, widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def removeTab(self, index): """ Public method to remove a tab. @param index the index of the tab to remove (integer) """ self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index)) self.__tabBar.removeTab(index) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def clear(self): """ Public method to remove all tabs. """ while self.count() > 0: self.removeTab(0) def prevTab(self): """ Public slot used to show the previous tab. """ ind = self.currentIndex() - 1 if ind == -1: ind = self.count() - 1 self.setCurrentIndex(ind) self.currentWidget().setFocus() def nextTab(self): """ Public slot used to show the next tab. """ ind = self.currentIndex() + 1 if ind == self.count(): ind = 0 self.setCurrentIndex(ind) self.currentWidget().setFocus() def count(self): """ Public method to get the number of tabs. @return number of tabs in the sidebar (integer) """ return self.__tabBar.count() def currentIndex(self): """ Public method to get the index of the current tab. @return index of the current tab (integer) """ return self.__stackedWidget.currentIndex() def setCurrentIndex(self, index): """ Public slot to set the current index. @param index the index to set as the current index (integer) """ self.__tabBar.setCurrentIndex(index) self.__stackedWidget.setCurrentIndex(index) if self.isMinimized(): self.expand() def currentWidget(self): """ Public method to get a reference to the current widget. @return reference to the current widget (QWidget) """ return self.__stackedWidget.currentWidget() def setCurrentWidget(self, widget): """ Public slot to set the current widget. @param widget reference to the widget to become the current widget (QWidget) """ self.__stackedWidget.setCurrentWidget(widget) self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex()) if self.isMinimized(): self.expand() def indexOf(self, widget): """ Public method to get the index of the given widget. @param widget reference to the widget to get the index of (QWidget) @return index of the given widget (integer) """ return self.__stackedWidget.indexOf(widget) def isTabEnabled(self, index): """ Public method to check, if a tab is enabled. @param index index of the tab to check (integer) @return flag indicating the enabled state (boolean) """ return self.__tabBar.isTabEnabled(index) def setTabEnabled(self, index, enabled): """ Public method to set the enabled state of a tab. @param index index of the tab to set (integer) @param enabled enabled state to set (boolean) """ self.__tabBar.setTabEnabled(index, enabled) def orientation(self): """ Public method to get the orientation of the sidebar. @return orientation of the sidebar (North, East, South, West) """ return self.__orientation def setOrientation(self, orient): """ Public method to set the orientation of the sidebar. @param orient orientation of the sidebar (North, East, South, West) """ if orient == E5SideBar.North: self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.TopToBottom) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.East: self.__tabBar.setShape(QTabBar.RoundedEast) self.__tabBar.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.RightToLeft) self.layout.setAlignment(self.barLayout, Qt.AlignTop) elif orient == E5SideBar.South: self.__tabBar.setShape(QTabBar.RoundedSouth) self.__tabBar.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.BottomToTop) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.West: self.__tabBar.setShape(QTabBar.RoundedWest) self.__tabBar.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.LeftToRight) self.layout.setAlignment(self.barLayout, Qt.AlignTop) self.__orientation = orient def tabIcon(self, index): """ Public method to get the icon of a tab. @param index index of the tab (integer) @return icon of the tab (QIcon) """ return self.__tabBar.tabIcon(index) def setTabIcon(self, index, icon): """ Public method to set the icon of a tab. @param index index of the tab (integer) @param icon icon to be set (QIcon) """ self.__tabBar.setTabIcon(index, icon) def tabText(self, index): """ Public method to get the text of a tab. @param index index of the tab (integer) @return text of the tab (string) """ return self.__tabBar.tabText(index) def setTabText(self, index, text): """ Public method to set the text of a tab. @param index index of the tab (integer) @param text text to set (string) """ self.__tabBar.setTabText(index, text) def tabToolTip(self, index): """ Public method to get the tooltip text of a tab. @param index index of the tab (integer) @return tooltip text of the tab (string) """ return self.__tabBar.tabToolTip(index) def setTabToolTip(self, index, tip): """ Public method to set the tooltip text of a tab. @param index index of the tab (integer) @param tip tooltip text to set (string) """ self.__tabBar.setTabToolTip(index, tip) def tabWhatsThis(self, index): """ Public method to get the WhatsThis text of a tab. @param index index of the tab (integer) @return WhatsThis text of the tab (string) """ return self.__tabBar.tabWhatsThis(index) def setTabWhatsThis(self, index, text): """ Public method to set the WhatsThis text of a tab. @param index index of the tab (integer) @param text WhatsThis text to set (string) """ self.__tabBar.setTabWhatsThis(index, text) def widget(self, index): """ Public method to get a reference to the widget associated with a tab. @param index index of the tab (integer) @return reference to the widget (QWidget) """ return self.__stackedWidget.widget(index) def saveState(self): """ Public method to save the state of the sidebar. @return saved state as a byte array (QByteArray) """ if len(self.splitterSizes) == 0: if self.splitter: self.splitterSizes = self.splitter.sizes() self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) stream.writeUInt16(self.Version) stream.writeBool(self.__minimized) stream << self.__bigSize stream.writeUInt16(self.__minSize) stream.writeUInt16(self.__maxSize) stream.writeUInt16(len(self.splitterSizes)) for size in self.splitterSizes: stream.writeUInt16(size) stream.writeBool(self.__autoHide) return data def restoreState(self, state): """ Public method to restore the state of the sidebar. @param state byte array containing the saved state (QByteArray) @return flag indicating success (boolean) """ if state.isEmpty(): return False if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = self.layout.minimumSize().height() maxSize = self.maximumHeight() else: minSize = self.layout.minimumSize().width() maxSize = self.maximumWidth() data = QByteArray(state) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) stream.readUInt16() # version minimized = stream.readBool() if minimized and not self.__minimized: self.shrink() stream >> self.__bigSize self.__minSize = max(stream.readUInt16(), minSize) self.__maxSize = max(stream.readUInt16(), maxSize) count = stream.readUInt16() self.splitterSizes = [] for i in range(count): self.splitterSizes.append(stream.readUInt16()) self.__autoHide = stream.readBool() self.__autoHideButton.setChecked(not self.__autoHide) if not minimized: self.expand() return True ####################################################################### ## methods below implement the autohide functionality ####################################################################### def __autoHideToggled(self, checked): """ Private slot to handle the toggling of the autohide button. @param checked flag indicating the checked state of the button (boolean) """ self.__autoHide = not checked if self.__autoHide: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOn.png")) else: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) def __appFocusChanged(self, old, now): """ Private slot to handle a change of the focus. @param old reference to the widget, that lost focus (QWidget or None) @param now reference to the widget having the focus (QWidget or None) """ self.__hasFocus = self.isAncestorOf(now) if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() elif self.__autoHide and self.__hasFocus and self.isMinimized(): self.expand() def enterEvent(self, event): """ Protected method to handle the mouse entering this widget. @param event reference to the event (QEvent) """ if self.__autoHide and self.isMinimized(): self.expand() else: self.__cancelDelayTimer() def leaveEvent(self, event): """ Protected method to handle the mouse leaving this widget. @param event reference to the event (QEvent) """ if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() else: self.__cancelDelayTimer() def shutdown(self): """ Public method to shut down the object. This method does some preparations so the object can be deleted properly. It disconnects from the focusChanged signal in order to avoid trouble later on. """ e5App().focusChanged[QWidget, QWidget].disconnect( self.__appFocusChanged)
class BottomPanel(QWidget): def __init__(self, app, parent=None): super().__init__(parent) self._app = app self._layout = QHBoxLayout(self) self.back_btn = ToolbarButton('⇦', self) self.forward_btn = ToolbarButton('⇨', self) self.magicbox = MagicBox(self._app) self._stack_switch = ToolbarButton('⁐', self) self._stacked_widget = QStackedWidget(self) self._stacked_widget.addWidget(self.magicbox) self._stack_switch.hide() self.status_line = StatusLine(self._app) self.settings_btn = ToolbarButton('⋮', self) self.settings_btn.setToolTip('配置') # initialize widgets self.status_line.add_item(StatusLineItem('plugin', PluginStatus(self._app))) self.status_line.add_item(StatusLineItem('notify', NotifyStatus(self._app))) self.back_btn.setEnabled(False) self.forward_btn.setEnabled(False) self.back_btn.clicked.connect(self._app.browser.back) self.forward_btn.clicked.connect(self._app.browser.forward) self._stack_switch.clicked.connect(self._show_next_stacked_widget) self._setup_ui() def _setup_ui(self): self.setObjectName('bottom_panel') self._layout.addSpacing(5) self._layout.addWidget(self.back_btn) self._layout.addSpacing(5) self._layout.addWidget(self.forward_btn) self._layout.addSpacing(80) self._layout.addWidget(self._stacked_widget) self._layout.addSpacing(10) self._layout.addWidget(self._stack_switch) # self._layout.addStretch(0) self._layout.addSpacing(80) self._layout.addWidget(self.status_line) self._layout.addWidget(self.settings_btn) # assume the magicbox height is about 30 h_margin, v_margin = 5, 10 height = self.magicbox.height() self.setFixedHeight(height + v_margin * 2 + 10) self._layout.setContentsMargins(h_margin, v_margin, h_margin, v_margin) self._layout.setSpacing(0) def _show_next_stacked_widget(self): current_index = self._stacked_widget.currentIndex() if current_index < self._stacked_widget.count() - 1: next_index = current_index + 1 else: next_index = 0 self._stacked_widget.setCurrentIndex(next_index) def add_stacked_widget(self, widget): """ .. versionadded:: 3.7.10 """ self._stacked_widget.addWidget(widget) if self._stacked_widget.count() > 1: self._stack_switch.show() def set_top_stacked_widget(self, widget): """ .. versionadded:: 3.7.10 """ self._stacked_widget.setCurrentWidget(widget) def clear_stacked_widget(self): """ .. versionadded:: 3.7.10 """ while self._stacked_widget.count() > 0: self._stacked_widget.removeWidget(self._stacked_widget.currentWidget()) self._stack_switch.hide() def show_and_focus_magicbox(self): """Show and focus magicbox .. versionadded:: 3.7.10 """ if self._stacked_widget.indexOf(self.magicbox) != -1: self.set_top_stacked_widget(self.magicbox) self.magicbox.setFocus()
class TabWidget(TabStackedWidget): def __init__(self, window, parent=None): super(TabWidget, self).__init__(parent) self._window = None # BrowserWindow self._tabBar = None # TabBar self._locationBars = None # QStackedWidget self._closedTabsManager = None # ClosedTabsManager self._menuTabs = None # MenuTabs self._buttonListTabs = None # ToolButton self._menuClosedTabs = None # QMenu self._buttonClosedTabs = None # ToolButton self._buttonAddTab = None # AddTabButton self._buttonAddTab2 = None # AddTabButton self._lastBackgroundTab = None # QPoint<WebTab> self._dontCloseWithOneTab = False self._showClosedTabsButton = False self._newTabAfterActive = False self._newEmptyTabAfterActive = False self._urlOnNewTab = QUrl() # QUrl self._currentTabFresh = False self._blockTabMovedSignal = False # start init self._window = window self._locationBars = QStackedWidget() self._closedTabsManager = ClosedTabsManager() self.setObjectName('tabWidget') self._tabBar = TabBar(self._window, self) self.setTabBar(self._tabBar) self.changed.connect(gVar.app.changeOccurred) self.pinStateChanged.connect(self.changed) self._tabBar.tabCloseRequested.connect(self.requestCloseTab) self._tabBar.tabMoved.connect(self._tabWasMoved) self._tabBar.moveAddTabButton.connect(self.moveAddTabButton) gVar.app.settingsReloaded.connect(self._loadSettings) self._menuTabs = MenuTabs(self) self._menuTabs.closeTab.connect(self.requestCloseTab) self._menuClosedTabs = QMenu(self) # AddTab button displayed next to last tab self._buttonAddTab = AddTabButton(self, self._tabBar) self._buttonAddTab.setProperty('outside-tabbar', False) self._buttonAddTab.clicked.connect(self._window.addTab) # AddTab button displayed ouside tabbar (as corner widget) self._buttonAddTab2 = AddTabButton(self, self._tabBar) self._buttonAddTab2.setProperty('outside-tabbar', True) self._buttonAddTab2.hide() self._buttonAddTab2.clicked.connect(self._window.addTab) # ClosedTabs button displayed as a permanet corner widget self._buttonClosedTabs = ToolButton(self._tabBar) self._buttonClosedTabs.setObjectName('tabwidget-button-closedtabs') self._buttonClosedTabs.setMenu(self._menuClosedTabs) self._buttonClosedTabs.setPopupMode(QToolButton.InstantPopup) self._buttonClosedTabs.setToolTip('Closed tabs') self._buttonClosedTabs.setAutoRaise(True) self._buttonClosedTabs.setFocusPolicy(Qt.NoFocus) self._buttonClosedTabs.setShowMenuInside(True) self._buttonClosedTabs.aboutToShowMenu.connect( self._aboutToShowClosedTabsMenu) # ListTabs button is showed only when tabbar overflows self._buttonListTabs = ToolButton(self._tabBar) self._buttonListTabs.setObjectName('tabwidget-button-opentabs') self._buttonListTabs.setMenu(self._menuTabs) self._buttonListTabs.setPopupMode(QToolButton.InstantPopup) self._buttonListTabs.setToolTip('List of tabs') self._buttonListTabs.setAutoRaise(True) self._buttonListTabs.setFocusPolicy(Qt.NoFocus) self._buttonListTabs.setShowMenuInside(True) self._buttonListTabs.hide() self._buttonListTabs.aboutToShowMenu.connect(self._aboutToShowTabsMenu) self._tabBar.addCornerWidget(self._buttonAddTab2, Qt.TopRightCorner) self._tabBar.addCornerWidget(self._buttonClosedTabs, Qt.TopRightCorner) self._tabBar.addCornerWidget(self._buttonListTabs, Qt.TopRightCorner) self._tabBar.overFlowChanged.connect(self.tabBarOverFlowChanged) self._loadSettings() def browserWindow(self): return self._window def restoreState(self, tabs, currentTab): ''' @param: tabs QVector<WebTab::SavedTab> ''' if not tabs: return False childTabs = [] # QVector<QPair<WebTab*, QVector<int>>> for tab in tabs: index = self.addViewByUrl(QUrl(), const.NT_CleanSelectedTab, False, tab.isPinned) webTab = self._weTab(index) webTab.restoreTab(tab) if tab.childTabs: childTabs.append([webTab, tab.childTabs]) for webTab, tabs in childTabs: for index in tabs: t = self._weTab(index) if t: webTab.addChildTab(t) self.setCurrentIndex(currentTab) QTimer.singleShot(0, self._tabBar.ensureVisible) self._weTab().tabActivated() return True def setCurrentIndex(self, index): super(TabWidget, self).setCurrentIndex(index) def nextTab(self): index = (self.currentIndex() + 1) % self.count() self.setCurrentIndex(index) def previousTab(self): if self.currentIndex() == 0: index = self.count() - 1 else: index = self.currentIndex() - 1 self.setCurrentIndex(index) def currentTabChanged(self, index): if not self._validIndex(index): return self._lastBackgroundTab = None self._currentTabFresh = False webTab = self._weTab(index) webTab.tabActivated() locBar = webTab.locationBar() if locBar and self._locationBars.indexOf(locBar) != -1: self._locationBars.setCurrentWidget(locBar) self._window.currentTabChanged() self.changed.emit() def normalTabsCount(self): return self._tabBar.normalTabsCount() def pinnedTabsCount(self): return self._tabBar.pinnedTabsCount() def extraReservedWidth(self): return self._buttonAddTab.width() def webTab(self, index=-1): ''' @return: WebTab ''' if index < 0: return self._weTab() else: return self._weTab(index) def tabBar(self): ''' @return: TabBar ''' return self._tabBar def closedTabsManager(self): ''' @return: ClosedTabsManager ''' return self._closedTabsManager def allTabs(self, withPinned=True): ''' @return: QList<WebTab*> ''' allTabs = [] for idx in range(self.count()): tab = self._weTab(idx) if not tab or (not withPinned and tab.isPinned()): continue allTabs.append(tab) return allTabs def canRestoreTab(self): return self._closedTabsManager.isClosedTabAvailable() def isCurrentTabFresh(self): return self._currentTabFresh def setCurrentTabFresh(self, currentTabFresh): self._currentTabFresh = currentTabFresh def locationBars(self): ''' @return: QStackedWidget ''' return self._locationBars def buttonClosedTabs(self): ''' @return: ToolButton ''' return self._buttonClosedTabs def buttonAddTab(self): ''' @return: AddTabButton ''' return self._buttonAddTab def moveTab(self, from_, to_): if not self._validIndex(to_) or from_ == to_: return tab = self.webTab(from_) if not tab: return self._blockTabMovedSignal = True # (Un)pin tab when needed if (tab.isPinned() and to_ >= self.pinnedTabsCount()) or \ (not tab.isPinned() and to_ < self.pinnedTabsCount()): tab.togglePinned() super(TabWidget, self).moveTab(from_, to_) self._blockTabMovedSignal = False self.tabMoved.emit(from_, to_) def pinUnPinTab(self, index, title=''): newIndex = super(TabWidget, self).pinUnPinTab(index, title) if index != newIndex and not self._blockTabMovedSignal: self.tabMoved.emit(index, newIndex) return newIndex def detachTab(self, tab): ''' @param: tab WebTab ''' assert (tab) if self.count() == 1 and gVar.app.windowCount() == 1: return self._locationBars.removeWidget(tab.locationBar()) tab.webView().wantsCloseTab.disconnect(self.closeTab) tab.webView().urlChanged.disconnect(self.changed) tab.webView().ipChanged.disconnect(self._window.ipLabel().setText) index = tab.tabIndex() tab.detach() tab.setPinned(False) self.tabRemoved.emit(index) if self.count() == 0: self._window.close() def addViewByUrl(self, url, openFlags, selectLine=False, pinned=False): return self.addViewByReq(LoadRequest(url), openFlags, selectLine, pinned) # Q_SLOTS: def addViewByReq(self, req, openFlags, selectLine=False, pinned=False): ''' @param: req LoadRequest @param: openFlags Qz::NewTabPositionFlags @param: selectLine bool @param: pinned bool ''' assert (isinstance(req, LoadRequest)) return self.addViewByReqTitle(req, '', openFlags, selectLine, -1, pinned) def addViewByUrlTitle( self, url, title="New tab", openFlags=const.NT_SelectedTab, # noqa C901 selectLine=False, position=-1, pinned=False): return self.addViewByReqTitle(LoadRequest(url), title, openFlags, selectLine, position, pinned) def addViewByReqTitle( self, req, title="New tab", openFlags=const.NT_SelectedTab, # noqa C901 selectLine=False, position=-1, pinned=False): ''' @param: req LoadRequest @param: title QString @param: openFlags Qz::NewTabPositionFlags @param: selectLine bool @param: position int @param: pinned bool ''' if isinstance(req, QUrl): req = LoadRequest(req) url = req.url() self._currentTabFresh = False if url.isEmpty() and not (openFlags & const.NT_CleanTab): url = self._urlOnNewTab openAfterActive = self._newTabAfterActive and not ( openFlags & const.NT_TabAtTheEnd) if openFlags == const.NT_SelectedNewEmptyTab and self._newEmptyTabAfterActive: openAfterActive = True if openAfterActive and position == -1: # If we are opening newBgTab from pinned tab, make sure it won't be # openned between other pinned tabs if openFlags & const.NT_NotSelectedTab and self._lastBackgroundTab: position = self._lastBackgroundTab.tabIndex() + 1 else: position = max(self.currentIndex() + 1, self._tabBar.pinnedTabsCount()) webTab = WebTab(self._window) webTab.setPinned(pinned) webTab.locationBar().showUrl(url) self._locationBars.addWidget(webTab.locationBar()) if position == -1: insertIndex = self.count() else: insertIndex = position index = self.insertTab(insertIndex, webTab, '', pinned) webTab.attach(self._window) if title: self._tabBar.setTabText(index, title) if openFlags & const.NT_SelectedTab: self.setCurrentIndex(index) else: self._lastBackgroundTab = webTab webTab.webView().wantsCloseTab.connect(self.closeTab) webTab.webView().urlChanged.connect(self.changed) webTab.webView().ipChanged.connect(self._window.ipLabel().setText) def urlChangedCb(url): if url != self._urlOnNewTab: self._currentTabFresh = False webTab.webView().urlChanged.connect(urlChangedCb) if url.isValid() and url != req.url(): r = LoadRequest(req) r.setUrl(url) webTab.webView().loadByReq(r) elif req.url().isValid(): webTab.webView().loadByReq(req) if selectLine and not self._window.locationBar().text(): self._window.locationBar().setFocus() # Make sure user notice opening new background tabs if not (openFlags & const.NT_SelectedTab): self._tabBar.ensureVisible(index) self.changed.emit() self.tabInserted.emit(index) return index def addViewByTab(self, tab, openFlags): ''' @param: tab WebTab @param: openFlags Qz::NewTabPositionFlags ''' return self.insertView(self.count() + 1, tab, openFlags) def insertView(self, index, tab, openFlags): ''' @param: index int @param: tab WebTab @param: openFlags Qz::NewTabPositionFlags ''' self._locationBars.addWidget(tab.locationBar()) newIndex = self.insertTab(index, tab, '', tab.isPinned()) tab.attach(self._window) if openFlags & const.NT_SelectedTab: self.setCurrentIndex(newIndex) else: self._lastBackgroundTab = tab tab.webView().wantsCloseTab.connect(self.closeTab) tab.webView().urlChanged.connect(self.changed) tab.webView().ipChanged.connect(self._window.ipLabel().setText) # Make sure user notice opening new background tabs if not (openFlags & const.NT_SelectedTab): self._tabBar.ensureVisible(index) self.changed.emit() self.tabInserted.emit(newIndex) return newIndex def addTabFromClipboard(self): # QString selectionClipboard = QApplication.clipboard().text( QClipboard.Selection) # QUrl guessedUrl = QUrl.fromUserInput(selectionClipboard) if not guessedUrl.isEmpty(): self.addViewByUrl(guessedUrl, const.NT_SelectedNewEmptyTab) def duplicateTab(self, index): if not self._validIndex(index): return webTab = self._weTab(index) index = self.addViewByUrlTitle(QUrl(), webTab.title(), const.NT_CleanSelectedTab) newWebTab = self._weTab(index) newWebTab.p_restoreTabByUrl(webTab.url(), webTab.historyData(), webTab.zoomLevel()) return index def closeTab(self, index=-1): ''' @brief: Force close tab ''' if index == -1: index = self.currentIndex() webTab = self._weTab(index) if not webTab or not self._validIndex(index): return # This is already handled in requestClosetab if self.count() <= 1: self.requestCloseTab(index) return self._closedTabsManager.saveTab(webTab) webView = webTab.webView() self._locationBars.removeWidget(webView.webTab().locationBar()) webView.wantsCloseTab.disconnect(self.closeTab) webView.urlChanged.disconnect(self.changed) webView.ipChanged.disconnect(self._window.ipLabel().setText) self._lastBackgroundTab = None webTab.detach() webTab.deleteLater() self._updateClosedTabsButton() self.changed.emit() self.tabRemoved.emit(index) def requestCloseTab(self, index=-1): ''' @brief: Request close tab (may be rejected) ''' if index == -1: index = self.currentIndex() webTab = self._weTab(index) if not webTab or not self._validIndex(index): return webView = webTab.webView() # This would close last tab, so we close the window instead if self.count() <= 1: # If we are not closing window upon closing last tab, let's just # load new-tab-url if self._dontCloseWithOneTab: # We don't want to accumulate more than one closed tab, if user # tries to close the last tab multiple times if webView.url() != self._urlOnNewTab: self._closedTabsManager.saveTab(webTab) webView.zoomReset() webView.load(self._urlOnNewTab) return self._window.close() return webView.triggerPageAction(QWebEnginePage.RequestClose) def reloadTab(self, index): if not self._validIndex(index): return self._weTab(index).reload() def reloadAllTabs(self): for idx in range(self.count()): self.reloadTab(idx) def stopTab(self, index): if not self._validIndex(index): return self._weTab(index).stop() def closeAllButCurrent(self, index): if not self._validIndex(index): return akt = self._weTab(index) tabs = self.allTabs(False) for tab in tabs: tabIndex = tab.tabIndex() if akt == self.widget(tabIndex): continue self.requestCloseTab(tabIndex) def closeToRight(self, index): if not self._validIndex(index): return tabs = self.allTabs(False) for tab in tabs: tabIndex = tab.tabIndex() if index >= tabIndex: continue self.requestCloseTab(tabIndex) def closeToLeft(self, index): if not self._validIndex(index): return tabs = self.allTabs(False) for tab in tabs: tabIndex = tab.tabIndex() if index <= tabIndex: continue self.requestCloseTab(tabIndex) # NOTICE: detachTab renamed to detachTabByIndex def detachTabByIndex(self, index): tab = self._weTab(index) assert (tab) if self.count() == 1 and gVar.app.windowCount() == 1: return self.detachTab(tab) # BrowserWindow* window window = gVar.app.createWindow(const.BW_NewWindow) window.setStartTab(tab) def loadTab(self, index): if not self._validIndex(index): return self._weTab(index).tabActivated() def unloadTab(self, index): if not self._validIndex(index): return self._weTab(index).unload() def restoreClosedTab(self, obj=0): ''' @param: obj QObject ''' if not obj: obj = self.sender() if not self._closedTabsManager.isClosedTabAvailable(): return tab = ClosedTabsManager.Tab() action = obj if isinstance(action, QAction) and action.data(): tab = self._closedTabsManager.takeTabAt(action.data()) else: tab = self._closedTabsManager.takeLastClosedTab() if tab.position < 0: return index = self.addViewByUrlTitle(QUrl(), tab.tabState.title, const.NT_CleanSelectedTab, False, tab.position) webTab = self._weTab(index) webTab.setParentTab(tab.parentTab) webTab.p_restoreTab(tab.tabState) self._updateClosedTabsButton() def restoreAllClosedTabs(self): if not self._closedTabsManager.isClosedTabAvailable(): return closedTabs = self._closedTabsManager.closedTabs() for tab in closedTabs: index = self.addViewByUrl(QUrl(), tab.tabState.title, const.NT_CleanSelectedTab) webTab = self._weTab(index) webTab.setParentTab(tab.parentTab) webTab.p_restoreTab(tab.tabState) self.clearClosedTabsList() def clearClosedTabsList(self): self._closedTabsManager.clearClosedTabs() self._updateClosedTabsButton() def moveAddTabButton(self, posX): posY = (self._tabBar.height() - self._buttonAddTab.height()) / 2 if QApplication.layoutDirection() == Qt.RightToLeft: posX = max(posX - self._buttonAddTab.width(), 0) else: posX = min(posX, self._tabBar.width() - self._buttonAddTab.width()) self._buttonAddTab.move(posX, posY) def tabBarOverFlowChanged(self, overflowed): # Show buttons inside tabbar self._buttonAddTab.setVisible(not overflowed) # Show buttons displayed outside tabbar (corner widgets) self._buttonAddTab2.setVisible(overflowed) self._buttonListTabs.setVisible(overflowed) # Q_SIGNALS: changed = pyqtSignal() tabInserted = pyqtSignal(int) # index tabRemoved = pyqtSignal(int) # index tabMoved = pyqtSignal(int, int) # from ,to # private Q_SLOTS: def _loadSettings(self): settings = Settings() settings.beginGroup('Browser-Tabs-Settings') self._dontCloseWithOneTab = settings.value('dontCloseWithOneTab', False, type=bool) self._showClosedTabsButton = settings.value('showClosedTabsButton', False, type=bool) self._newTabAfterActive = settings.value('newTabAfterActive', True, type=bool) self._newEmptyTabAfterActive = settings.value('newEmptyTabAfterActive', False, type=bool) settings.endGroup() settings.beginGroup('Web-URL-Settings') self._urlOnNewTab = settings.value('newTabUrl', 'app:speeddial', type=QUrl) settings.endGroup() self._tabBar.loadSettings() self._updateClosedTabsButton() def _aboutToShowTabsMenu(self): self._menuTabs.clear() for idx in range(self.count()): tab = self._weTab(idx) if not tab or tab.isPinned(): continue action = QAction(self) action.setIcon(tab.icon()) if idx == self.currentIndex(): f = action.font() f.setBold(True) action.setFont(f) title = tab.title() title.replace('&', '&&') action.setText(gVar.appTools.truncatedText(title, 40)) # TODO: QVariant::fromValue(qobject_cast<QWidget*>(tab) action.setData(tab) action.triggered.connect(self._actionChangeIndex) self._menuTabs.addAction(action) def _aboutToShowClosedTabsMenu(self): self._menuClosedTabs.clear() closedTabs = self.closedTabsManager().closedTabs() for idx, tab in enumerate(closedTabs): title = gVar.appTools.truncatedText(tab.tabState.title, 40) self._menuClosedTabs.addAction(tab.tabState.icon, title, self.restoreClosedTab).setData(idx) if self._menuClosedTabs.isEmpty(): self._menuClosedTabs.addAction('Empty').setEnabled(False) else: self._menuClosedTabs.addSeparator() self._menuClosedTabs.addAction('Restore All Closed Tabs', self.restoreAllClosedTabs) self._menuClosedTabs.addAction(QIcon.fromTheme('edit-clear'), 'Clear list', self.clearClosedTabsList) def _actionChangeIndex(self): # TODO: QAction* action = qobject_cast<QAction*>(sender()) action = self.sender() if action: # TODO: qobject_cast<WebTab*>(qvariant_cast<QWidget*>(action.data())) tab = action.data() if tab: self._tabBar.ensureVisible(tab.tabIndex()) self.setCurrentIndex(tab.tabIndex()) def _tabWasMoved(self, before, after): ''' @param: before int @param: after int ''' self._lastBackgroundTab = None self.changed.emit() if not self._blockTabMovedSignal: self.tabMoved.emit(before, after) # private: def _weTab(self, index=None): ''' @return: WebTab ''' if index is None: index = self.currentIndex() result = self.widget(index) if isinstance(result, WebTab): return result else: return None def _tabIcon(self, index): ''' @return: TabIcon ''' self._weTab(index).tabIcon() def _validIndex(self, index): return index >= 0 and index < self.count() def _updateClosedTabsButton(self): self._buttonClosedTabs.setVisible(self._showClosedTabsButton and self.canRestoreTab()) # override def keyPressEvent(self, event): ''' @param: event QKeyEvent ''' #if gVar.app.plugins().processKeyPress(const.ON_TabWidget, self, event): # return super(TabWidget, self).keyPressEvent(event) # override def keyReleaseEvent(self, event): ''' @param: event QKeyEvent ''' #if gVar.app.plugins().processKeyRelease(const.ON_TabWidget, self, event): # return super(TabWidget, self).keyReleaseEvent(event)
class TabStackedWidget(QWidget): ''' @note: just some of QTabWidget's methods were implemented ''' def __init__(self, parent=None): super(TabStackedWidget, self).__init__(parent) self._stack = None # QStackedWidget self._tabBar = None # ComboTabBar self._mainLayout = None # QVBoxLayout self._dirtyTabBar = False self._currentIndex = 0 self._previousIndex = 0 self._stack = QStackedWidget(self) self._mainLayout = QVBoxLayout() self._mainLayout.setSpacing(0) self._mainLayout.setContentsMargins(0, 0, 0, 0) self._mainLayout.addWidget(self._stack) self.setLayout(self._mainLayout) self._stack.widgetRemoved.connect(self._tabWasRemoved) def tabBar(self): ''' @return: ComboTabBar ''' return self._tabBar def setTabBar(self, tb): ''' @param: ComboTabBar ''' assert (tb) if tb.parentWidget() != self: tb.setParent(self) tb.show() del self._tabBar self._dirtyTabBar = True self._tabBar = tb self.setFocusProxy(self._tabBar) self._tabBar.currentChanged.connect(self._showTab) self._tabBar.tabMoved.connect( lambda from_, to_: TabStackedWidget._tabWasMoved(self, from_, to_)) self._tabBar.overFlowChanged.connect(self.setUpLayout) if self._tabBar.tabsCloseable(): self._tabBar.tabCloseRequested.connect(self.tabCloseRequested) self.setDocumentMode(self._tabBar.documentMode()) self._tabBar.installEventFilter(self) self.setUpLayout() def documentMode(self): return self._tabBar.documentMode() def setDocumentMode(self, enabled): self._tabBar.setDocumentModel(enabled) self._tabBar.setExpanding(not enabled) self._tabBar.setDrawBase(enabled) def addTab(self, widget, label, pinned=False): ''' @return: int ''' return self.insertTab(-1, widget, label, pinned) def insertTab(self, index, widget, label, pinned=False): ''' @param: widget QWidget @param: label QString ''' if not widget: return -1 if pinned: if index < 0: index = self._tabBar.pinnedTabsCount() else: index = min(index, self._tabBar.pinnedTabsCount()) index = self._stack.insertWidget(index, widget) self._tabBar.insertTabByIconText(index, QIcon(), label, True) else: if index < 0: index = self._tabBar.pinnedTabsCount() else: index = max(index, self._tabBar.pinnedTabsCount()) index = self._stack.insertWidget(index, widget) self._tabBar.insertTabByIconText(index, QIcon(), label, False) if self._previousIndex >= index: self._previousIndex += 1 if self._currentIndex >= index: self._currentIndex += 1 QTimer.singleShot(0, self.setUpLayout) return index def tabText(self, index): return self._tabBar.tabText(index) def setTabText(self, index, label): self._tabBar.setTabText(index, label) def tabToolTip(self, index): return self._tabBar.tabToolTip(index) def setTabToolTip(self, index, tip): self._tabBar.setTabToolTip(index, tip) def pinUnPinTab(self, index, title=''): widget = self._stack.widget(index) currentWidget = self._stack.currentWidget() if not widget or not currentWidget: return -1 makePinned = index >= self._tabBar.pinnedTabsCount() button = self._tabBar.tabButton(index, self._tabBar.iconButtonPosition()) # To show tooltip of tab which is pinned in the current session toolTip = self.tabToolTip(index) self._tabBar._blockCurrentChangedSignal = True self._tabBar.setTabButton(index, self._tabBar.iconButtonPosition(), None) self._stack.removeWidget(widget) if makePinned: insertIndex = 0 else: insertIndex = self._tabBar.pinnedTabsCount() newIndex = self.insertTab(insertIndex, widget, title, makePinned) self._tabBar.setTabButton(newIndex, self._tabBar.iconButtonPosition(), button) self._tabBar._blockCurrentChangedSignal = False self.setTabToolTip(newIndex, toolTip) # Restore current widget self.setCurrentWidget(currentWidget) self.pinStateChanged.emit(newIndex, makePinned) return newIndex def removeTab(self, index): widget = self._stack.widget(index) if widget: # Select another current tab before remove, so it won't be handled # by QTabBar if index == self.currentIndex() and self.count() > 1: self._selectTabOnRemove() self._stack.removeWidget(widget) def moveTab(self, from_, to_): self._tabBar.moveTab(from_, to_) def currentIndex(self): ''' @return: int ''' return self._tabBar.currentIndex() def currentWidget(self): ''' @return: QWidget ''' return self._stack.currentWidget() def widget(self, index): ''' @return: QWidget ''' return self._stack.widget(index) def indexOf(self, widget): ''' @param: widget QWidget @return: int ''' return self._stack.indexOf(widget) def count(self): return self._tabBar.count() # Q_SIGNALS currentChanged = pyqtSignal(int) # index tabCloseRequested = pyqtSignal(int) # index pinStateChanged = pyqtSignal(int, bool) # index, pinned # public Q_SLOTS def setCurrentIndex(self, index): self._tabBar.setCurrentIndex(index) def setCurrentWidget(self, widget): ''' @widget: widget QWidget ''' self._tabBar.setCurrentIndex(self.indexOf(widget)) def setUpLayout(self): if not self._tabBar.isVisible(): self._dirtyTabBar = True return self._tabBar.setElideMode(self._tabBar.elideMode()) self._dirtyTabBar = False # private Q_SLOTS def _showTab(self, index): if self._validIndex(index): self._stack.setCurrentIndex(index) self._previousIndex = self._currentIndex self._currentIndex = index # This is slot connected to ComboTabBar::currentChanged # We must send the signal event with invalid index (-1) self.currentChanged.emit(index) def _tabWasMoved(self, from_, to_): self._stack.blockSignals(True) widget = self._stack.widget(from_) self._stack.removeWidget(widget) self._stack.insertWidget(to_, widget) self._stack.blockSignals(False) def _tabWasRemoved(self, index): if self._previousIndex == index: self._previousIndex = -1 elif self._previousIndex > index: self._previousIndex -= 1 if self._currentIndex == index: self._currentIndex = -1 elif self._currentIndex > index: self._currentIndex -= 1 self._tabBar.removeTab(index) # protected: # override def eventFilter(self, obj, event): ''' @param: obj QObject @param: event QEvent @return: bool ''' if self._dirtyTabBar and obj == self._tabBar and event.type( ) == QEvent.Show: self.setUpLayout() return False # override def keyPressEvent(self, event): ''' @param: event QKeyEvent ''' evkey = event.key() evmodifiers = event.modifiers() if evkey == Qt.Key_Tab or evkey == Qt.Key_Backtab and \ self.count() > 1 and evmodifiers & Qt.ControlModifier: pageCount = self.count() page = self.currentIndex() if (evkey == Qt.Key_Backtab) or (evmodifiers & Qt.ShiftModifier): dx = -1 else: dx = 1 for idx in range(pageCount): page += dx if page < 0: page = self.count() - 1 elif page >= pageCount: page = 0 if self._tabBar.isTabEnabled(page): self.setCurrentIndex(page) break if not QApplication.focusWidget(): self._tabBar.setFocus() else: event.ignore() # private: def _validIndex(self, index): return index < self._stack.count() and index >= 0 def _selectTabOnRemove(self): assert (self.count() > 1) index = -1 behavior = self._tabBar.selectionBehaviorOnRemove() if behavior == QTabBar.SelectPreviousTab: if self._validIndex(self._previousIndex): index = self._previousIndex # fallthrough elif behavior == QTabBar.SelectLeftTab: index = self.currentIndex() - 1 if not self._validIndex(index): index = 1 elif behavior == QTabBar.SelectRightTab: index = self.currentIndex() + 1 if not self._validIndex(index): index = self.currentIndex() - 1 assert (self._validIndex(index)) self.setCurrentIndex(index)
class ComprehensionTestApp(QMainWindow): """ @modifies self.symbol_test, self.question1, self.question2 @effects makes a new ComprehensionTestApp """ def __init__(self): super().__init__() self.symbol_tests = QStackedWidget() self.question1 = "Exactly what do you think this symbol means?" self.question2 = "What action would you take in response to this symbol?" self.init_gui() """ @modifies self @effects sets up the main window """ def init_gui(self): exitAct = QAction('&Exit', self) exitAct.setShortcut('Ctrl+Q') exitAct.setStatusTip('Exit application') exitAct.triggered.connect(self.quit) saveAct = QAction('&Save as PDF', self) saveAct.setShortcut('Ctrl+S') saveAct.setStatusTip('Saves as PDF') saveAct.triggered.connect(self.save_as_pdf) importAct = QAction('&Import symbols', self) importAct.setShortcut('Ctrl+I') importAct.setStatusTip( 'Imports all the specified images on to their own test page') importAct.triggered.connect(self.import_symbols) menu_bar = self.menuBar() filemenu = menu_bar.addMenu('&File') filemenu.addAction(importAct) filemenu.addAction(saveAct) filemenu.addAction(exitAct) goToAct = QAction('&Go to page', self) goToAct.setStatusTip('Go to the specified page') goToAct.setShortcut('Ctrl+G') goToAct.triggered.connect(self.go_to_page) newQuestAct = QAction('&Change default questions', self) newQuestAct.setStatusTip( 'Changes the default questions asked about the symbol') newQuestAct.setShortcut('Ctrl+H') newQuestAct.triggered.connect(self.change_questions) moveLeftAct = QAction('&Move current page left', self) moveLeftAct.setShortcut('Ctrl+,') moveLeftAct.setStatusTip('Moves the current page one page to the left') moveLeftAct.triggered.connect(self.move_test_left) moveRightAct = QAction('&Move current page right', self) moveRightAct.setShortcut('Ctrl+.') moveRightAct.setStatusTip( 'Moves the current page one page to the right') moveRightAct.triggered.connect(self.move_test_right) moveToAct = QAction('Move current page to', self) moveToAct.setShortcut('Ctrl+M') moveToAct.setStatusTip('Moves the current page to the specified page') moveToAct.triggered.connect(self.move_to_page) delPagesAct = QAction('Delete pages', self) delPagesAct.setShortcut('Ctrl+D') delPagesAct.setStatusTip('Deletes the specified pages') delPagesAct.triggered.connect(self.delete_pages) editmenu = menu_bar.addMenu('&Edit') editmenu.addAction(delPagesAct) editmenu.addAction(newQuestAct) editmenu.addAction(goToAct) editmenu.addAction(moveLeftAct) editmenu.addAction(moveRightAct) editmenu.addAction(moveToAct) nextAct = QAction(QIcon("next.png"), 'Next page (Ctrl+P)', self) nextAct.setShortcut('Ctrl+P') nextAct.setStatusTip('Goes to the next page') nextAct.triggered.connect(self.next_page) prevAct = QAction(QIcon("prev.png"), "Previous page (Ctrl+O)", self) prevAct.setShortcut('Ctrl+O') prevAct.setStatusTip('Goes to the previous page') prevAct.triggered.connect(self.prev_page) newAct = QAction(QIcon("add.png"), "Add a new page (Ctrl+N)", self) newAct.setShortcut('Ctrl+N') newAct.setStatusTip('Creates a new page') newAct.triggered.connect(self.add_blank_test) remAct = QAction(QIcon("remove.png"), "Delete page (Ctrl+R)", self) remAct.setShortcut('Ctrl+R') remAct.setStatusTip('Deletes the current page') remAct.triggered.connect(self.delete_test) tool_bar = self.addToolBar("Next Page") tool_bar.addAction(prevAct) tool_bar.addAction(nextAct) tool_bar.addAction(newAct) tool_bar.addAction(remAct) self.setWindowTitle("Open Comprehension Test Generator") self.setWindowIcon(QIcon("rubber ducky.png")) self.setGeometry(500, 500, 500, 450) self.add_blank_test() self.setCentralWidget(self.symbol_tests) self.show() """ @modifies none @effects confirms if the user wishes to exit """ def closeEvent(self, event): msg = QMessageBox.question(self, 'Message', "Really quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if msg == QMessageBox.Yes: event.accept() else: event.ignore() """ @modifies none @effects confirms if the user wishes to exit """ def quit(self): msg = QMessageBox.question(self, 'Message', "Really quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if msg == QMessageBox.Yes: qApp.quit() """ @modifies self.symbol_tests @effects adds a blank symbol_test to the end of symbol_tests sets the current symbol_test to the new one """ def add_blank_test(self): page = self.symbol_tests.count() + 1 test = SymbolTestWidget( self, comp_questions=[self.question1, self.question2], pageNumber=page, pageTotal=page) test.set_visual_context("blank image.png") self.symbol_tests.addWidget(test) self.symbol_tests.setCurrentIndex(page - 1) for i in range(0, self.symbol_tests.count() - 1): self.symbol_tests.widget(i).set_numPages(self.symbol_tests.count()) """ @modifies self.symbol_tests @effects changes the current symbol_test to the next one """ def next_page(self): if self.symbol_tests.currentIndex() < self.symbol_tests.count() - 1: self.symbol_tests.setCurrentIndex( self.symbol_tests.currentIndex() + 1) """ @modifies self.symbol_test @effects changes the current symbol_test to the previous one """ def prev_page(self): if self.symbol_tests.currentIndex() > 0: self.symbol_tests.setCurrentIndex( self.symbol_tests.currentIndex() - 1) """ @modifies none @effects saves all the symbol_tests as seperate pages on a PDF document """ def save_as_pdf(self): filename = QFileDialog.getSaveFileName(self, 'Save to PDF', 'c:\\', "*.pdf") if filename != ('', ''): if os.path.exists(filename[0]): try: infile = PdfFileReader(filename[0], 'rb') except: error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) error.setText( "File could not be written to. If the file is currently open, try closing it" ) error.exec_() return if infile.getNumPages() == 0: print("HERE!") doc = QTextDocument() doc.print(printer) printer = QPrinter() printer.setPageSize(QPrinter.A4) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(filename[0]) painter = QPainter() font = QFont("times") font.setPointSize(12) x = painter.begin(printer) if x == False: error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) error.setText( "File could not be saved. If the file is currently open, try closing it" ) error.exec_() return painter.setFont(font) for i in range(0, self.symbol_tests.count()): cur_symbol_test = self.symbol_tests.widget(i) if cur_symbol_test.print_visual_context() == False: pixmap = cur_symbol_test.get_symbol() pixmap = pixmap.scaled(350, 350) painter.drawPixmap(30, 100, pixmap) painter.drawText(750, 20, cur_symbol_test.get_page()) painter.drawText( 420, 200, 350, 400, QtCore.Qt.TextWordWrap, "Context: " + cur_symbol_test.get_context()) painter.drawText(30, 600, cur_symbol_test.get_question1()) painter.drawText(30, 830, cur_symbol_test.get_question2()) cur_pen = painter.pen() line_pen = QPen() line_pen.setWidth(2) painter.setPen(line_pen) painter.drawLine(70, 656, 600, 656) painter.drawLine(70, 712, 600, 712) painter.drawLine(70, 768, 600, 768) painter.drawLine(70, 886, 600, 886) painter.drawLine(70, 942, 600, 942) painter.drawLine(70, 998, 600, 998) painter.setPen(cur_pen) else: pixmap = cur_symbol_test.get_visual_context() pixmap = pixmap.scaled(300, 300) painter.drawPixmap(200, 10, pixmap) pixmap = cur_symbol_test.get_symbol() pixmap = pixmap.scaled(250, 250) painter.drawPixmap(225, 320, pixmap) painter.drawText(750, 20, cur_symbol_test.get_page()) #painter.drawText(420, 200, 350, 400, QtCore.Qt.TextWordWrap, "Context: " + cur_symbol_test.get_context()) painter.drawText(30, 600, cur_symbol_test.get_question1()) painter.drawText(30, 830, cur_symbol_test.get_question2()) cur_pen = painter.pen() line_pen = QPen() line_pen.setWidth(2) painter.setPen(line_pen) painter.drawLine(70, 656, 600, 656) painter.drawLine(70, 712, 600, 712) painter.drawLine(70, 768, 600, 768) painter.drawLine(70, 886, 600, 886) painter.drawLine(70, 942, 600, 942) painter.drawLine(70, 998, 600, 998) painter.setPen(cur_pen) if (i < self.symbol_tests.count() - 1): printer.newPage() painter.end() """ @modifies self.symbol_tests @effects adds all the specified symbols to their own symbol_test and adds those tests to self.symbol_tests """ def import_symbols(self): fnames = QFileDialog.getOpenFileNames(self, 'Open file', 'c:\\', "Image files (*.jpg *.png)") if fnames != ([], ''): for i in range(0, len(fnames[0])): page = self.symbol_tests.count() + 1 test = SymbolTestWidget( self, comp_questions=[self.question1, self.question2], pageNumber=page, pageTotal=page) test.set_symbol(fnames[0][i]) test.set_visual_context("blank image.png") self.symbol_tests.addWidget(test) self.symbol_tests.setCurrentIndex(page - 1) for i in range(0, self.symbol_tests.count()): self.symbol_tests.widget(i).set_numPages( self.symbol_tests.count()) """ @modifies self.symbol_tests @effects removes the current test from self.symbol_tests """ def delete_test(self): msg = QMessageBox.question(self, 'Message', "Delete test?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if msg == QMessageBox.No: return if self.symbol_tests.currentIndex() != 0: cur_idx = self.symbol_tests.currentIndex() self.symbol_tests.setCurrentIndex(cur_idx - 1) self.symbol_tests.removeWidget(self.symbol_tests.widget(cur_idx)) for i in range(0, self.symbol_tests.count()): cur_symbol_test = self.symbol_tests.widget(i) cur_symbol_test.set_numPages(self.symbol_tests.count()) if i >= cur_idx: cur_symbol_test.set_page(i + 1) elif self.symbol_tests.currentIndex( ) == 0 and self.symbol_tests.count() > 1: self.symbol_tests.setCurrentIndex(1) self.symbol_tests.removeWidget(self.symbol_tests.widget(0)) for i in range(0, self.symbol_tests.count()): cur_symbol_test = self.symbol_tests.widget(i) cur_symbol_test.set_numPages(self.symbol_tests.count()) cur_symbol_test.set_page(i + 1) elif self.symbol_tests.currentIndex() == 0 and self.symbol_tests.count( ) == 1: page = 1 test = SymbolTestWidget( self, comp_questions=[self.question1, self.question2], pageNumber=page, pageTotal=page) self.symbol_tests.addWidget(test) self.symbol_tests.setCurrentIndex(1) self.symbol_tests.removeWidget(self.symbol_tests.widget(0)) """ @modifies self.symbol_tests @effects decreases the current symbol_test's index in self.symbol_tests by 1, moves it down 1 postion """ def move_test_left(self): cur_idx = self.symbol_tests.currentIndex() if cur_idx > 0: cur_widget = self.symbol_tests.widget(cur_idx) prev_widget = self.symbol_tests.widget(cur_idx - 1) cur_widget.set_page(int(cur_widget.get_page()) - 1) prev_widget.set_page(int(prev_widget.get_page()) + 1) self.symbol_tests.removeWidget(prev_widget) self.symbol_tests.insertWidget( self.symbol_tests.indexOf(cur_widget) + 1, prev_widget) """ @modifies self.symbol_tests @effects increases the current symbol_test's index in self.symbol_tests by 1, moves it up 1 postion """ def move_test_right(self): cur_idx = self.symbol_tests.currentIndex() if cur_idx < self.symbol_tests.count() - 1: cur_widget = self.symbol_tests.widget(cur_idx) next_widget = self.symbol_tests.widget(cur_idx + 1) cur_widget.set_page(int(cur_widget.get_page()) + 1) next_widget.set_page(int(next_widget.get_page()) - 1) self.symbol_tests.removeWidget(next_widget) self.symbol_tests.insertWidget( self.symbol_tests.indexOf(cur_widget), next_widget) """ @modifies self.symbol_tests @effects moves the current symbol_test to a specified position in self.symbol_tests """ def move_to_page(self): text = QInputDialog.getText(self, "Move Page", "Move current page to: ", QLineEdit.Normal) if text[1] != False: try: x = int(text[0]) if x <= 0 or x > self.symbol_tests.count(): error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) error.setText("Page must be between 1 and " + str(self.symbol_tests.count())) error.exec_() return cur_idx = self.symbol_tests.currentIndex() cur_widget = self.symbol_tests.widget(cur_idx) cur_widget.set_page(x - 1) self.symbol_tests.removeWidget(cur_widget) self.symbol_tests.insertWidget(x - 1, cur_widget) self.symbol_tests.setCurrentIndex(x - 1) for i in range(0, self.symbol_tests.count()): loop_widget = self.symbol_tests.widget(i) loop_widget.set_page(i + 1) except ValueError: error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) error.setText("Can only enter an integer between 1 and " + str(self.symbol_tests.count())) error.exec_() """ @modifies self.symbol_tests @effects sets the surrent symbol_test equal to the one at the specified page """ def go_to_page(self): text = QInputDialog.getText(self, "Go to", "Go to page: ", QLineEdit.Normal) if text[1] != False: try: x = int(text[0]) if x <= 0 or x > self.symbol_tests.count(): error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) error.setText("Page must be between 1 and " + str(self.symbol_tests.count())) error.exec_() return self.symbol_tests.setCurrentIndex(x - 1) except ValueError: error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) error.setText("Can only enter an integer between 1 and " + str(self.symbol_tests.count())) error.exec_() """ @modifies self.question1, self.question2 @effects changes the default questions """ def change_questions(self): text = QInputDialog.getText(self, "Question 1 Input", "Question 1: ", QLineEdit.Normal, self.question1) if str(text[0]) != '': self.question1 = str(text[0]) text = QInputDialog.getText(self, "Question 2 Input", "Question 2: ", QLineEdit.Normal, self.question2) if str(text[0]) != '': self.question2 = str(text[0]) def delete_pages(self): text = QInputDialog.getText( self, "Delete pages", "Enter page numbers seperated by a comma:", QLineEdit.Normal) error = QMessageBox() error.setIcon(QMessageBox.Warning) error.setStandardButtons(QMessageBox.Ok) if text[1] != False: pages = text[0].split(",") del_widgets = [] for page in pages: try: x = int(page) except: error.setText("All page numbers must be integers") error.exec_() return if int(page) > self.symbol_tests.count() or int(page) < 1: error.setText("Page " + page + " does not exist") error.exec_() return del_widgets.append(self.symbol_tests.widget(int(page) - 1)) confirm = QMessageBox.question( self, 'Message', "Really delete pages: " + text[0] + "?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) for del_widget in del_widgets: del_idx = self.symbol_tests.indexOf(del_widget) if del_idx != self.symbol_tests.currentIndex(): self.symbol_tests.removeWidget(del_widget) else: #Removing the only test if self.symbol_tests.count() == 1: self.add_blank_test() self.symbol_tests.removeWidget(del_widget) self.symbol_tests.currentWidget().set_page(1) self.symbol_tests.currentWidget().set_numPages(1) return else: if del_idx == self.symbol_tests.count() - 1: self.symbol_tests.setCurrentIndex(del_idx - 1) self.symbol_tests.removeWidget(del_widget) else: self.symbol_tests.setCurrentIndex(del_idx + 1) self.symbol_tests.removeWidget(del_widget) count = 1 for i in range(0, self.symbol_tests.count()): cur_widget = self.symbol_tests.widget(i) cur_widget.set_page(count) cur_widget.set_numPages(self.symbol_tests.count()) count += 1