diff --git a/demo/demo.py b/demo/demo.py index 11c3f09..e1d6adf 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -1,36 +1,76 @@ import datetime import logging +import os +import sys +from PyQt5 import uic from PyQt5.QtCore import (QCoreApplication, QDir, Qt, QSettings, QSignalBlocker, - QRect) -from PyQt5.QtGui import QGuiApplication + QRect, QPoint, qDebug, qInstallMessageHandler, + QtDebugMsg, QtInfoMsg, QtWarningMsg, + QtCriticalMsg, QtFatalMsg) +from PyQt5.QtGui import (QGuiApplication, QIcon, QCloseEvent) from PyQt5.QtWidgets import (QCalendarWidget, QFileSystemModel, QFrame, QLabel, QMenu, QTreeView, QAction, QWidgetAction, - QComboBox, QStyle, QSizePolicy, QInputDialog) - -from PyQt5 import QtWidgets + QComboBox, QStyle, QSizePolicy, QInputDialog, QMenu, + QToolButton, QWidget, QPlainTextEdit, + QTableWidget, QTableWidgetItem, QApplication, + QMessageBox) +try: + from PyQt5.QAxContainer import QAxWidget +except ImportError: + ACTIVEX_AVAILABLE = False +else: + ACTIVEX_AVAILABLE = True from PyQtAds import QtAds +import rc # pyrcc5 demo.qrc -o rc.py + +UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + class _State: label_count = 0 calendar_count = 0 file_system_count = 0 + editor_count = 0 + table_count = 0 + activex_count = 0 + +def features_string(dock_widget: QtAds.CDockWidget) -> str: + '''Function returns a features string with closable (c), movable (m) and floatable (f) + features. i.e. The following string is for a not closable but movable and floatable + widget: c- m+ f+''' + + f = dock_widget.features() + closable = f & QtAds.CDockWidget.DockWidgetClosable + movable = f & QtAds.CDockWidget.DockWidgetMovable + floatable = f &QtAds.CDockWidget.DockWidgetFloatable + + return "c{} m{} f{}".format("+" if closable else "-", + "+" if movable else "-", + "+" if floatable else "-") + + +def append_feature_string_to_window_title(dock_widget: QtAds.CDockWidget): + '''Appends the string returned by features_string() to the window title of + the given DockWidget''' + + dock_widget.setWindowTitle(dock_widget.windowTitle() + " ({})".format(features_string(dock_widget))) + + +def svg_icon(filename: str): + '''Helper function to create an SVG icon''' + # This is a workaround, because because in item views SVG icons are not + # properly scaled an look blurry or pixelate + icon = QIcon(filename) + icon.addPixmap(icon.pixmap(92)) + return icon + def create_long_text_label_dock_widget(view_menu: QMenu) -> QtAds.CDockWidget: - ''' - Create long text label dock widget - - Parameters - ---------- - view_menu : QMenu - - Returns - ------- - value : QtAds.CDockWidget - ''' label = QLabel() label.setWordWrap(True) label.setAlignment(Qt.AlignTop | Qt.AlignLeft) @@ -46,10 +86,9 @@ pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. - '''.format(_State.label_count, str(datetime.datetime.now()))) - _State.label_count += 1 - + '''.format(_State.label_count, datetime.datetime.now().strftime("%H:%M:%S:%f"))) dock_widget = QtAds.CDockWidget("Label {}".format(_State.label_count)) + _State.label_count += 1 dock_widget.setWidget(label) view_menu.addAction(dock_widget.toggleViewAction()) @@ -57,39 +96,18 @@ quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. def create_calendar_dock_widget(view_menu: QMenu) -> QtAds.CDockWidget: - ''' - Create calendar dock widget - - Parameters - ---------- - view_menu : QMenu - - Returns - ------- - value : QtAds.CDockWidget - ''' widget = QCalendarWidget() dock_widget = QtAds.CDockWidget("Calendar {}".format(_State.calendar_count)) _State.calendar_count += 1 dock_widget.setWidget(widget) dock_widget.setToggleViewActionMode(QtAds.CDockWidget.ActionModeShow) + dock_widget.setIcon(svg_icon(":/adsdemo/images/date_range.svg")) view_menu.addAction(dock_widget.toggleViewAction()) return dock_widget def create_file_system_tree_dock_widget(view_menu: QMenu) -> QtAds.CDockWidget: - ''' - Create file system tree dock widget - - Parameters - ---------- - view_menu : QMenu - - Returns - ------- - value : QtAds.CDockWidget - ''' widget = QTreeView() widget.setFrameShape(QFrame.NoFrame) @@ -102,9 +120,80 @@ def create_file_system_tree_dock_widget(view_menu: QMenu) -> QtAds.CDockWidget: dock_widget.setWidget(widget) view_menu.addAction(dock_widget.toggleViewAction()) return dock_widget + + +def create_editor_widget(view_menu: QMenu) -> QtAds.CDockWidget: + widget = QPlainTextEdit() + widget.setPlaceholderText("This is an editor. If you close the editor, it will be " + "deleted. Enter your text here.") + widget.setStyleSheet("border: none") + dock_widget = QtAds.CDockWidget("Editor {}".format(_State.editor_count)) + _State.editor_count += 1 + dock_widget.setWidget(widget) + dock_widget.setIcon(svg_icon(":/adsdemo/images/edit.svg")) + dock_widget.setFeature(QtAds.CDockWidget.CustomCloseHandling, True) + view_menu.addAction(dock_widget.toggleViewAction()) + + options_menu = QMenu(dock_widget) + options_menu.setTitle("Options") + options_menu.setToolTip(options_menu.title()) + options_menu.setIcon(svg_icon(":/adsdemo/images/custom-menu-button.svg")) + menu_action = options_menu.menuAction() + # The object name of the action will be set for the QToolButton that + # is created in the dock area title bar. You can use this name for CSS + # styling + menu_action.setObjectName("options_menu") + dock_widget.setTitleBarActions([options_menu.menuAction()]) + a = options_menu.addAction("Clear Editor") + a.triggered.connect(widget.clear) + + return dock_widget -class MainWindow(QtWidgets.QMainWindow): +def create_table_widget(view_menu: QMenu) -> QtAds.CDockWidget: + widget = QTableWidget() + dock_widget = QtAds.CDockWidget("Table {}".format(_State.table_count)) + _State.table_count += 1 + COLCOUNT = 5 + ROWCOUNT = 30 + widget.setColumnCount(COLCOUNT) + widget.setRowCount(ROWCOUNT) + for col in range(ROWCOUNT): + widget.setHorizontalHeaderItem(col, QTableWidgetItem("Col {}".format(col+1))) + for row in range(ROWCOUNT): + widget.setItem(row, col, QTableWidgetItem("T {:}-{:}".format(row+1, col+1))) + + dock_widget.setWidget(widget) + dock_widget.setIcon(svg_icon(":/adsdemo/images/grid_on.svg")) + view_menu.addAction(dock_widget.toggleViewAction()) + return dock_widget + + +if ACTIVEX_AVAILABLE: + def create_activex_widget(view_menu: QMenu, parent: QWidget = None) -> QtAds.CDockWidget: + widget = QAxWidget("{6bf52a52-394a-11d3-b153-00c04f79faa6}", parent) + dock_widget = QtAds.CDockWidget("Active X {}".format(_State.activex_count)) + _State.activex_count += 1 + dock_widget.setWidget(widget) + view_menu.addAction(dock_widget.toggleViewAction()) + return dock_widget + + +class CustomComponentsFactory(QtAds.CDockComponentsFactory): + + def createDockAreaTitleBar(self, dock_area: QtAds.CDockAreaWidget) -> QtAds.CDockAreaTitleBar: + title_bar = QtAds.CDockAreaTitleBar(dock_area) + custom_button = QToolButton(dock_area) + custom_button.setToolTip("Help") + custom_button.setIcon(svg_icon(":/adsdemo/images/help_outline.svg")) + custom_button.setAutoRaise(True) + index = title_bar.indexOf(title_bar.button(QtAds.TitleBarButtonTabsMenu)) + title_bar.insertWidget(index + 1, custom_button) + return title_bar + + + +class MainWindow(MainWindowUI, MainWindowBase): save_perspective_action: QAction perspective_list_action: QWidgetAction perspective_combo_box: QComboBox @@ -117,111 +206,101 @@ class MainWindow(QtWidgets.QMainWindow): self.perspective_combo_box = None self.dock_manager = None - self.setup_ui() - - self.dock_manager = QtAds.CDockManager(self) - self.perspective_combo_box.activated[str].connect(self.dock_manager.openPerspective) - self.create_content() - self.resize(800, 600) - self.restore_state() - self.restore_perspectives() - - def setup_ui(self): - self.setObjectName("MainWindow") - self.resize(400, 300) - self.setDockOptions(QtWidgets.QMainWindow.AllowTabbedDocks) - self.centralWidget = QtWidgets.QWidget(self) - self.centralWidget.setObjectName("centralWidget") - self.setCentralWidget(self.centralWidget) - self.status_bar = QtWidgets.QStatusBar(self) - self.status_bar.setObjectName("statusBar") - self.setStatusBar(self.status_bar) - self.menu_bar = QtWidgets.QMenuBar(self) - self.menu_bar.setGeometry(QRect(0, 0, 400, 21)) - self.menu_bar.setObjectName("menuBar") - self.menu_file = QtWidgets.QMenu(self.menu_bar) - self.menu_file.setObjectName("menuFile") - self.menu_view = QtWidgets.QMenu(self.menu_bar) - self.menu_view.setObjectName("menuView") - self.menu_about = QtWidgets.QMenu(self.menu_bar) - self.menu_about.setObjectName("menuAbout") - self.setMenuBar(self.menu_bar) - self.tool_bar = QtWidgets.QToolBar(self) - self.tool_bar.setObjectName("toolBar") - self.addToolBar(Qt.TopToolBarArea, self.tool_bar) - self.action_exit = QtWidgets.QAction(self) - self.action_exit.setObjectName("actionExit") - self.action_save_state = QtWidgets.QAction(self) - self.action_save_state.setObjectName("actionSaveState") - self.action_save_state.triggered.connect(self.saveState) - - self.action_restore_state = QtWidgets.QAction(self) - self.action_restore_state.setObjectName("actionRestoreState") - self.action_restore_state.triggered.connect(self.restore_state) - - self.menu_file.addAction(self.action_exit) - self.menu_file.addAction(self.action_save_state) - self.menu_file.addAction(self.action_restore_state) - self.menu_bar.addAction(self.menu_file.menuAction()) - self.menu_bar.addAction(self.menu_view.menuAction()) - self.menu_bar.addAction(self.menu_about.menuAction()) - - self.setWindowTitle("MainWindow") - self.menu_file.setTitle("File") - self.menu_view.setTitle("View") - self.menu_about.setTitle("About") - self.tool_bar.setWindowTitle("toolBar") - self.action_exit.setText("Exit") - self.action_save_state.setText("Save State") - self.action_restore_state.setText("Restore State") + self.setupUi(self) self.create_actions() + + # uncomment the following line if the tab close button should be + # a QToolButton instead of a QPushButton + # QtAds.CDockManager.setConfigFlags(QtAds.CDockManager.configFlags() | QtAds.CDockManager.TabCloseButtonIsToolButton) + + # uncomment the following line if you want a fixed tab width that does + # not change if the visibility of the close button changes + # QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.RetainTabSizeWhenCloseButtonHidden, True) - def create_actions(self): - ''' - Creates the toolbar actions - ''' - self.tool_bar.addAction(self.action_save_state) - self.action_save_state.setIcon(self.style().standardIcon(QStyle.SP_DialogSaveButton)) - self.tool_bar.addAction(self.action_restore_state) - self.action_restore_state.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton)) - self.save_perspective_action = QAction("Save Perspective", self) - self.save_perspective_action.triggered.connect(self.save_perspective) + # uncomment the follwing line if you want to use non opaque undocking and splitter + # movements + # QtAds.CDockManager.setConfigFlags(QtAds.CDockManager.DefaultNonOpaqueConfig) - self.perspective_list_action = QWidgetAction(self) - self.perspective_combo_box = QComboBox(self) - self.perspective_combo_box.setSizeAdjustPolicy(QComboBox.AdjustToContents) - self.perspective_combo_box.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - self.perspective_list_action.setDefaultWidget(self.perspective_combo_box) - self.tool_bar.addSeparator() - self.tool_bar.addAction(self.perspective_list_action) - self.tool_bar.addAction(self.save_perspective_action) + # Now create the dock manager and its content + self.dock_manager = QtAds.CDockManager(self) + + # Uncomment the following line to have the old style where the dock + # area close button closes the active tab + # QtAds.CDockManager.setConfigFlags(QtAds.CDockManager.DockAreaHasCloseButton + # | QtAds.CDockManager.DockAreaCloseButtonClosesTab) + self.perspective_combo_box.activated[str].connect(self.dock_manager.openPerspective) + + self.create_content() + # Default window geometry - center on screen + self.resize(1280, 720) + self.setGeometry(QStyle.alignedRect( + Qt.LeftToRight, Qt.AlignCenter, self.frameSize(), + QGuiApplication.primaryScreen().availableGeometry())) + + # self.restore_state() + self.restore_perspectives() def create_content(self): - ''' - Fill the dock manager with dock widgets - ''' # Test container docking - view_menu = self.menu_view + view_menu = self.menuView dock_widget = create_calendar_dock_widget(view_menu) - dock_widget.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton)) dock_widget.setFeature(QtAds.CDockWidget.DockWidgetClosable, False) - self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, dock_widget) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetMovable, False) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetFloatable, False) + special_dock_area = self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, dock_widget) + + # For this Special Dock Area we want to avoid dropping on the center of it (i.e. we don't want this widget to be ever tabbified): + special_dock_area.setAllowedAreas(QtAds.OuterDockAreas) + # special_dock_area.setAllowedAreas(QtAds.LeftDockWidgetArea | QtAds.RightDockWidgetArea) # just for testing + self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, create_long_text_label_dock_widget(view_menu)) file_system_widget = create_file_system_tree_dock_widget(view_menu) tool_bar = file_system_widget.createDefaultToolBar() - tool_bar.addAction(self.action_save_state) - tool_bar.addAction(self.action_restore_state) + tool_bar.addAction(self.actionSaveState) + tool_bar.addAction(self.actionRestoreState) + file_system_widget.setFeature(QtAds.CDockWidget.DockWidgetFloatable, False) + append_feature_string_to_window_title(file_system_widget) self.dock_manager.addDockWidget(QtAds.BottomDockWidgetArea, file_system_widget) + file_system_widget = create_file_system_tree_dock_widget(view_menu) tool_bar = file_system_widget.createDefaultToolBar() - tool_bar.addAction(self.action_save_state) - tool_bar.addAction(self.action_restore_state) + tool_bar.addAction(self.actionSaveState) + tool_bar.addAction(self.actionRestoreState) file_system_widget.setFeature(QtAds.CDockWidget.DockWidgetMovable, False) + file_system_widget.setFeature(QtAds.CDockWidget.DockWidgetFloatable, False) + append_feature_string_to_window_title(file_system_widget) + + # Test custom factory - we inject a help button into the title bar + self.factory = CustomComponentsFactory() + QtAds.CDockComponentsFactory.setFactory(self.factory) top_dock_area = self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, file_system_widget) + QtAds.CDockComponentsFactory.resetDefaultFactory() + + + # We create a calendar widget and clear all flags to prevent the dock area + # from closing dock_widget = create_calendar_dock_widget(view_menu) dock_widget.setFeature(QtAds.CDockWidget.DockWidgetClosable, False) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetMovable, False) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetFloatable, False) dock_widget.setTabToolTip("Tab ToolTip\nHodie est dies magna") - self.dock_manager.addDockWidget(QtAds.CenterDockWidgetArea, dock_widget, top_dock_area) + dock_area = self.dock_manager.addDockWidget(QtAds.CenterDockWidgetArea, dock_widget, top_dock_area) + + # Now we add a custom button to the dock area title bar that will create + # new editor widgets when clicked + custom_button = QToolButton(dock_area) + custom_button.setToolTip("Create Editor") + custom_button.setIcon(svg_icon(":/adsdemo/images/plus.svg")) + custom_button.setAutoRaise(True) + title_bar = dock_area.titleBar() + index = title_bar.indexOf(title_bar.tabBar()) + title_bar.insertWidget(index + 1, custom_button) + def on_button_clicked(): + dock_widget = create_editor_widget(self.menuView) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + self.dock_manager.addDockWidgetTabToArea(dock_widget, dock_area) + dock_widget.closeRequested.connect(self.on_editor_close_requested) + custom_button.clicked.connect(on_button_clicked) # Test dock area docking right_dock_area = self.dock_manager.addDockWidget( @@ -236,11 +315,112 @@ class MainWindow(QtWidgets.QMainWindow): create_long_text_label_dock_widget(view_menu), right_dock_area) self.dock_manager.addDockWidget( - QtAds.RightDockWidgetArea, + QtAds.CenterDockWidgetArea, create_long_text_label_dock_widget(view_menu), right_dock_area) self.dock_manager.addDockWidget( QtAds.CenterDockWidgetArea, create_long_text_label_dock_widget(view_menu), bottom_dock_area) + + action = self.menuView.addAction("Set {} floating".format(dock_widget.windowTitle())) + action.triggered.connect(dock_widget.setFloating) + + if ACTIVEX_AVAILABLE: + flags = self.dock_manager.configFlags() + if flags & QtAds.CDockManager.OpaqueUndocking: + self.dock_manager.addDockWidget(QtAds.CenterDockWidgetArea, + create_activex_widget(view_menu), right_dock_area) + + for dock_widget in self.dock_manager.dockWidgetsMap().values(): + dock_widget.viewToggled.connect(self.on_view_toggled) + dock_widget.visibilityChanged.connect(self.on_view_visibility_changed) + + def create_actions(self): + self.toolBar.addAction(self.actionSaveState) + self.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + self.actionSaveState.setIcon(svg_icon(":/adsdemo/images/save.svg")) + self.toolBar.addAction(self.actionRestoreState) + self.actionRestoreState.setIcon(svg_icon(":/adsdemo/images/restore.svg")) + + self.save_perspective_action = QAction("Create Perspective", self) + self.save_perspective_action.setIcon(svg_icon(":/adsdemo/images/picture_in_picture.svg")) + self.save_perspective_action.triggered.connect(self.save_perspective) + self.perspective_list_action = QWidgetAction(self) + self.perspective_combo_box = QComboBox(self) + self.perspective_combo_box.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.perspective_combo_box.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.perspective_list_action.setDefaultWidget(self.perspective_combo_box) + self.toolBar.addSeparator() + self.toolBar.addAction(self.perspective_list_action) + self.toolBar.addAction(self.save_perspective_action) + + a = self.toolBar.addAction("Create Editor") + a.setToolTip("Creates floating dynamic dockable editor windows that are deleted on close") + a.setIcon(svg_icon(":/adsdemo/images/note_add.svg")) + a.triggered.connect(self.create_editor) + + a = self.toolBar.addAction("Create Table") + a.setToolTip("Creates floating dynamic dockable table with millions of entries") + a.setIcon(svg_icon(":/adsdemo/images/grid_on.svg")) + a.triggered.connect(self.create_table) + + def closeEvent(self, event: QCloseEvent): + self.save_state() + super().closeEvent(event) + + def on_action_save_state_triggered(state: bool): + qDebug("MainWindow::on_action_save_state_triggered") + self.save_state() + + def on_action_restore_state_triggered(state: bool): + qDebug("MainWindow::on_action_restore_state_triggered") + self.restore_state() + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, "Save perspective", + "Enter unique name:") + + if ok and perspective_name: + self.dock_manager.addPerspective(perspective_name) + _ = QSignalBlocker(self.perspective_combo_box) + self.perspective_combo_box.clear() + self.perspective_combo_box.addItems(self.dock_manager.perspectiveNames()) + self.perspective_combo_box.setCurrentText(perspective_name) + + self.save_perspectives() + + def on_view_toggled(self, open: bool): + dock_widget = self.sender() + if dock_widget is None: + return + + qDebug("{} view_toggled({})".format(dock_widget.objectName(), open)) + + def on_view_visibility_changed(self, visible: bool): + dock_widget = self.sender() + if dock_widget is None: + return + + qDebug("{} visibility_changed({})".format(dock_widget.objectName(), visible)) + + def create_editor(self): + dock_widget = create_editor_widget(self.menuView) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + floating_widget = self.dock_manager.addDockWidgetFloating(dock_widget) + floating_widget.move(QPoint(20, 20)) + dock_widget.closeRequested.connect(self.on_editor_close_requested) + + def on_editor_close_requested(self): + dock_widget = self.sender() + result = QMessageBox.question(self, "Close Editor", + "Editor {} contains unsaved changes? Would you like to close it?".format(dock_widget.windowTitle())) + if result == QMessageBox.Yes: + dock_widget.closeDockWidget() + + def create_table(self): + dock_widget = create_table_widget(self.menuView) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + floating_widget = self.dock_manager.addDockWidgetFloating(dock_widget) + floating_widget.move(QPoint(40, 40)) def save_state(self): ''' @@ -251,13 +431,6 @@ class MainWindow(QtWidgets.QMainWindow): settings.setValue("mainWindow/State", self.saveState()) settings.setValue("mainWindow/DockingState", self.dock_manager.saveState()) - def save_perspectives(self): - ''' - Save the list of perspectives - ''' - settings = QSettings("Settings.ini", QSettings.IniFormat) - self.dock_manager.savePerspectives(settings) - def restore_state(self): ''' Restores the dock manager state @@ -274,6 +447,13 @@ class MainWindow(QtWidgets.QMainWindow): state = settings.value("mainWindow/DockingState") if state is not None: self.dock_manager.restore_state(state) + + def save_perspectives(self): + ''' + Save the list of perspectives + ''' + settings = QSettings("Settings.ini", QSettings.IniFormat) + self.dock_manager.savePerspectives(settings) def restore_perspectives(self): ''' @@ -295,22 +475,31 @@ class MainWindow(QtWidgets.QMainWindow): self.save_perspectives() -def main(app_): - main_window = MainWindow() - main_window.show() - state = main_window.dock_manager.saveState() - # print('This is what the saved state looks like in XML:') - # print(str(state, 'utf-8')) - # print() - # main_window.dock_manager.restore_state(state) - return main_window +def my_message_output(type, context, msg): + if type == QtDebugMsg: + print("Debug: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtInfoMsg: + print("Info: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtWarningMsg: + print("Warning: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtCriticalMsg: + print("Critical: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtFatalMsg: + print("Fatal: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) if __name__ == '__main__': - # logging.basicConfig(level='DEBUG') QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling) - app = QtWidgets.QApplication([]) - window = main(app) - window.show() + app = QApplication(sys.argv) + app.setQuitOnLastWindowClosed(True) + + with open(os.path.join(os.path.dirname(__file__), "app.css"), "r") as style_sheet_file: + app.setStyleSheet(style_sheet_file.read()) + + qInstallMessageHandler(my_message_output) + qDebug("Message handler test") + + mw = MainWindow() + mw.show() app.exec_() diff --git a/example/example.py b/example/example.py new file mode 100644 index 0000000..529bfc1 --- /dev/null +++ b/example/example.py @@ -0,0 +1,52 @@ +import datetime +import logging +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QApplication, QLabel + +from PyQtAds import QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'MainWindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + + +class MainWindow(MainWindowUI, MainWindowBase): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setupUi(self) + + # Create the dock manager. Because the parent parameter is a QMainWindow + # the dock manager registers itself as the central widget. + self.dock_manager = QtAds.CDockManager(self) + + # Create example content label - this can be any application specific + # widget + l = QLabel() + l.setWordWrap(True) + l.setAlignment(Qt.AlignTop | Qt.AlignLeft); + l.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ") + + # Create a dock widget with the title Label 1 and set the created label + # as the dock widget content + dock_widget = QtAds.CDockWidget("Label 1") + dock_widget.setWidget(l) + + # Add the toggleViewAction of the dock widget to the menu to give + # the user the possibility to show the dock widget if it has been closed + self.menuView.addAction(dock_widget.toggleViewAction()) + + # Add the dock widget to the top dock widget area + self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/recipes/meta.yaml b/recipes/meta.yaml deleted file mode 100644 index d7ee54b..0000000 --- a/recipes/meta.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{% set data = load_setup_py_data() %} - -package: - name: pyqtads - version: {{ data.get('version') }} - -source: - path: ../ - -build: - number: 0 - script: python setup.py install --single-version-externally-managed --record=record.txt --conda-recipe - -requirements: - build: - - python - - setuptools - - pyqt>=5.9 - - sip>=4.19 - run: - - python - - pyqt>=5.9 - - sip>=4.19 - - pywin32 [win] - -test: - imports: - - PyQtAds - -about: - home: {{ data.get('url') }} - license: {{ data.get('license') }} - license_family: LGPL - license_file: 'LICENSE.md' - summary: {{ data.get('description') }} - description: {{ data.get('description') }} - doc_url: '' - dev_url: {{ data.get('url') }} - -extra: - recipe-maintainers: 'nicolas.elie@cnrs.fr' diff --git a/setup.py b/setup.py index 0cab022..6ae6e74 100644 --- a/setup.py +++ b/setup.py @@ -201,6 +201,10 @@ class build_ext(sipdistutils.build_ext): def get_moc_args(out_file, source): if sys.platform.startswith('linux'): return ["moc", "-D", "Q_OS_LINUX=1", "-o", out_file, source] + if sys.platform.startswith('darwin'): + return ["moc", "-D", "Q_OS_MACOS=1", "-o", out_file, source] + if sys.platform.startswith('win'): + return ["moc", "-D", "Q_OS_WIN=1", "-o", out_file, source] return ["moc", "-o", out_file, source] # Run moc on all header files. diff --git a/simple.py b/simple.py deleted file mode 100644 index 9fff924..0000000 --- a/simple.py +++ /dev/null @@ -1,82 +0,0 @@ -import logging - -from PyQt5 import QtWidgets, QtCore -from PyQt5.QtCore import Qt -from PyQtAds import QtAds - - -class MainWindow(QtWidgets.QMainWindow): - def __init__(self, parent=None): - super().__init__(parent) - self.setup_ui() - self.dock_manager = QtAds.CDockManager(self) - - self.dock_widgets = [] - - for label_text, area in ( - ('1 Top', QtAds.TopDockWidgetArea), - ('2 Bottom', QtAds.BottomDockWidgetArea), - ('3 Left', QtAds.LeftDockWidgetArea), - ('4 Right', QtAds.RightDockWidgetArea), - ): - # Create example content label - this can be any application specific - # widget - label = QtWidgets.QLabel() - label.setWordWrap(True) - label.setAlignment(Qt.AlignTop | Qt.AlignLeft) - label.setText(f"{label_text}: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ") - - # Create a dock widget with the title Label 1 and set the created label - # as the dock widget content - dock_widget = QtAds.CDockWidget(label_text) - dock_widget.setWidget(label) - self.dock_widgets.append(dock_widget) - - # Add the toggleViewAction of the dock widget to the menu to give - # the user the possibility to show the dock widget if it has been closed - self.menu_view.addAction(dock_widget.toggleViewAction()) - - # Add the dock widget to the top dock widget area - self.dock_manager.addDockWidget(area, dock_widget) - - def setup_ui(self): - self.setWindowTitle("MainWindow") - self.setObjectName("MainWindow") - self.resize(400, 300) - self.central_widget = QtWidgets.QWidget(self) - self.central_widget.setObjectName("central_widget") - self.setCentralWidget(self.central_widget) - - self.menu_bar = QtWidgets.QMenuBar(self) - self.menu_bar.setGeometry(QtCore.QRect(0, 0, 400, 21)) - self.menu_bar.setObjectName("menuBar") - - self.menu_view = QtWidgets.QMenu(self.menu_bar) - self.menu_view.setObjectName("menu_view") - self.menu_view.setTitle("View") - self.setMenuBar(self.menu_bar) - - self.status_bar = QtWidgets.QStatusBar(self) - self.status_bar.setObjectName("statusBar") - self.setStatusBar(self.status_bar) - self.menu_bar.addAction(self.menu_view.menuAction()) - - -def main(app): - main = MainWindow() - main.show() - state = main.dock_manager.saveState() - print('This is what the saved state looks like in XML:') - print(state) - print() - main.dock_manager.restoreState(state) - return main - - -if __name__ == '__main__': - logging.basicConfig(level='DEBUG') - app = QtWidgets.QApplication([]) - window = main(app) - window.show() - print('shown') - app.exec_() diff --git a/sip/DockManager.sip b/sip/DockManager.sip index 4277d04..828d416 100644 --- a/sip/DockManager.sip +++ b/sip/DockManager.sip @@ -134,6 +134,8 @@ protected: ads::CDockOverlay* containerOverlay() const; ads::CDockOverlay* dockAreaOverlay() const; + virtual void showEvent(QShowEvent *event); + public: enum eViewMenuInsertionOrder { diff --git a/sip/DockOverlay.sip b/sip/DockOverlay.sip index a712a10..040d634 100644 --- a/sip/DockOverlay.sip +++ b/sip/DockOverlay.sip @@ -27,6 +27,7 @@ public: ads::DockWidgetArea showOverlay(QWidget* target); void hideOverlay(); void enableDropPreview(bool Enable); + bool dropPreviewEnabled() const; QRect dropOverlayRect() const; virtual bool event(QEvent *e); @@ -58,6 +59,8 @@ protected: void setIconOverlayColor(const QColor& Color); void setIconArrowColor(const QColor& Color); void setIconShadowColor(const QColor& Color); + virtual void showEvent(QShowEvent* e); + void setAreaWidgets(const QHash& widgets); public: CDockOverlayCross(ads::CDockOverlay* overlay /TransferThis/); diff --git a/sip/DockWidget.sip b/sip/DockWidget.sip index 2942e26..c06e063 100644 --- a/sip/DockWidget.sip +++ b/sip/DockWidget.sip @@ -30,7 +30,9 @@ public: DockWidgetMovable, DockWidgetFloatable, DockWidgetDeleteOnClose, - AllDockWidgetFeatures, + CustomCloseHandling, + DefaultDockWidgetFeatures, + AllDockWidgetFeatures, NoDockWidgetFeatures }; typedef QFlags DockWidgetFeatures; @@ -83,6 +85,9 @@ public: Qt::ToolButtonStyle toolBarStyle(ads::CDockWidget::eState State) const; void setToolBarIconSize(const QSize& IconSize, ads::CDockWidget::eState State); QSize toolBarIconSize(eState State) const; + void setTitleBarActions(QList actions); + virtual QList titleBarActions() const; + void setTabToolTip(const QString &text); public: @@ -92,12 +97,14 @@ public slots: void toggleView(bool Open = true); void setFloating(); void deleteDockWidget(); + void closeDockWidget(); signals: void viewToggled(bool Open); void closed(); void titleChanged(const QString& Title); void topLevelChanged(bool topLevel); + void closeRequested(); void visibilityChanged(bool visible); void featuresChanged(ads::CDockWidget::DockWidgetFeatures features); }; diff --git a/sip/DockWidgetTab.sip b/sip/DockWidgetTab.sip index ca2d50a..f6c2453 100644 --- a/sip/DockWidgetTab.sip +++ b/sip/DockWidgetTab.sip @@ -23,9 +23,9 @@ public: virtual ~CDockWidgetTab(); bool isActiveTab() const; void setActiveTab(bool active); - ads::CDockWidget* dockWidget() const; void setDockAreaWidget(ads::CDockAreaWidget* DockArea /Transfer/); ads::CDockAreaWidget* dockAreaWidget() const; + ads::CDockWidget* dockWidget() const; void setIcon(const QIcon& Icon); const QIcon& icon() const; QString text() const; diff --git a/sip/FloatingDockContainer.sip b/sip/FloatingDockContainer.sip index 0add962..38c4ab6 100644 --- a/sip/FloatingDockContainer.sip +++ b/sip/FloatingDockContainer.sip @@ -5,6 +5,13 @@ %If (Qt_5_0_0 -) +%If (WS_X11) + typedef QDockWidget tFloatingWidgetBase; +%End +%If (!WS_X11) + typedef QWidget tFloatingWidgetBase; +%End + namespace ads { @@ -23,7 +30,7 @@ public: }; -class CFloatingDockContainer : QWidget, ads::IFloatingWidget +class CFloatingDockContainer : tFloatingWidgetBase, ads::IFloatingWidget { %TypeHeaderCode diff --git a/sip/ads.sip b/sip/ads.sip index 5a881e8..fe92502 100644 --- a/sip/ads.sip +++ b/sip/ads.sip @@ -1,7 +1,6 @@ %Module(name=PyQtAds.QtAds.ads, call_super_init=True, keyword_arguments="Optional", use_limited_api=True) %Import QtCore/QtCoremod.sip %DefaultSupertype sip.simplewrapper -%Platforms {Linux macOS Windows} %Include ads_globals.sip %Include DockWidget.sip @@ -16,12 +15,9 @@ %Include DockSplitter.sip %Include DockWidgetTab.sip %Include ElidingLabel.sip +%Include FloatingDockContainer.sip %Include FloatingDragPreview.sip %Include IconProvider.sip -%If (Linux) - %Include linux/FloatingDockContainer.sip +%If (WS_X11) %Include linux/FloatingWidgetTitleBar.sip %End -%If (!Linux) -%Include FloatingDockContainer.sip -%End diff --git a/sip/ads_globals.sip b/sip/ads_globals.sip index ead6b0e..66b0dbe 100644 --- a/sip/ads_globals.sip +++ b/sip/ads_globals.sip @@ -8,6 +8,13 @@ namespace ads #include %End + enum eStateFileVersion + { + InitialVerison, + Version1, + CurrentVersion + }; + enum DockWidgetArea { NoDockWidgetArea, diff --git a/sip/linux/FloatingDockContainer.sip b/sip/linux/FloatingDockContainer.sip deleted file mode 100644 index 7a01061..0000000 --- a/sip/linux/FloatingDockContainer.sip +++ /dev/null @@ -1,67 +0,0 @@ -// NOTE: this is the Linux version, with QDockWidget as the -// CFloatingDockContainer base class. - -%Import QtWidgets/QtWidgetsmod.sip - -%If (Qt_5_0_0 -) - -namespace ads -{ - -class IFloatingWidget -{ - %TypeHeaderCode - #include - %End - -public: - virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, - ads::eDragState DragState, QWidget* MouseEventHandler) = 0; - - virtual void moveFloating() = 0; - virtual void finishDragging() = 0; -}; - - -class CFloatingDockContainer : QDockWidget, ads::IFloatingWidget -{ - - %TypeHeaderCode - #include - %End - -protected: - virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, - ads::eDragState DragState, QWidget* MouseEventHandler); - void startDragging(const QPoint& DragStartMousePos, const QSize& Size, - QWidget* MouseEventHandler); - virtual void finishDragging(); - void initFloatingGeometry(const QPoint& DragStartMousePos, const QSize& Size); - void moveFloating(); - bool restoreState(ads::CDockingStateReader& Stream, bool Testing); - void updateWindowTitle(); - - -protected: - virtual void changeEvent(QEvent *event); - virtual void moveEvent(QMoveEvent *event); - virtual bool event(QEvent *e); - virtual void closeEvent(QCloseEvent *event); - virtual void hideEvent(QHideEvent *event); - virtual void showEvent(QShowEvent *event); - -public: - CFloatingDockContainer(ads::CDockManager* DockManager /TransferThis/); - CFloatingDockContainer(ads::CDockAreaWidget* DockArea /TransferThis/); - CFloatingDockContainer(ads::CDockWidget* DockWidget /TransferThis/); - virtual ~CFloatingDockContainer(); - ads::CDockContainerWidget* dockContainer() const; - bool isClosable() const; - bool hasTopLevelDockWidget() const; - ads::CDockWidget* topLevelDockWidget() const; - QList dockWidgets() const; -}; - -}; - -%End diff --git a/sip/linux/FloatingWidgetTitleBar.sip b/sip/linux/FloatingWidgetTitleBar.sip index ceeef80..fe37bd3 100644 --- a/sip/linux/FloatingWidgetTitleBar.sip +++ b/sip/linux/FloatingWidgetTitleBar.sip @@ -5,7 +5,7 @@ namespace ads { %TypeHeaderCode - #include + #include %End class CFloatingWidgetTitleBar : QWidget