def createPopupMenu(self) -> QtWidgets.QMenu: menu = super().createPopupMenu() menu.clear() toolbars: typing.Sequence[QtWidgets.QToolBar] = gg(self.findChildren(QtWidgets.QToolBar)) for clazz in self._toolbarClasses: assert issubclass(clazz, ToolbarMixin) toolbar = next((x for x in toolbars if isinstance(x, clazz)), None) action = menu.addAction(clazz.getToolbarTitle()) action.setCheckable(True) action.setChecked(toolbar is not None) action.triggered.connect(lambda clazz=clazz, toolbar=toolbar: self.addToolBar( clazz(self)) if toolbar is None else self.removeToolBar(toolbar)) menu.addSeparator() for toolbar in toolbars: if toolbar.rect().contains(toolbar.mapFromGlobal(QtGui.QCursor.pos())): title = tt.Toolbar_Lock % gg(toolbar).getToolbarTitle() action = QtWidgets.QAction(title, menu) action.setCheckable(True) action.setChecked(not toolbar.isMovable()) action.triggered.connect(lambda *args, toolbar=toolbar: toolbar.setMovable(not toolbar.isMovable())) menu.addAction(action) menu.addSeparator() lockAllAction = QtWidgets.QAction(tt.Toolbar_LockAll, menu) lockAllAction.setDisabled(all(not x.isMovable() for x in toolbars)) lockAllAction.triggered.connect(lambda: list(x.setMovable(False) for x in toolbars)) unlockAllAction = QtWidgets.QAction(tt.Toolbar_UnlockAll, menu) unlockAllAction.setDisabled(all(x.isMovable() for x in toolbars)) unlockAllAction.triggered.connect(lambda: list(x.setMovable(True) for x in toolbars)) menu.addAction(lockAllAction) menu.addAction(unlockAllAction) return menu
def __init__(self, parent: typing.Optional[QtWidgets.QWidget] = None) -> None: super().__init__(parent) self.setSelectionBehavior( QtWidgets.QTableView.SelectionBehavior.SelectRows) self.setEditTriggers( gg(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers, QtWidgets.QAbstractItemView.EditTriggers)) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setDefaultAlignment( gg(QtCore.Qt.AlignmentFlag.AlignLeft) | QtCore.Qt.AlignmentFlag.AlignVCenter) self.setAlternatingRowColors(True) self.setStyleSheet("alternate-background-color: rgb(245, 245, 245)") self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) self.setShowGrid(False) self.setItemDelegate(self.NoFocusDelegate()) self.horizontalHeader().setStyleSheet( "QHeaderView::section { border-top:0px solid #D8D8D8; border-bottom: 1px solid #D8D8D8; " "background-color:white; padding:2px; font-weight: light; }") self.horizontalHeader().setHighlightSections(False) tablePalette = self.palette() tablePalette.setColor( QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Highlight, tablePalette.color(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Highlight)) tablePalette.setColor( QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.HighlightedText, tablePalette.color(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.HighlightedText)) self.setPalette(tablePalette)
def __init__(self, target: ControlsWidget): super().__init__() self._target = target self._widgetConfig = target.getWidgetConfig() self._logger = logging.getLogger("controlsWidgetConfig") sizes = [16, 24, 32, 40, 48, 64, 96, 128] layout = QtWidgets.QGridLayout() self._iconSizeLabel = QtWidgets.QLabel(tt.ControlsWidget_IconSize) self._iconSizeCombo = QtWidgets.QComboBox() self._iconSizeCombo.addItems([str(x) for x in sizes]) self._iconSizeCombo.setCurrentIndex( sizes.index(self._widgetConfig.iconSize)) self._buttonBox = QtWidgets.QDialogButtonBox( gg(QtWidgets.QDialogButtonBox.Ok) | gg(QtWidgets.QDialogButtonBox.Cancel) | gg(QtWidgets.QDialogButtonBox.Apply)) layout.setColumnStretch(0, 1) layout.setColumnStretch(1, 2) layout.addWidget(self._iconSizeLabel) layout.addWidget(self._iconSizeCombo) layout.addWidget(WidgetUtils.createExpandingSpacer(), layout.rowCount(), 0, 1, 2) layout.addWidget(self._buttonBox, layout.rowCount(), 0, 1, 2) self.setLayout(layout) self._buttonBox.clicked.connect(self._onButtonBoxClicked)
def _loadConfig(self): screenSize = QtGui.QGuiApplication.primaryScreen().size() windowSize = gg(screenSize) / 1.5 diffSize = (screenSize - windowSize) / 2 defaultGeometry = QtCore.QRect(QtCore.QPoint(diffSize.width(), diffSize.height()), windowSize) self.setGeometry(defaultGeometry) self._loadToolbars(self._config.toolbars) self.restoreGeometry(QtCore.QByteArray(gg(self._config.geometry))) self.restoreState(QtCore.QByteArray(gg(self._config.state)))
def _onLayoutEditingChanged(self, editing: bool) -> None: if editing: self._logger.info("Create mask widget") self._maskWidget = MaskWidget(self) self._maskWidget.show() else: self._maskWidget.setParent(gg(None)) self._maskWidget = None self._logger.info("Refresh splitter handles") for widget in self.findChildren(SplitterWidget): gg(widget, SplitterWidget).refreshHandles()
def _setupLyrics(self): self._logger.info(">> Setting up lyrics...") if self._player.getCurrentMusic().isAbsent(): self._logger.info("No music, return") return currentMusic = self._player.getCurrentMusic().orElseThrow(AssertionError) lyricsPath = Path(currentMusic.filename).with_suffix(".lrc") lyricsText = lyricsPath.read_text(encoding="gbk") self._logger.info("Parsing lyrics") lyrics = LyricUtils.parseLyrics(lyricsText) self._logger.info("Lyrics count: %d", len(lyrics)) self._lyrics = lyrics self._prevLyricIndex = -1 LayoutUtils.clearLayout(self._layout) self._layout.addSpacing(self.height() // 2) for position, lyric in list(lyrics.items())[:]: lyricLabel = ClickableLabel(lyric, self) lyricLabel.setAlignment( gg(QtCore.Qt.AlignmentFlag.AlignCenter, Any) | QtCore.Qt.AlignmentFlag.AlignVCenter) lyricLabel.clicked.connect( lambda _, position=position: self._player.setPosition(position)) lyricLabel.setMargin(int(2 * self._app.getZoom())) self._layout.addWidget(lyricLabel) self._layout.addSpacing(self.height() // 2) self._logger.info("Lyrics layout has children: %d", self._layout.count()) self.verticalScrollBar().setValue(0) self._logger.info("<< Lyrics set up")
def paint(self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex) -> None: itemOption = QtWidgets.QStyleOptionViewItem(option) if gg(option, typing.Any).state & QtWidgets.QStyle.State_HasFocus: itemOption.state = itemOption.state ^ QtWidgets.QStyle.State_HasFocus super().paint(painter, itemOption, index)
def loadLayout(self, layout: Element) -> None: self._logger.info("Load layout") if self.centralWidget() is not None: self._logger.info("Central widget is not none, clear it") self.centralWidget().setParent(gg(None)) assert self.centralWidget() is None self._drawElement(layout, self)
def _selectRows(self, indexes: typing.Iterable[int]) -> None: for index in indexes: self.selectionModel().select( QtCore.QItemSelection(self.model().index(index, 0), self.model().index(index, 2)), gg(QtCore.QItemSelectionModel.Select, typing.Any) | QtCore.QItemSelectionModel.Rows)
def _setLyricIndex(self, lyricIndex): self._logger.debug("Set lyric index to %d", lyricIndex) for index in range(len(self._lyrics)): lyricLabel: QtWidgets.QLabel = gg(self._layout.itemAt(index + 1).widget()) color = QtGui.QColor("#e1413c" if index == lyricIndex else "#23557d") palette = lyricLabel.palette() palette.setColor(QtGui.QPalette.ColorRole.WindowText, color) lyricLabel.setPalette(palette) originalValue = self.verticalScrollBar().value() targetValue = lyricLabel.pos().y() - self.height() // 2 + lyricLabel.height() // 2 index == lyricIndex and (lambda animation=QtCore.QPropertyAnimation( self.verticalScrollBar(), gg(b"value"), self): [ animation.setStartValue(originalValue), animation.setEndValue(targetValue), animation.start(QtCore.QPropertyAnimation.DeletionPolicy.DeleteWhenStopped) ])() self._logger.debug("Lyrics refreshed")
def __init__(self): super().__init__() self._app = App.instance() self.setLayout(QtWidgets.QGridLayout(self)) self._label = QtWidgets.QLabel() self._label.setAlignment(gg(QtCore.Qt.AlignmentFlag.AlignCenter)) self.layout().addWidget(self._label) self._refreshView() self._app.languageChanged.connect(self._refreshView)
def createButtonBox(ok=False, cancel=False, apply=False): flag = 0 if ok: flag |= QtWidgets.QDialogButtonBox.StandardButton.Ok if cancel: flag |= QtWidgets.QDialogButtonBox.StandardButton.Cancel if apply: flag |= QtWidgets.QDialogButtonBox.StandardButton.Apply return QtWidgets.QDialogButtonBox(gg(flag))
def __init__(self, target: DemoWidget) -> None: super().__init__() self._target = target self._logger = logging.getLogger("demoWidgetConfigWidget") self._widgetConfig = target.getWidgetConfig() increaseButton = QtWidgets.QPushButton("Increase") decreaseButton = QtWidgets.QPushButton("Decrease") self._widgetCounterLabel = QtWidgets.QLabel() layout = QtWidgets.QVBoxLayout() layout.setSpacing(15) layout.addStretch() layout.addWidget(decreaseButton, 0, gg(QtCore.Qt.AlignCenter)) layout.addWidget(self._widgetCounterLabel, 0, gg(QtCore.Qt.AlignCenter)) layout.addWidget(increaseButton, 0, gg(QtCore.Qt.AlignCenter)) layout.addStretch() self.setLayout(layout) increaseButton.clicked.connect(self._onIncreaseButtonClicked) decreaseButton.clicked.connect(self._onDecreaseButtonClicked) target.widgetConfigChanged.connect(self._onWidgetConfigChanged) self._refreshWidget()
def __init__(self, config=None): super().__init__() self._logger = logging.getLogger("demoWidget") self._app = App.instance() self._pluginConfig = DemoPlugin.getPluginConfig() self._widgetConfig = config or self.getWidgetConfigClass().getDefaultObject() self._pluginCounterLabel = QtWidgets.QLabel() self._widgetCounterLabel = QtWidgets.QLabel() layout = QtWidgets.QVBoxLayout() layout.setSpacing(15) layout.addStretch() layout.addWidget(self._pluginCounterLabel, 0, gg(QtCore.Qt.AlignCenter)) layout.addWidget(self._widgetCounterLabel, 0, gg(QtCore.Qt.AlignCenter)) layout.addStretch() self.setLayout(layout) DemoPlugin.pluginConfigChanged.connect(self._onPluginConfigChanged) self.widgetConfigChanged.connect(self._onWidgetConfigChanged) self._app.languageChanged.connect(self.onLanguageChanged) self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self._onCustomContextMenuRequested) self._refreshWidget()
def __init__(self) -> None: super().__init__() self._app = App.instance() self._logger = logging.getLogger("demoPluginConfigWidget") self._pluginConfig = DemoPlugin.getPluginConfig() decreaseButton = QtWidgets.QPushButton("Decrease") increaseButton = QtWidgets.QPushButton("Increase") self._pluginCounterLabel = QtWidgets.QLabel() layout = QtWidgets.QVBoxLayout() layout.setSpacing(15) layout.addStretch() layout.addWidget(decreaseButton, 0, gg(QtCore.Qt.AlignCenter)) layout.addWidget(self._pluginCounterLabel, 0, gg(QtCore.Qt.AlignCenter)) layout.addWidget(increaseButton, 0, gg(QtCore.Qt.AlignCenter)) layout.addStretch() self.setLayout(layout) increaseButton.clicked.connect(self._onIncreaseButtonClicked) decreaseButton.clicked.connect(self._onDecreaseButtonClicked) DemoPlugin.pluginConfigChanged.connect(self._onPluginConfigChanged) self._app.languageChanged.connect(self._onLanguageChanged) self._refreshWidget()
def _doReplace(self, widget: ReplacerMixin): assert isinstance(widget, QtWidgets.QWidget) logger = logging.getLogger("maskWidget") parent = self._replaceable.parentWidget() logger.info("Replacing %s with %s", self._replaceable, widget) assert isinstance(widget, (QtWidgets.QWidget, ReplacerMixin)) if isinstance(parent, QtWidgets.QMainWindow): logger.info("Parent is main window") parent.setCentralWidget(widget) elif isinstance(parent, QtWidgets.QSplitter): logger.info("Parent is splitter") parent.replaceWidget(parent.indexOf(self._replaceable), widget) else: logger.info("Parent is others") parent.layout().replaceWidget(self._replaceable, widget) self._replaceable.setParent(gg(None)) self.raise_() self._app.getMainWindow().layoutChanged.emit()
def _loadElementConfig(self, element: Element): from IceSpringMusicPlayer.common.pluginWidgetMixin import PluginWidgetMixin if issubclass(element.clazz, PluginWidgetMixin): self._logger.info("Load element config: %s", element) elementConfigJd = json.loads(json.dumps( element.config, default=element.clazz.getWidgetConfigClass().pythonToJson), object_pairs_hook=element.clazz. getWidgetConfigClass().jsonToPython) elementConfig = gg( element.clazz.getWidgetConfigClass( ))(** { **element.clazz.getWidgetConfigClass().getDefaultObject( ).__dict__, **elementConfigJd }) self._logger.info("Loaded element config: %s", elementConfig) element.config = elementConfig for child in element.children: self._loadElementConfig(child)
def _drawElement(self, element: Element, parent: QtWidgets.QWidget) -> None: self._logger.info("Draw element: %s to %s", element.clazz, parent) if issubclass(element.clazz, PluginWidgetMixin) and "config" in inspect.signature( element.clazz.__init__).parameters.keys(): widget = gg(element.clazz)(element.config) else: widget = element.clazz() if isinstance(widget, QtWidgets.QSplitter) and element.vertical: widget.setOrientation(QtCore.Qt.Orientation.Vertical) if isinstance(parent, QtWidgets.QMainWindow): self._logger.info("Parent is main window") parent.setCentralWidget(widget) elif isinstance(parent, QtWidgets.QSplitter): self._logger.info("Parent is splitter") parent.addWidget(widget) parent.setProperty("iceSizes", (parent.property("iceSizes") or ()) + (element.weight * 2 ** 16,)) parent.setSizes(parent.property("iceSizes")) else: raise AssertionError("Unsupported parent: %s" % type(parent)) for child in element.children: self._drawElement(child, widget)
def _loadConfig(self) -> Config: self._logger.info("Load config") path = Path("config.json") if not path.exists(): self._logger.info("No config.json file, return default config") return self.getDefaultConfig() jd = json.loads(path.read_text(), object_pairs_hook=Config.fromJson) config = Config( **{ **self.getDefaultConfig().__dict__, **{k: v for k, v in jd.items() if k in Config.__annotations__} }) self._logger.info("Process plugin configs (%d plugins)", len(self._pluginService.getPluginClasses())) for plugin in config.plugins: self._logger.info("Plugin in config: %s", plugin) jd = json.loads(json.dumps( plugin.config, default=plugin.clazz.getPluginConfigClass().pythonToJson), object_pairs_hook=plugin.clazz. getPluginConfigClass().jsonToPython) plugin.config = gg(plugin.clazz.getPluginConfigClass())(**jd) self._logger.info("Process plugins not in config") loadedClasses = [x.clazz for x in config.plugins] for clazz in self._pluginService.getPluginClasses(): if clazz not in loadedClasses: self._logger.info("Plugin not in config: %s", clazz) config.plugins.append( Plugin(clazz=clazz, disabled=False, config=clazz.getPluginConfigClass(). getDefaultObject())) self._logger.info("Sort plugins") config.plugins.sort( key=lambda x: x.clazz.__module__ + "." + x.clazz.__name__) self._logger.info("Load layout config") self._loadElementConfig(config.layout) self._logger.info("Loaded config: %s", config) return config
def __init__(self, config, parent) -> None: super().__init__() self._widgetConfig = config self._parent = gg(parent) self._logger = logging.getLogger("playlistTable") self._app = App.instance() self._player = App.instance().getPlayer() self._playlistService = self._app.getPlaylistService() self.setModel(PlaylistModel(self)) self.setColumnWidth(0, int(35 * self._app.getZoom())) self.setColumnWidth(1, int(150 * self._app.getZoom())) self.doubleClicked.connect(self._onDoubleClicked) self.setIconSize(QtCore.QSize(32, 32) * self._app.getZoom()) self.horizontalHeader().setSortIndicator( -1, QtCore.Qt.SortOrder.AscendingOrder) self.setWordWrap(False) self.setSortingEnabled(True) self.setContextMenuPolicy( QtCore.Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect( self._onCustomContextMenuRequested) self.selectionModel().selectionChanged.connect( self._onSelectionChanged) self._app.requestLocateCurrentMusic.connect( self._onRequestLocateCurrentMusic) self._player.stateChanged.connect(self._onPlayerStateChanged) self._player.frontPlaylistIndexAboutToBeChanged.connect( self._onFrontPlaylistIndexAboutToBeChanged) self._player.frontPlaylistIndexChanged.connect( self._onFrontPlaylistIndexChanged) self._player.selectedMusicIndexesChanged.connect( self._onSelectedMusicIndexesChanged) self._player.currentMusicIndexChanged.connect( self._onCurrentMusicIndexChanged) self._player.musicsInserted.connect(self._onMusicsInserted) self._player.musicsRemoved.connect(self._onMusicsRemoved) self._player.musicsSorted.connect(self._onMusicsSorted) self._selectAndFollowMusics(self._player.getSelectedMusicIndexes()) self._loadConfig() self._parent.widgetConfigChanged.connect(self._onWidgetConfigChanged)
def toggleFullscreen(cls, widget: QtWidgets.QWidget): if not hasattr(cls, "__fullscreen"): setattr(cls, "__fullscreen", dict()) jd = getattr(cls, "__fullscreen") if not widget.isFullScreen(): parent = widget.parentWidget() window = QApplication.activeWindow() assert isinstance(parent, QtWidgets.QSplitter) jd[id(widget)] = [ parent, parent.indexOf(widget), parent.sizes(), QApplication.activeWindow() ] widget.setParent(gg(None)) widget.showFullScreen() window.hide() else: parent, index, sizes, window = jd[id(widget)] assert isinstance(parent, QtWidgets.QSplitter) window.show() parent.insertWidget(index, widget) parent.setSizes(sizes) del jd[id(widget)]
def model(self) -> PlaylistManagerModel: return gg(super().model())
def __init__(self): super().__init__() label = QtWidgets.QLabel("Hello World") label.setAlignment(gg(QtCore.Qt.AlignmentFlag.AlignCenter)) self.setLayout(QtWidgets.QGridLayout()) self.layout().addWidget(label)
def createExpandingSpacer(): spacer = QtWidgets.QWidget() spacer.setSizePolicy(gg(QtWidgets.QSizePolicy.Expanding), gg(QtWidgets.QSizePolicy.Expanding)) return spacer
def clearLayout(layout: QtWidgets.QLayout): for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(gg(None, QtCore.QObject)) if layout.itemAt(i).widget() \ else layout.removeItem(layout.itemAt(i))
def _clearAndSelectRow(self, index: int) -> None: self.selectionModel().select( self.model().index(index, 0), gg(QtCore.QItemSelectionModel.SelectionFlag.ClearAndSelect, typing.Any) | QtCore.QItemSelectionModel.SelectionFlag.Rows)
def instance() -> App: return gg(QtCore.QCoreApplication.instance())
def _selectRowRange(self, fromRow, toRow): self.selectionModel().select( QtCore.QItemSelection(self.model().index(fromRow, 0), self.model().index(toRow, 0)), gg(QtCore.QItemSelectionModel.Select, typing.Any) | QtCore.QItemSelectionModel.Rows)
def model(self) -> "PlaylistModel": return gg(super().model())
def _widgetToElement(self, widget: QtWidgets.QWidget) -> Element: from IceSpringMusicPlayer.common.replacerMixin import ReplacerMixin assert isinstance(widget, (ReplacerMixin, QtWidgets.QWidget)) return Element( clazz=type(widget), vertical=isinstance(widget, QtWidgets.QSplitter) and widget.orientation() == QtCore.Qt.Orientation.Vertical, weight=widget.height() if isinstance(widget.parentWidget(), QtWidgets.QSplitter) and gg( widget.parentWidget()).orientation() == QtCore.Qt.Orientation.Vertical else widget.width(), config=widget.getWidgetConfig() if isinstance(widget, PluginWidgetMixin) else dict(), children=[self._widgetToElement(widget.widget(x)) for x in range(widget.count())] if isinstance( widget, SplitterWidget) else [] )