├── scripts ├── window_switcher │ ├── __init__.py │ ├── libs │ │ ├── __init__.py │ │ └── qt │ │ │ ├── __init__.py │ │ │ ├── resources │ │ │ └── core.css │ │ │ ├── layout.py │ │ │ └── stylesheet.py │ ├── vendor │ │ ├── __init__.py │ │ └── Qt.py │ ├── resources │ │ ├── no_image.png │ │ └── window_switcher.css │ ├── settings.py │ ├── window_helper.py │ └── view.py └── __init__.py ├── install_shelf.mel ├── install_hotkey.mel ├── README.md ├── LICENSE ├── install_shelf.py ├── install_hotkey.py └── .gitignore /scripts/window_switcher/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /scripts/window_switcher/libs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /scripts/window_switcher/libs/qt/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /scripts/window_switcher/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /scripts/window_switcher/resources/no_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tm8r/MayaWindowSwitcher/HEAD/scripts/window_switcher/resources/no_image.png -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | __title__ = "MayaWindowSwitcher" 5 | __version__ = "1.0.1" 6 | __copyright__ = "tm8r" 7 | -------------------------------------------------------------------------------- /scripts/window_switcher/libs/qt/resources/core.css: -------------------------------------------------------------------------------- 1 | QLabel#h1 { 2 | background-color: #666; 3 | font-weight: bold; 4 | color: #fff; 5 | padding: 2px; 6 | border-radius: 2px; 7 | } 8 | 9 | QToolButton#h1 { 10 | background-color: #666; 11 | font-weight: bold; 12 | color: #fff; 13 | border: none; 14 | border-radius: 2px; 15 | } 16 | 17 | QFrame#inner { 18 | background-color: #494949; 19 | } 20 | 21 | QFrame#header { 22 | background-color: #333; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /install_shelf.mel: -------------------------------------------------------------------------------- 1 | /* WindowSwitcher install script */ 2 | 3 | global proc installShelfTm8rWindowSwitcher(){ 4 | string $whatIs = `whatIs installShelfTm8rWindowSwitcher`; 5 | string $tmpPath = `match ": (.*)" $whatIs`; 6 | string $melPath = `substitute ": " $tmpPath ""`; 7 | string $pythonPath = `substitute "install_shelf.mel" $melPath "install_shelf.py"`; 8 | print($pythonPath); 9 | python("import imp;imp.load_source('_installShelfTm8rWindowSwitcher', '"+$pythonPath+"')"); 10 | } 11 | 12 | installShelfTm8rWindowSwitcher(); 13 | -------------------------------------------------------------------------------- /install_hotkey.mel: -------------------------------------------------------------------------------- 1 | /* WindowSwitcher install script */ 2 | 3 | global proc installHotkeyTm8rWindowSwitcher(){ 4 | string $whatIs = `whatIs installHotkeyTm8rWindowSwitcher`; 5 | string $tmpPath = `match ": (.*)" $whatIs`; 6 | string $melPath = `substitute ": " $tmpPath ""`; 7 | string $pythonPath = `substitute "install_hotkey.mel" $melPath "install_hotkey.py"`; 8 | print($pythonPath); 9 | python("import imp;imp.load_source('_installHotkeyTm8rWindowSwitcher', '"+$pythonPath+"')"); 10 | } 11 | 12 | installHotkeyTm8rWindowSwitcher(); 13 | -------------------------------------------------------------------------------- /scripts/window_switcher/libs/qt/layout.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | u"""Layout library""" 3 | from __future__ import absolute_import, division, print_function 4 | 5 | 6 | def clear_layout(layout): 7 | u"""clear layout 8 | 9 | Args: 10 | layout (Qt.QtWidgets.QBoxLayout): target layout 11 | """ 12 | if layout is not None: 13 | while layout.count(): 14 | item = layout.takeAt(0) 15 | if item.widget(): 16 | item.widget().deleteLater() 17 | else: 18 | clear_layout(item.layout()) 19 | -------------------------------------------------------------------------------- /scripts/window_switcher/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from maya import cmds 5 | 6 | _SIMPLE_MODE_KEY = "window_switcher_simple_mode" 7 | _SIMPLE_MODE_ENABLED = 1 8 | _SIMPLE_MODE_DISABLED = 0 9 | 10 | 11 | def is_simple_mode(): 12 | return cmds.optionVar(q=_SIMPLE_MODE_KEY) == _SIMPLE_MODE_ENABLED 13 | 14 | 15 | def enable_simple_mode(): 16 | cmds.optionVar(iv=(_SIMPLE_MODE_KEY, _SIMPLE_MODE_ENABLED)) 17 | 18 | 19 | def disable_simple_mode(): 20 | cmds.optionVar(iv=(_SIMPLE_MODE_KEY, _SIMPLE_MODE_DISABLED)) 21 | -------------------------------------------------------------------------------- /scripts/window_switcher/resources/window_switcher.css: -------------------------------------------------------------------------------- 1 | QPushButton { 2 | border: 2px solid #000000; 3 | background: #444444; 4 | color: #ffffff; 5 | } 6 | 7 | QPushButton:checked { 8 | border: 2px solid #ffffff; 9 | color: #ffffff; 10 | } 11 | 12 | QToolButton { 13 | background: none; 14 | border: 2px solid rgba(0, 0, 0, 0); 15 | padding: 0; 16 | } 17 | 18 | QToolButton:hover, QToolButton:checked { 19 | border: 2px solid #ffffff; 20 | color: #ffffff; 21 | padding: 0; 22 | } 23 | 24 | QToolButton:pressed { 25 | border: 2px solid #ffffff; 26 | color: #ffffff; 27 | padding: 6px; 28 | } 29 | 30 | QLabel { 31 | font-weight: bold; 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MayaWindowSwitcher 2 | ![window_switcher](https://user-images.githubusercontent.com/1896961/72981168-7cfbb380-3e1f-11ea-9173-1cfc3f3d5d06.gif) 3 | 4 | Switch between open windows on Maya(like Alt+Tab in windows) 5 | 6 | # Installation 7 | ## Hotkey 8 | Drag and drop the `install_hotkey.mel` file into the Maya viewport. 9 | 10 | Press `Ctrl+Shift+T` to launch the tool. 11 | 12 | 13 | ## Shelf 14 | Drag and drop the install_shelf.mel file into the Maya viewport. 15 | 16 | ## Option 17 | Change to simple mode.(disable create thumbnail previews) 18 | ```python 19 | from window_switcher import settings 20 | settings.enable_simple_mode() 21 | ``` 22 | 23 | Return to normal mode. 24 | ```python 25 | from window_switcher import settings 26 | settings.disable_simple_mode() 27 | ``` 28 | 29 | # License 30 | [MIT](https://en.wikipedia.org/wiki/MIT_License) 31 | 32 | # Author 33 | tm8r (https://github.com/tm8r) -------------------------------------------------------------------------------- /scripts/window_switcher/window_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from window_switcher.vendor.Qt import QtWidgets 5 | 6 | _MAYA_MAIN_WINDOW_NAME = "MayaWindow" 7 | 8 | 9 | def collect_switchable_windows(parent=None): 10 | switchable_windows = [] 11 | active_window = None 12 | for w in QtWidgets.QApplication.topLevelWidgets(): 13 | if not w.isVisible(): 14 | continue 15 | if parent and w == parent: 16 | continue 17 | if not w.children(): 18 | continue 19 | if w.isActiveWindow(): 20 | active_window = w 21 | continue 22 | 23 | if not w.windowTitle() and w.height() < 20: 24 | # workaround for Maya2019 outliner bug. 25 | continue 26 | 27 | switchable_windows.append(w) 28 | 29 | if active_window: 30 | switchable_windows.insert(0, active_window) 31 | return switchable_windows 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mihara Tomonari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/window_switcher/libs/qt/stylesheet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """manage style sheet""" 3 | from __future__ import absolute_import, division, print_function 4 | 5 | import os 6 | 7 | _RESOURCES_DIRECTORY = os.path.join(os.path.dirname(__file__), "resources") 8 | 9 | 10 | class StyleSheet(object): 11 | """manage style sheet""" 12 | 13 | _CSS_DICT = {} 14 | 15 | def __init__(self): 16 | """initialize""" 17 | self._core_css = _read_text(_RESOURCES_DIRECTORY + "/core.css") 18 | 19 | @property 20 | def core_css(self): 21 | """get core css 22 | 23 | Returns: 24 | str: core css 25 | """ 26 | return self._core_css 27 | 28 | def get_css(self, path): 29 | """get merged css(core css and unique css) 30 | 31 | Args: 32 | path (str): css path 33 | 34 | Returns: 35 | unicode: result css 36 | """ 37 | if path not in self._CSS_DICT: 38 | self._CSS_DICT[path] = _read_text(path) 39 | return self._core_css + self._CSS_DICT[path] 40 | 41 | def reload(self): 42 | """reload CSS(for develop)""" 43 | self._core_css = _read_text(_RESOURCES_DIRECTORY + "/core.css") 44 | self._CSS_DICT = {} 45 | 46 | 47 | def _read_text(path): 48 | res = "" 49 | if not os.path.isfile(path): 50 | return res 51 | with open(path, "r") as f: 52 | res = f.read() 53 | return res 54 | -------------------------------------------------------------------------------- /install_shelf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | 6 | from maya import cmds 7 | from maya import mel 8 | 9 | 10 | def onMayaDroppedPythonFile(*args, **kwargs): 11 | """for Maya2017.3+""" 12 | _create_shelf() 13 | 14 | 15 | def onMayaDroppedMelFile(): 16 | "for old Maya" 17 | _create_shelf() 18 | 19 | 20 | def _create_shelf(): 21 | script_path = os.path.dirname(__file__) + "/scripts" 22 | 23 | command = """ 24 | # ------------------------- 25 | # MayaWindowSwitcher 26 | # Author: @tm8r 27 | # https://github.com/tm8r/MayaWindowSwitcher 28 | # ------------------------- 29 | 30 | import os 31 | import sys 32 | 33 | from maya import cmds 34 | 35 | def switch_window(): 36 | script_path = "{0}" 37 | if not os.path.exists(script_path): 38 | cmds.error("WindowSwitcher install directory is not found. path={0}") 39 | return 40 | if script_path not in sys.path: 41 | sys.path.insert(0, script_path) 42 | 43 | import window_switcher.view 44 | window_switcher.view.WindowSwitcher.switch() 45 | 46 | switch_window()""".format(script_path) 47 | 48 | shelf = mel.eval("$gShelfTopLevel=$gShelfTopLevel") 49 | parent = cmds.tabLayout(shelf, query=True, selectTab=True) 50 | cmds.shelfButton( 51 | command=command, 52 | image="pythonFamily.png", 53 | annotation="WindowSwitcher", 54 | label="WindowSwitcher", 55 | imageOverlayLabel="WindowSwitcher", 56 | sourceType="Python", 57 | parent=parent 58 | ) 59 | 60 | 61 | if __name__ == "_installShelfTm8rWindowSwitcher": 62 | onMayaDroppedMelFile() 63 | -------------------------------------------------------------------------------- /install_hotkey.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | 6 | from maya import cmds 7 | from maya import mel 8 | 9 | 10 | def onMayaDroppedPythonFile(*args, **kwargs): 11 | """for Maya2017.3+""" 12 | _register_hotkey() 13 | 14 | 15 | def onMayaDroppedMelFile(): 16 | "for old Maya" 17 | _register_hotkey() 18 | 19 | 20 | def _register_hotkey(): 21 | script_path = os.path.dirname(__file__) + "/scripts" 22 | 23 | command = """ 24 | # ------------------------- 25 | # MayaWindowSwitcher 26 | # Author: @tm8r 27 | # https://github.com/tm8r/MayaWindowSwitcher 28 | # ------------------------- 29 | 30 | import os 31 | import sys 32 | 33 | from maya import cmds 34 | 35 | def switch_window(): 36 | script_path = "{0}" 37 | if not os.path.exists(script_path): 38 | cmds.error("WindowSwitcher install directory is not found. path={0}") 39 | return 40 | if script_path not in sys.path: 41 | sys.path.insert(0, script_path) 42 | 43 | import window_switcher.view 44 | window_switcher.view.WindowSwitcher.switch() 45 | 46 | switch_window()""".format(script_path) 47 | 48 | command_name = "WindowSwitcher" 49 | if cmds.runTimeCommand(command_name, ex=True): 50 | cmds.runTimeCommand(command_name, e=True, delete=True) 51 | cmds.runTimeCommand( 52 | command_name, 53 | ann="Open WindowSwitcher", 54 | category="User", 55 | command=command, 56 | commandLanguage="python" 57 | ) 58 | named_command = command_name + "Command" 59 | named_command = cmds.nameCommand( 60 | named_command, 61 | annotation="Open WindowSwitcher", 62 | command=command_name, 63 | ) 64 | if int(cmds.about(v=True)) <= 2015: 65 | cmds.hotkey(k="T", ctl=True, n=named_command) 66 | return 67 | 68 | cmds.hotkey(k="t", ctl=True, sht=True, n=named_command) 69 | 70 | 71 | if __name__ == "_installHotkeyTm8rWindowSwitcher": 72 | onMayaDroppedMelFile() 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # idea 132 | .idea/ -------------------------------------------------------------------------------- /scripts/window_switcher/view.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | from window_switcher.vendor.Qt import QtCompat 6 | from window_switcher.vendor.Qt import QtCore 7 | from window_switcher.vendor.Qt import QtGui 8 | from window_switcher.vendor.Qt import QtWidgets 9 | 10 | from window_switcher.libs.qt.layout import clear_layout 11 | from window_switcher.libs.qt.stylesheet import StyleSheet 12 | from window_switcher import settings 13 | from window_switcher import window_helper 14 | 15 | from maya import OpenMayaUI as omui 16 | 17 | try: 18 | long 19 | except NameError: 20 | long = int 21 | 22 | try: 23 | MAYA_WINDOW = QtCompat.wrapInstance(long(omui.MQtUtil.mainWindow()), QtWidgets.QWidget) 24 | except: 25 | MAYA_WINDOW = None 26 | 27 | _RESOURCES_DIRECTORY = os.path.join(os.path.dirname(__file__), "resources") 28 | 29 | _NO_IMAGE_PIXMAP = QtGui.QPixmap(_RESOURCES_DIRECTORY + "/no_image.png") 30 | 31 | _WINDOW_HORIZONTAL_SIZE = 4 32 | 33 | _WINDOW_ELEMENT_WIDTH = 160 34 | _WINDOW_BUTTON_HEIGHT = 140 35 | 36 | 37 | class WindowSwitcher(QtWidgets.QDialog): 38 | _INSTANCE = None # type: WindowSwitcher 39 | _ACTIVE = False 40 | 41 | def __init__(self, parent=MAYA_WINDOW): 42 | u"""initialize""" 43 | super(WindowSwitcher, self).__init__(parent=parent) 44 | self._windows = [] 45 | self._buttons = [] 46 | self._current_index = 0 47 | 48 | self.setWindowTitle("WindowSwitcher") 49 | self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint) 50 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) 51 | self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True) 52 | self.setObjectName("WindowSwitcher") 53 | self.setStyleSheet(StyleSheet().get_css(_RESOURCES_DIRECTORY + "/window_switcher.css")) 54 | 55 | self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 56 | self.setMinimumSize(QtCore.QSize(480, 200)) 57 | 58 | root_layout = QtWidgets.QGridLayout() 59 | root_layout.setContentsMargins(20, 20, 20, 20) 60 | self.setLayout(root_layout) 61 | 62 | self.main_layout = QtWidgets.QVBoxLayout() 63 | root_layout.addLayout(self.main_layout, 0, 0, 1, 1) 64 | 65 | self._refresh() 66 | 67 | @classmethod 68 | def open(cls, *args): 69 | u"""UIを表示""" 70 | win = cls._INSTANCE 71 | if not win: 72 | win = cls() 73 | cls._INSTANCE = win 74 | cls._ACTIVE = True 75 | win.show() 76 | win.activateWindow() 77 | 78 | @classmethod 79 | def switch(cls): 80 | if cls._INSTANCE and cls._ACTIVE: 81 | win = cls._INSTANCE 82 | win._switch_selection() 83 | return 84 | if cls._INSTANCE and not cls._ACTIVE: 85 | cls._ACTIVE = True 86 | win = cls._INSTANCE 87 | win._refresh() 88 | win.show() 89 | win.activateWindow() 90 | return 91 | cls.open() 92 | 93 | def _change_index(self, index): 94 | self._current_index = index 95 | self.close() 96 | 97 | def _refresh(self): 98 | clear_layout(self.main_layout) 99 | self._current_index = 0 100 | self._buttons = [] 101 | self._windows = window_helper.collect_switchable_windows(self) 102 | if not self._windows: 103 | warn_label = QtWidgets.QLabel("No switchable windows.") 104 | warn_label.setAlignment(QtCore.Qt.AlignCenter) 105 | self.main_layout.addWidget(warn_label) 106 | return 107 | layout = None 108 | for index, w in enumerate(self._windows): 109 | if index == 0 or index % _WINDOW_HORIZONTAL_SIZE == 0: 110 | layout = QtWidgets.QHBoxLayout() 111 | self.main_layout.addLayout(layout) 112 | icon_layout = QtWidgets.QVBoxLayout() 113 | layout.addLayout(icon_layout) 114 | button = QtWidgets.QToolButton() 115 | button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 116 | button.customContextMenuRequested.connect( 117 | lambda x, y=button, z=w: self._on_window_button_context_menu_requested(x, y, z)) 118 | button.setIconSize(QtCore.QSize(_WINDOW_ELEMENT_WIDTH, _WINDOW_BUTTON_HEIGHT)) 119 | icon = QtGui.QIcon() 120 | 121 | if settings.is_simple_mode(): 122 | icon.addPixmap(_NO_IMAGE_PIXMAP) 123 | else: 124 | screen = QtWidgets.QApplication.primaryScreen() 125 | if not screen: 126 | return 127 | win_id = int(w.winId()) 128 | window_pixmap = screen.grabWindow(win_id) 129 | icon.addPixmap( 130 | window_pixmap, 131 | QtGui.QIcon.Mode.Normal, 132 | QtGui.QIcon.State.Off 133 | ) 134 | 135 | button.setIcon(icon) 136 | button.setCheckable(True) 137 | button.setFixedWidth(_WINDOW_ELEMENT_WIDTH) 138 | button.setFixedHeight(_WINDOW_BUTTON_HEIGHT) 139 | button.clicked.connect(lambda x=index: self._change_index(x)) 140 | icon_layout.addWidget(button) 141 | label = QtWidgets.QLabel(w.windowTitle()) 142 | label.setMaximumWidth(_WINDOW_ELEMENT_WIDTH) 143 | label.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) 144 | icon_layout.addWidget(label) 145 | self._buttons.append(button) 146 | 147 | self._buttons[0].setChecked(True) 148 | 149 | def _on_window_button_context_menu_requested(self, point, button, window): 150 | menu = QtWidgets.QMenu(button) 151 | move_action = QtWidgets.QAction("Reset Position", button) 152 | main_window_center = MAYA_WINDOW.rect().center() 153 | left = main_window_center.x() - window.width() / 2 154 | top = main_window_center.y() - window.height() / 2 155 | 156 | move_action.triggered.connect(lambda x=window, y=left, z=top: x.move(y, z)) 157 | menu.addAction(move_action) 158 | menu.exec_(button.mapToGlobal(point)) 159 | 160 | def _switch_selection(self): 161 | self._current_index += 1 162 | if self._current_index >= len(self._windows): 163 | self._current_index = 0 164 | for index, b in enumerate(self._buttons): 165 | if index == self._current_index: 166 | b.setChecked(True) 167 | continue 168 | b.setChecked(False) 169 | 170 | # region event override 171 | # override 172 | def paintEvent(self, event): 173 | painter = QtGui.QPainter(self) 174 | painter.fillRect(self.rect(), QtGui.QColor(0, 0, 0, 150)) 175 | 176 | # override 177 | def keyReleaseEvent(self, event): 178 | if event.key() == QtCore.Qt.Key_Escape: 179 | # prevent reject 180 | return 181 | if event.modifiers() != QtCore.Qt.NoModifier: 182 | event.accept() 183 | return 184 | self.close() 185 | 186 | # override 187 | def changeEvent(self, event): 188 | if event.type() == QtCore.QEvent.ActivationChange and not self.isActiveWindow() and self.isVisible(): 189 | self.close() 190 | return 191 | super(WindowSwitcher, self).changeEvent(event) 192 | 193 | # override 194 | def closeEvent(self, event): 195 | super(WindowSwitcher, self).closeEvent(event) 196 | WindowSwitcher._ACTIVE = False 197 | if not self._windows: 198 | return 199 | window = self._windows[self._current_index] 200 | if window.isMinimized(): 201 | window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized) 202 | window.activateWindow() 203 | 204 | # endregion event override 205 | -------------------------------------------------------------------------------- /scripts/window_switcher/vendor/Qt.py: -------------------------------------------------------------------------------- 1 | """Minimal Python 2 & 3 shim around all Qt bindings 2 | 3 | DOCUMENTATION 4 | Qt.py was born in the film and visual effects industry to address 5 | the growing need for the development of software capable of running 6 | with more than one flavour of the Qt bindings for Python. 7 | 8 | Supported Binding: PySide, PySide2, PySide6, PyQt4, PyQt5 9 | 10 | 1. Build for one, run with all 11 | 2. Explicit is better than implicit 12 | 3. Support co-existence 13 | 14 | Default resolution order: 15 | - PySide6 16 | - PySide2 17 | - PyQt5 18 | - PySide 19 | - PyQt4 20 | 21 | Usage: 22 | >> import sys 23 | >> from Qt import QtWidgets 24 | >> app = QtWidgets.QApplication(sys.argv) 25 | >> button = QtWidgets.QPushButton("Hello World") 26 | >> button.show() 27 | >> app.exec_() 28 | 29 | All members of PySide2 are mapped from other bindings, should they exist. 30 | If no equivalent member exist, it is excluded from Qt.py and inaccessible. 31 | The idea is to highlight members that exist across all supported binding, 32 | and guarantee that code that runs on one binding runs on all others. 33 | 34 | For more details, visit https://github.com/mottosso/Qt.py 35 | 36 | LICENSE 37 | 38 | See end of file for license (MIT, BSD) information. 39 | 40 | """ 41 | 42 | import os 43 | import sys 44 | import types 45 | import shutil 46 | import importlib 47 | import json 48 | 49 | 50 | __version__ = "1.4.1" 51 | 52 | # Enable support for `from Qt import *` 53 | __all__ = [] 54 | 55 | # Flags from environment variables 56 | QT_VERBOSE = bool(os.getenv("QT_VERBOSE")) 57 | QT_PREFERRED_BINDING_JSON = os.getenv("QT_PREFERRED_BINDING_JSON", "") 58 | QT_PREFERRED_BINDING = os.getenv("QT_PREFERRED_BINDING", "") 59 | QT_SIP_API_HINT = os.getenv("QT_SIP_API_HINT") 60 | 61 | # Reference to Qt.py 62 | Qt = sys.modules[__name__] 63 | Qt.QtCompat = types.ModuleType("QtCompat") 64 | 65 | try: 66 | long 67 | except NameError: 68 | # Python 3 compatibility 69 | long = int 70 | 71 | 72 | """Common members of all bindings 73 | 74 | This is where each member of Qt.py is explicitly defined. 75 | It is based on a "lowest common denominator" of all bindings; 76 | including members found in each of the 4 bindings. 77 | 78 | The "_common_members" dictionary is generated using the 79 | build_membership.sh script. 80 | 81 | """ 82 | 83 | _common_members = { 84 | "QtCore": [ 85 | "QAbstractAnimation", 86 | "QAbstractEventDispatcher", 87 | "QAbstractItemModel", 88 | "QAbstractListModel", 89 | "QAbstractTableModel", 90 | "QAnimationGroup", 91 | "QBasicTimer", 92 | "QBitArray", 93 | "QBuffer", 94 | "QByteArray", 95 | "QByteArrayMatcher", 96 | "QChildEvent", 97 | "QCoreApplication", 98 | "QCryptographicHash", 99 | "QDataStream", 100 | "QDate", 101 | "QDateTime", 102 | "QDir", 103 | "QDirIterator", 104 | "QDynamicPropertyChangeEvent", 105 | "QEasingCurve", 106 | "QElapsedTimer", 107 | "QEvent", 108 | "QEventLoop", 109 | "QFile", 110 | "QFileInfo", 111 | "QFileSystemWatcher", 112 | "QGenericArgument", 113 | "QGenericReturnArgument", 114 | "QItemSelection", 115 | "QItemSelectionRange", 116 | "QIODevice", 117 | "QLibraryInfo", 118 | "QLine", 119 | "QLineF", 120 | "QLocale", 121 | "QMargins", 122 | "QMetaClassInfo", 123 | "QMetaEnum", 124 | "QMetaMethod", 125 | "QMetaObject", 126 | "QMetaProperty", 127 | "QMimeData", 128 | "QModelIndex", 129 | "QMutex", 130 | "QMutexLocker", 131 | "QObject", 132 | "QParallelAnimationGroup", 133 | "QPauseAnimation", 134 | "QPersistentModelIndex", 135 | "QPluginLoader", 136 | "QPoint", 137 | "QPointF", 138 | "QProcess", 139 | "QProcessEnvironment", 140 | "QPropertyAnimation", 141 | "QReadLocker", 142 | "QReadWriteLock", 143 | "QRect", 144 | "QRectF", 145 | "QResource", 146 | "QRunnable", 147 | "QSemaphore", 148 | "QSequentialAnimationGroup", 149 | "QSettings", 150 | "QSignalMapper", 151 | "QSize", 152 | "QSizeF", 153 | "QSocketNotifier", 154 | "QSysInfo", 155 | "QSystemSemaphore", 156 | "QT_TRANSLATE_NOOP", 157 | "QT_TR_NOOP", 158 | "QT_TR_NOOP_UTF8", 159 | "QTemporaryFile", 160 | "QTextBoundaryFinder", 161 | "QTextStream", 162 | "QTextStreamManipulator", 163 | "QThread", 164 | "QThreadPool", 165 | "QTime", 166 | "QTimeLine", 167 | "QTimer", 168 | "QTimerEvent", 169 | "QTranslator", 170 | "QUrl", 171 | "QVariantAnimation", 172 | "QWaitCondition", 173 | "QWriteLocker", 174 | "QXmlStreamAttribute", 175 | "QXmlStreamAttributes", 176 | "QXmlStreamEntityDeclaration", 177 | "QXmlStreamEntityResolver", 178 | "QXmlStreamNamespaceDeclaration", 179 | "QXmlStreamNotationDeclaration", 180 | "QXmlStreamReader", 181 | "QXmlStreamWriter", 182 | "Qt", 183 | "QtMsgType", 184 | "qAbs", 185 | "qAddPostRoutine", 186 | "qCritical", 187 | "qDebug", 188 | "qFatal", 189 | "qFuzzyCompare", 190 | "qIsFinite", 191 | "qIsInf", 192 | "qIsNaN", 193 | "qIsNull", 194 | "qRegisterResourceData", 195 | "qUnregisterResourceData", 196 | "qVersion", 197 | "qWarning", 198 | ], 199 | "QtGui": [ 200 | "QAbstractTextDocumentLayout", 201 | "QActionEvent", 202 | "QBitmap", 203 | "QBrush", 204 | "QClipboard", 205 | "QCloseEvent", 206 | "QColor", 207 | "QConicalGradient", 208 | "QContextMenuEvent", 209 | "QCursor", 210 | "QDesktopServices", 211 | "QDoubleValidator", 212 | "QDrag", 213 | "QDragEnterEvent", 214 | "QDragLeaveEvent", 215 | "QDragMoveEvent", 216 | "QDropEvent", 217 | "QFileOpenEvent", 218 | "QFocusEvent", 219 | "QFont", 220 | "QFontDatabase", 221 | "QFontInfo", 222 | "QFontMetrics", 223 | "QFontMetricsF", 224 | "QGradient", 225 | "QHelpEvent", 226 | "QHideEvent", 227 | "QHoverEvent", 228 | "QIcon", 229 | "QIconDragEvent", 230 | "QIconEngine", 231 | "QImage", 232 | "QImageIOHandler", 233 | "QImageReader", 234 | "QImageWriter", 235 | "QInputEvent", 236 | "QInputMethodEvent", 237 | "QIntValidator", 238 | "QKeyEvent", 239 | "QKeySequence", 240 | "QLinearGradient", 241 | "QMatrix2x2", 242 | "QMatrix2x3", 243 | "QMatrix2x4", 244 | "QMatrix3x2", 245 | "QMatrix3x3", 246 | "QMatrix3x4", 247 | "QMatrix4x2", 248 | "QMatrix4x3", 249 | "QMatrix4x4", 250 | "QMouseEvent", 251 | "QMoveEvent", 252 | "QMovie", 253 | "QPaintDevice", 254 | "QPaintEngine", 255 | "QPaintEngineState", 256 | "QPaintEvent", 257 | "QPainter", 258 | "QPainterPath", 259 | "QPainterPathStroker", 260 | "QPalette", 261 | "QPen", 262 | "QPicture", 263 | "QPixmap", 264 | "QPixmapCache", 265 | "QPolygon", 266 | "QPolygonF", 267 | "QQuaternion", 268 | "QRadialGradient", 269 | "QRegion", 270 | "QResizeEvent", 271 | "QSessionManager", 272 | "QShortcutEvent", 273 | "QShowEvent", 274 | "QStandardItem", 275 | "QStandardItemModel", 276 | "QStatusTipEvent", 277 | "QSyntaxHighlighter", 278 | "QTabletEvent", 279 | "QTextBlock", 280 | "QTextBlockFormat", 281 | "QTextBlockGroup", 282 | "QTextBlockUserData", 283 | "QTextCharFormat", 284 | "QTextCursor", 285 | "QTextDocument", 286 | "QTextDocumentFragment", 287 | "QTextFormat", 288 | "QTextFragment", 289 | "QTextFrame", 290 | "QTextFrameFormat", 291 | "QTextImageFormat", 292 | "QTextInlineObject", 293 | "QTextItem", 294 | "QTextLayout", 295 | "QTextLength", 296 | "QTextLine", 297 | "QTextList", 298 | "QTextListFormat", 299 | "QTextObject", 300 | "QTextObjectInterface", 301 | "QTextOption", 302 | "QTextTable", 303 | "QTextTableCell", 304 | "QTextTableCellFormat", 305 | "QTextTableFormat", 306 | "QTouchEvent", 307 | "QTransform", 308 | "QValidator", 309 | "QVector2D", 310 | "QVector3D", 311 | "QVector4D", 312 | "QWhatsThisClickedEvent", 313 | "QWheelEvent", 314 | "QWindowStateChangeEvent", 315 | "qAlpha", 316 | "qBlue", 317 | "qGray", 318 | "qGreen", 319 | "qIsGray", 320 | "qRed", 321 | "qRgb", 322 | "qRgba" 323 | ], 324 | "QtHelp": [ 325 | "QHelpContentItem", 326 | "QHelpContentModel", 327 | "QHelpContentWidget", 328 | "QHelpEngine", 329 | "QHelpEngineCore", 330 | "QHelpIndexModel", 331 | "QHelpIndexWidget", 332 | "QHelpSearchEngine", 333 | "QHelpSearchQuery", 334 | "QHelpSearchQueryWidget", 335 | "QHelpSearchResultWidget" 336 | ], 337 | "QtNetwork": [ 338 | "QAbstractNetworkCache", 339 | "QAbstractSocket", 340 | "QAuthenticator", 341 | "QHostAddress", 342 | "QHostInfo", 343 | "QLocalServer", 344 | "QLocalSocket", 345 | "QNetworkAccessManager", 346 | "QNetworkAddressEntry", 347 | "QNetworkCacheMetaData", 348 | "QNetworkCookie", 349 | "QNetworkCookieJar", 350 | "QNetworkDiskCache", 351 | "QNetworkInterface", 352 | "QNetworkProxy", 353 | "QNetworkProxyFactory", 354 | "QNetworkProxyQuery", 355 | "QNetworkReply", 356 | "QNetworkRequest", 357 | "QSsl", 358 | "QTcpServer", 359 | "QTcpSocket", 360 | "QUdpSocket" 361 | ], 362 | "QtPrintSupport": [ 363 | "QAbstractPrintDialog", 364 | "QPageSetupDialog", 365 | "QPrintDialog", 366 | "QPrintEngine", 367 | "QPrintPreviewDialog", 368 | "QPrintPreviewWidget", 369 | "QPrinter", 370 | "QPrinterInfo" 371 | ], 372 | "QtSvg": [ 373 | "QSvgGenerator", 374 | "QSvgRenderer" 375 | ], 376 | "QtTest": [ 377 | "QTest" 378 | ], 379 | "QtWidgets": [ 380 | "QAbstractButton", 381 | "QAbstractGraphicsShapeItem", 382 | "QAbstractItemDelegate", 383 | "QAbstractItemView", 384 | "QAbstractScrollArea", 385 | "QAbstractSlider", 386 | "QAbstractSpinBox", 387 | "QAction", 388 | "QApplication", 389 | "QBoxLayout", 390 | "QButtonGroup", 391 | "QCalendarWidget", 392 | "QCheckBox", 393 | "QColorDialog", 394 | "QColumnView", 395 | "QComboBox", 396 | "QCommandLinkButton", 397 | "QCommonStyle", 398 | "QCompleter", 399 | "QDataWidgetMapper", 400 | "QDateEdit", 401 | "QDateTimeEdit", 402 | "QDial", 403 | "QDialog", 404 | "QDialogButtonBox", 405 | "QDockWidget", 406 | "QDoubleSpinBox", 407 | "QErrorMessage", 408 | "QFileDialog", 409 | "QFileIconProvider", 410 | "QFileSystemModel", 411 | "QFocusFrame", 412 | "QFontComboBox", 413 | "QFontDialog", 414 | "QFormLayout", 415 | "QFrame", 416 | "QGesture", 417 | "QGestureEvent", 418 | "QGestureRecognizer", 419 | "QGraphicsAnchor", 420 | "QGraphicsAnchorLayout", 421 | "QGraphicsBlurEffect", 422 | "QGraphicsColorizeEffect", 423 | "QGraphicsDropShadowEffect", 424 | "QGraphicsEffect", 425 | "QGraphicsEllipseItem", 426 | "QGraphicsGridLayout", 427 | "QGraphicsItem", 428 | "QGraphicsItemGroup", 429 | "QGraphicsLayout", 430 | "QGraphicsLayoutItem", 431 | "QGraphicsLineItem", 432 | "QGraphicsLinearLayout", 433 | "QGraphicsObject", 434 | "QGraphicsOpacityEffect", 435 | "QGraphicsPathItem", 436 | "QGraphicsPixmapItem", 437 | "QGraphicsPolygonItem", 438 | "QGraphicsProxyWidget", 439 | "QGraphicsRectItem", 440 | "QGraphicsRotation", 441 | "QGraphicsScale", 442 | "QGraphicsScene", 443 | "QGraphicsSceneContextMenuEvent", 444 | "QGraphicsSceneDragDropEvent", 445 | "QGraphicsSceneEvent", 446 | "QGraphicsSceneHelpEvent", 447 | "QGraphicsSceneHoverEvent", 448 | "QGraphicsSceneMouseEvent", 449 | "QGraphicsSceneMoveEvent", 450 | "QGraphicsSceneResizeEvent", 451 | "QGraphicsSceneWheelEvent", 452 | "QGraphicsSimpleTextItem", 453 | "QGraphicsTextItem", 454 | "QGraphicsTransform", 455 | "QGraphicsView", 456 | "QGraphicsWidget", 457 | "QGridLayout", 458 | "QGroupBox", 459 | "QHBoxLayout", 460 | "QHeaderView", 461 | "QInputDialog", 462 | "QItemDelegate", 463 | "QItemEditorCreatorBase", 464 | "QItemEditorFactory", 465 | "QLCDNumber", 466 | "QLabel", 467 | "QLayout", 468 | "QLayoutItem", 469 | "QLineEdit", 470 | "QListView", 471 | "QListWidget", 472 | "QListWidgetItem", 473 | "QMainWindow", 474 | "QMdiArea", 475 | "QMdiSubWindow", 476 | "QMenu", 477 | "QMenuBar", 478 | "QMessageBox", 479 | "QPanGesture", 480 | "QPinchGesture", 481 | "QPlainTextDocumentLayout", 482 | "QPlainTextEdit", 483 | "QProgressBar", 484 | "QProgressDialog", 485 | "QPushButton", 486 | "QRadioButton", 487 | "QRubberBand", 488 | "QScrollArea", 489 | "QScrollBar", 490 | "QSizeGrip", 491 | "QSizePolicy", 492 | "QSlider", 493 | "QSpacerItem", 494 | "QSpinBox", 495 | "QSplashScreen", 496 | "QSplitter", 497 | "QSplitterHandle", 498 | "QStackedLayout", 499 | "QStackedWidget", 500 | "QStatusBar", 501 | "QStyle", 502 | "QStyleFactory", 503 | "QStyleHintReturn", 504 | "QStyleHintReturnMask", 505 | "QStyleHintReturnVariant", 506 | "QStyleOption", 507 | "QStyleOptionButton", 508 | "QStyleOptionComboBox", 509 | "QStyleOptionComplex", 510 | "QStyleOptionDockWidget", 511 | "QStyleOptionFocusRect", 512 | "QStyleOptionFrame", 513 | "QStyleOptionGraphicsItem", 514 | "QStyleOptionGroupBox", 515 | "QStyleOptionHeader", 516 | "QStyleOptionMenuItem", 517 | "QStyleOptionProgressBar", 518 | "QStyleOptionRubberBand", 519 | "QStyleOptionSizeGrip", 520 | "QStyleOptionSlider", 521 | "QStyleOptionSpinBox", 522 | "QStyleOptionTab", 523 | "QStyleOptionTabBarBase", 524 | "QStyleOptionTabWidgetFrame", 525 | "QStyleOptionTitleBar", 526 | "QStyleOptionToolBar", 527 | "QStyleOptionToolBox", 528 | "QStyleOptionToolButton", 529 | "QStyleOptionViewItem", 530 | "QStylePainter", 531 | "QStyledItemDelegate", 532 | "QSwipeGesture", 533 | "QSystemTrayIcon", 534 | "QTabBar", 535 | "QTabWidget", 536 | "QTableView", 537 | "QTableWidget", 538 | "QTableWidgetItem", 539 | "QTableWidgetSelectionRange", 540 | "QTapAndHoldGesture", 541 | "QTapGesture", 542 | "QTextBrowser", 543 | "QTextEdit", 544 | "QTimeEdit", 545 | "QToolBar", 546 | "QToolBox", 547 | "QToolButton", 548 | "QToolTip", 549 | "QTreeView", 550 | "QTreeWidget", 551 | "QTreeWidgetItem", 552 | "QTreeWidgetItemIterator", 553 | "QUndoView", 554 | "QVBoxLayout", 555 | "QWhatsThis", 556 | "QWidget", 557 | "QWidgetAction", 558 | "QWidgetItem", 559 | "QWizard", 560 | "QWizardPage" 561 | ], 562 | "QtXml": [ 563 | "QDomAttr", 564 | "QDomCDATASection", 565 | "QDomCharacterData", 566 | "QDomComment", 567 | "QDomDocument", 568 | "QDomDocumentFragment", 569 | "QDomDocumentType", 570 | "QDomElement", 571 | "QDomEntity", 572 | "QDomEntityReference", 573 | "QDomImplementation", 574 | "QDomNamedNodeMap", 575 | "QDomNode", 576 | "QDomNodeList", 577 | "QDomNotation", 578 | "QDomProcessingInstruction", 579 | "QDomText" 580 | ] 581 | } 582 | 583 | """ Missing members 584 | 585 | This mapping describes members that have been deprecated 586 | in one or more bindings and have been left out of the 587 | _common_members mapping. 588 | 589 | The member can provide an extra details string to be 590 | included in exceptions and warnings. 591 | """ 592 | 593 | _missing_members = { 594 | "QtGui": { 595 | "QMatrix": "Deprecated in PyQt5", 596 | }, 597 | } 598 | 599 | 600 | def _qInstallMessageHandler(handler): 601 | """Install a message handler that works in all bindings 602 | 603 | Args: 604 | handler: A function that takes 3 arguments, or None 605 | """ 606 | def messageOutputHandler(*args): 607 | # In Qt4 bindings, message handlers are passed 2 arguments 608 | # In Qt5 bindings, message handlers are passed 3 arguments 609 | # The first argument is a QtMsgType 610 | # The last argument is the message to be printed 611 | # The Middle argument (if passed) is a QMessageLogContext 612 | if len(args) == 3: 613 | msgType, logContext, msg = args 614 | elif len(args) == 2: 615 | msgType, msg = args 616 | logContext = None 617 | else: 618 | raise TypeError( 619 | "handler expected 2 or 3 arguments, got {0}".format(len(args))) 620 | 621 | if isinstance(msg, bytes): 622 | # In python 3, some bindings pass a bytestring, which cannot be 623 | # used elsewhere. Decoding a python 2 or 3 bytestring object will 624 | # consistently return a unicode object. 625 | msg = msg.decode() 626 | 627 | handler(msgType, logContext, msg) 628 | 629 | passObject = messageOutputHandler if handler else handler 630 | if Qt.IsPySide or Qt.IsPyQt4: 631 | return Qt._QtCore.qInstallMsgHandler(passObject) 632 | elif Qt.IsPySide2 or Qt.IsPyQt5 or Qt.IsPySide6: 633 | return Qt._QtCore.qInstallMessageHandler(passObject) 634 | 635 | 636 | def _getcpppointer(object): 637 | if hasattr(Qt, "_shiboken6"): 638 | return getattr(Qt, "_shiboken6").getCppPointer(object)[0] 639 | elif hasattr(Qt, "_shiboken2"): 640 | return getattr(Qt, "_shiboken2").getCppPointer(object)[0] 641 | elif hasattr(Qt, "_shiboken"): 642 | return getattr(Qt, "_shiboken").getCppPointer(object)[0] 643 | elif hasattr(Qt, "_sip"): 644 | return getattr(Qt, "_sip").unwrapinstance(object) 645 | raise AttributeError("'module' has no attribute 'getCppPointer'") 646 | 647 | 648 | def _wrapinstance(ptr, base=None): 649 | """Enable implicit cast of pointer to most suitable class 650 | 651 | This behaviour is available in sip per default. 652 | 653 | Based on http://nathanhorne.com/pyqtpyside-wrap-instance 654 | 655 | Usage: 656 | This mechanism kicks in under these circumstances. 657 | 1. Qt.py is using PySide 1 or 2. 658 | 2. A `base` argument is not provided. 659 | 660 | See :func:`QtCompat.wrapInstance()` 661 | 662 | Arguments: 663 | ptr (long): Pointer to QObject in memory 664 | base (QObject, optional): Base class to wrap with. Defaults to QObject, 665 | which should handle anything. 666 | 667 | """ 668 | 669 | assert isinstance(ptr, long), "Argument 'ptr' must be of type " 670 | assert (base is None) or issubclass(base, Qt.QtCore.QObject), ( 671 | "Argument 'base' must be of type ") 672 | 673 | if Qt.IsPyQt4 or Qt.IsPyQt5: 674 | func = getattr(Qt, "_sip").wrapinstance 675 | elif Qt.IsPySide2: 676 | func = getattr(Qt, "_shiboken2").wrapInstance 677 | elif Qt.IsPySide6: 678 | func = getattr(Qt, "_shiboken6").wrapInstance 679 | elif Qt.IsPySide: 680 | func = getattr(Qt, "_shiboken").wrapInstance 681 | else: 682 | raise AttributeError("'module' has no attribute 'wrapInstance'") 683 | 684 | if base is None: 685 | if Qt.IsPyQt4 or Qt.IsPyQt5: 686 | base = Qt.QtCore.QObject 687 | else: 688 | q_object = func(long(ptr), Qt.QtCore.QObject) 689 | meta_object = q_object.metaObject() 690 | 691 | while True: 692 | class_name = meta_object.className() 693 | 694 | try: 695 | base = getattr(Qt.QtWidgets, class_name) 696 | except AttributeError: 697 | try: 698 | base = getattr(Qt.QtCore, class_name) 699 | except AttributeError: 700 | meta_object = meta_object.superClass() 701 | continue 702 | 703 | break 704 | 705 | return func(long(ptr), base) 706 | 707 | 708 | def _isvalid(object): 709 | """Check if the object is valid to use in Python runtime. 710 | 711 | Usage: 712 | See :func:`QtCompat.isValid()` 713 | 714 | Arguments: 715 | object (QObject): QObject to check the validity of. 716 | 717 | """ 718 | if hasattr(Qt, "_shiboken6"): 719 | return getattr(Qt, "_shiboken6").isValid(object) 720 | 721 | elif hasattr(Qt, "_shiboken2"): 722 | return getattr(Qt, "_shiboken2").isValid(object) 723 | 724 | elif hasattr(Qt, "_shiboken"): 725 | return getattr(Qt, "_shiboken").isValid(object) 726 | 727 | elif hasattr(Qt, "_sip"): 728 | return not getattr(Qt, "_sip").isdeleted(object) 729 | 730 | else: 731 | raise AttributeError("'module' has no attribute isValid") 732 | 733 | 734 | def _translate(context, sourceText, *args): 735 | # In Qt4 bindings, translate can be passed 2 or 3 arguments 736 | # In Qt5 bindings, translate can be passed 2 arguments 737 | # The first argument is disambiguation[str] 738 | # The last argument is n[int] 739 | # The middle argument can be encoding[QtCore.QCoreApplication.Encoding] 740 | try: 741 | app = Qt.QtCore.QCoreApplication 742 | except AttributeError: 743 | raise NotImplementedError( 744 | "Missing QCoreApplication implementation for {}".format( 745 | Qt.__binding__ 746 | ) 747 | ) 748 | 749 | def get_arg(index): 750 | try: 751 | return args[index] 752 | except IndexError: 753 | pass 754 | 755 | n = -1 756 | encoding = None 757 | 758 | if len(args) == 3: 759 | disambiguation, encoding, n = args 760 | else: 761 | disambiguation = get_arg(0) 762 | n_or_encoding = get_arg(1) 763 | 764 | if isinstance(n_or_encoding, int): 765 | n = n_or_encoding 766 | else: 767 | encoding = n_or_encoding 768 | 769 | if Qt.__binding__ in ("PySide2", "PySide6","PyQt5"): 770 | sanitized_args = [context, sourceText, disambiguation, n] 771 | else: 772 | sanitized_args = [ 773 | context, 774 | sourceText, 775 | disambiguation, 776 | encoding or app.CodecForTr, 777 | n, 778 | ] 779 | 780 | return app.translate(*sanitized_args) 781 | 782 | 783 | def _loadUi(uifile, baseinstance=None): 784 | """Dynamically load a user interface from the given `uifile` 785 | 786 | This function calls `uic.loadUi` if using PyQt bindings, 787 | else it implements a comparable binding for PySide. 788 | 789 | Documentation: 790 | http://pyqt.sourceforge.net/Docs/PyQt5/designer.html#PyQt5.uic.loadUi 791 | 792 | Arguments: 793 | uifile (str): Absolute path to Qt Designer file. 794 | baseinstance (QWidget): Instantiated QWidget or subclass thereof 795 | 796 | Return: 797 | baseinstance if `baseinstance` is not `None`. Otherwise 798 | return the newly created instance of the user interface. 799 | 800 | """ 801 | if hasattr(Qt, "_uic"): 802 | return Qt._uic.loadUi(uifile, baseinstance) 803 | 804 | elif hasattr(Qt, "_QtUiTools"): 805 | # Implement `PyQt5.uic.loadUi` for PySide(2) 806 | 807 | class _UiLoader(Qt._QtUiTools.QUiLoader): 808 | """Create the user interface in a base instance. 809 | 810 | Unlike `Qt._QtUiTools.QUiLoader` itself this class does not 811 | create a new instance of the top-level widget, but creates the user 812 | interface in an existing instance of the top-level class if needed. 813 | 814 | This mimics the behaviour of `PyQt5.uic.loadUi`. 815 | 816 | """ 817 | 818 | def __init__(self, baseinstance): 819 | super(_UiLoader, self).__init__(baseinstance) 820 | self.baseinstance = baseinstance 821 | self.custom_widgets = {} 822 | 823 | def _loadCustomWidgets(self, etree): 824 | """ 825 | Workaround to pyside-77 bug. 826 | 827 | From QUiLoader doc we should use registerCustomWidget method. 828 | But this causes a segfault on some platforms. 829 | 830 | Instead we fetch from customwidgets DOM node the python class 831 | objects. Then we can directly use them in createWidget method. 832 | """ 833 | 834 | def headerToModule(header): 835 | """ 836 | Translate a header file to python module path 837 | foo/bar.h => foo.bar 838 | """ 839 | # Remove header extension 840 | module = os.path.splitext(header)[0] 841 | 842 | # Replace os separator by python module separator 843 | return module.replace("/", ".").replace("\\", ".") 844 | 845 | custom_widgets = etree.find("customwidgets") 846 | 847 | if custom_widgets is None: 848 | return 849 | 850 | for custom_widget in custom_widgets: 851 | class_name = custom_widget.find("class").text 852 | header = custom_widget.find("header").text 853 | 854 | try: 855 | # try to import the module using the header as defined by the user 856 | module = importlib.import_module(header) 857 | except ImportError: 858 | # try again, but use the customized conversion of a path to a module 859 | module = importlib.import_module(headerToModule(header)) 860 | 861 | self.custom_widgets[class_name] = getattr(module, 862 | class_name) 863 | 864 | def load(self, uifile, *args, **kwargs): 865 | from xml.etree.ElementTree import ElementTree 866 | 867 | # For whatever reason, if this doesn't happen then 868 | # reading an invalid or non-existing .ui file throws 869 | # a RuntimeError. 870 | etree = ElementTree() 871 | etree.parse(uifile) 872 | self._loadCustomWidgets(etree) 873 | 874 | widget = Qt._QtUiTools.QUiLoader.load( 875 | self, uifile, *args, **kwargs) 876 | 877 | # Workaround for PySide 1.0.9, see issue #208 878 | widget.parentWidget() 879 | 880 | return widget 881 | 882 | def createWidget(self, class_name, parent=None, name=""): 883 | """Called for each widget defined in ui file 884 | 885 | Overridden here to populate `baseinstance` instead. 886 | 887 | """ 888 | 889 | if parent is None and self.baseinstance: 890 | # Supposed to create the top-level widget, 891 | # return the base instance instead 892 | return self.baseinstance 893 | 894 | # For some reason, Line is not in the list of available 895 | # widgets, but works fine, so we have to special case it here. 896 | if class_name in self.availableWidgets() + ["Line"]: 897 | # Create a new widget for child widgets 898 | widget = Qt._QtUiTools.QUiLoader.createWidget(self, 899 | class_name, 900 | parent, 901 | name) 902 | elif class_name in self.custom_widgets: 903 | widget = self.custom_widgets[class_name](parent=parent) 904 | else: 905 | raise Exception("Custom widget '%s' not supported" 906 | % class_name) 907 | 908 | if self.baseinstance: 909 | # Set an attribute for the new child widget on the base 910 | # instance, just like PyQt5.uic.loadUi does. 911 | setattr(self.baseinstance, name, widget) 912 | 913 | return widget 914 | 915 | widget = _UiLoader(baseinstance).load(uifile) 916 | Qt.QtCore.QMetaObject.connectSlotsByName(widget) 917 | 918 | return widget 919 | 920 | else: 921 | raise NotImplementedError("No implementation available for loadUi") 922 | 923 | 924 | """Misplaced members 925 | 926 | These members from the original submodule are misplaced relative PySide2 927 | 928 | NOTE: For bindings where a member is not replaced, they still 929 | need to be added such that they are added to Qt.py 930 | 931 | """ 932 | _misplaced_members = { 933 | "PySide6": { 934 | "QtGui.QUndoCommand": "QtWidgets.QUndoCommand", 935 | "QtGui.QUndoGroup": "QtWidgets.QUndoGroup", 936 | "QtGui.QUndoStack": "QtWidgets.QUndoStack", 937 | "QtGui.QActionGroup": "QtWidgets.QActionGroup", 938 | "QtCore.QStringListModel": "QtCore.QStringListModel", 939 | "QtGui.QStringListModel": "QtCore.QStringListModel", 940 | "QtCore.Property": "QtCore.Property", 941 | "QtCore.Signal": "QtCore.Signal", 942 | "QtCore.Slot": "QtCore.Slot", 943 | "QtCore.QAbstractProxyModel": "QtCore.QAbstractProxyModel", 944 | "QtCore.QSortFilterProxyModel": "QtCore.QSortFilterProxyModel", 945 | "QtCore.QItemSelection": "QtCore.QItemSelection", 946 | "QtCore.QItemSelectionModel": "QtCore.QItemSelectionModel", 947 | "QtCore.QItemSelectionRange": "QtCore.QItemSelectionRange", 948 | "QtCore.QRegularExpression": "QtCore.QRegExp", 949 | "QtStateMachine.QStateMachine": "QtCore.QStateMachine", 950 | "QtStateMachine.QState": "QtCore.QState", 951 | "QtGui.QRegularExpressionValidator": "QtGui.QRegExpValidator", 952 | "QtGui.QShortcut": "QtWidgets.QShortcut", 953 | "QtGui.QAction": "QtWidgets.QAction", 954 | "QtUiTools.QUiLoader": ["QtCompat.loadUi", _loadUi], 955 | "shiboken6.wrapInstance": ["QtCompat.wrapInstance", _wrapinstance], 956 | "shiboken6.getCppPointer": ["QtCompat.getCppPointer", _getcpppointer], 957 | "shiboken6.isValid": ["QtCompat.isValid", _isvalid], 958 | "QtWidgets.qApp": "QtWidgets.QApplication.instance()", 959 | "QtCore.QCoreApplication.translate": [ 960 | "QtCompat.translate", _translate 961 | ], 962 | "QtWidgets.QApplication.translate": [ 963 | "QtCompat.translate", _translate 964 | ], 965 | "QtCore.qInstallMessageHandler": [ 966 | "QtCompat.qInstallMessageHandler", _qInstallMessageHandler 967 | ], 968 | "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", 969 | }, 970 | "PySide2": { 971 | "QtWidgets.QUndoCommand": "QtWidgets.QUndoCommand", 972 | "QtWidgets.QUndoGroup": "QtWidgets.QUndoGroup", 973 | "QtWidgets.QUndoStack": "QtWidgets.QUndoStack", 974 | "QtWidgets.QActionGroup": "QtWidgets.QActionGroup", 975 | "QtCore.QStringListModel": "QtCore.QStringListModel", 976 | 977 | # Older versions of PySide2 still left this in QtGui, this accounts for those too 978 | "QtGui.QStringListModel": "QtCore.QStringListModel", 979 | 980 | "QtCore.Property": "QtCore.Property", 981 | "QtCore.Signal": "QtCore.Signal", 982 | "QtCore.Slot": "QtCore.Slot", 983 | "QtCore.QRegExp": "QtCore.QRegExp", 984 | "QtWidgets.QShortcut": "QtWidgets.QShortcut", 985 | "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", 986 | "QtCore.QAbstractProxyModel": "QtCore.QAbstractProxyModel", 987 | "QtCore.QSortFilterProxyModel": "QtCore.QSortFilterProxyModel", 988 | "QtCore.QItemSelection": "QtCore.QItemSelection", 989 | "QtCore.QItemSelectionModel": "QtCore.QItemSelectionModel", 990 | "QtCore.QItemSelectionRange": "QtCore.QItemSelectionRange", 991 | "QtUiTools.QUiLoader": ["QtCompat.loadUi", _loadUi], 992 | "shiboken2.wrapInstance": ["QtCompat.wrapInstance", _wrapinstance], 993 | "shiboken2.getCppPointer": ["QtCompat.getCppPointer", _getcpppointer], 994 | "shiboken2.isValid": ["QtCompat.isValid", _isvalid], 995 | "QtWidgets.qApp": "QtWidgets.QApplication.instance()", 996 | "QtCore.QCoreApplication.translate": [ 997 | "QtCompat.translate", _translate 998 | ], 999 | "QtWidgets.QApplication.translate": [ 1000 | "QtCompat.translate", _translate 1001 | ], 1002 | "QtCore.qInstallMessageHandler": [ 1003 | "QtCompat.qInstallMessageHandler", _qInstallMessageHandler 1004 | ], 1005 | "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", 1006 | }, 1007 | "PyQt5": { 1008 | "QtWidgets.QUndoCommand": "QtWidgets.QUndoCommand", 1009 | "QtWidgets.QUndoGroup": "QtWidgets.QUndoGroup", 1010 | "QtWidgets.QUndoStack": "QtWidgets.QUndoStack", 1011 | "QtWidgets.QActionGroup": "QtWidgets.QActionGroup", 1012 | "QtCore.pyqtProperty": "QtCore.Property", 1013 | "QtCore.pyqtSignal": "QtCore.Signal", 1014 | "QtCore.pyqtSlot": "QtCore.Slot", 1015 | "uic.loadUi": ["QtCompat.loadUi", _loadUi], 1016 | "sip.wrapinstance": ["QtCompat.wrapInstance", _wrapinstance], 1017 | "sip.unwrapinstance": ["QtCompat.getCppPointer", _getcpppointer], 1018 | "sip.isdeleted": ["QtCompat.isValid", _isvalid], 1019 | "QtWidgets.qApp": "QtWidgets.QApplication.instance()", 1020 | "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", 1021 | "QtCore.QRegExp": "QtCore.QRegExp", 1022 | "QtCore.QCoreApplication.translate": [ 1023 | "QtCompat.translate", _translate 1024 | ], 1025 | "QtWidgets.QApplication.translate": [ 1026 | "QtCompat.translate", _translate 1027 | ], 1028 | "QtCore.qInstallMessageHandler": [ 1029 | "QtCompat.qInstallMessageHandler", _qInstallMessageHandler 1030 | ], 1031 | "QtWidgets.QShortcut": "QtWidgets.QShortcut", 1032 | "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", 1033 | }, 1034 | "PySide": { 1035 | "QtGui.QUndoCommand": "QtWidgets.QUndoCommand", 1036 | "QtGui.QUndoGroup": "QtWidgets.QUndoGroup", 1037 | "QtGui.QUndoStack": "QtWidgets.QUndoStack", 1038 | "QtGui.QActionGroup": "QtWidgets.QActionGroup", 1039 | "QtCore.Property": "QtCore.Property", 1040 | "QtCore.Signal": "QtCore.Signal", 1041 | "QtCore.Slot": "QtCore.Slot", 1042 | "QtGui.QAbstractProxyModel": "QtCore.QAbstractProxyModel", 1043 | "QtGui.QSortFilterProxyModel": "QtCore.QSortFilterProxyModel", 1044 | "QtGui.QStringListModel": "QtCore.QStringListModel", 1045 | "QtGui.QItemSelection": "QtCore.QItemSelection", 1046 | "QtGui.QItemSelectionModel": "QtCore.QItemSelectionModel", 1047 | "QtGui.QItemSelectionRange": "QtCore.QItemSelectionRange", 1048 | "QtGui.QAbstractPrintDialog": "QtPrintSupport.QAbstractPrintDialog", 1049 | "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", 1050 | "QtGui.QPageSetupDialog": "QtPrintSupport.QPageSetupDialog", 1051 | "QtGui.QPrintDialog": "QtPrintSupport.QPrintDialog", 1052 | "QtGui.QPrintEngine": "QtPrintSupport.QPrintEngine", 1053 | "QtGui.QPrintPreviewDialog": "QtPrintSupport.QPrintPreviewDialog", 1054 | "QtGui.QPrintPreviewWidget": "QtPrintSupport.QPrintPreviewWidget", 1055 | "QtGui.QPrinter": "QtPrintSupport.QPrinter", 1056 | "QtWidgets.QShortcut": "QtWidgets.QShortcut", 1057 | "QtGui.QPrinterInfo": "QtPrintSupport.QPrinterInfo", 1058 | "QtUiTools.QUiLoader": ["QtCompat.loadUi", _loadUi], 1059 | "shiboken.wrapInstance": ["QtCompat.wrapInstance", _wrapinstance], 1060 | "shiboken.unwrapInstance": ["QtCompat.getCppPointer", _getcpppointer], 1061 | "shiboken.isValid": ["QtCompat.isValid", _isvalid], 1062 | "QtGui.qApp": "QtWidgets.QApplication.instance()", 1063 | "QtCore.QRegExp": "QtCore.QRegExp", 1064 | "QtCore.QCoreApplication.translate": [ 1065 | "QtCompat.translate", _translate 1066 | ], 1067 | "QtGui.QApplication.translate": [ 1068 | "QtCompat.translate", _translate 1069 | ], 1070 | "QtCore.qInstallMsgHandler": [ 1071 | "QtCompat.qInstallMessageHandler", _qInstallMessageHandler 1072 | ], 1073 | "QtGui.QStyleOptionViewItemV4": "QtCompat.QStyleOptionViewItemV4", 1074 | }, 1075 | "PyQt4": { 1076 | "QtGui.QUndoCommand": "QtWidgets.QUndoCommand", 1077 | "QtGui.QUndoGroup": "QtWidgets.QUndoGroup", 1078 | "QtGui.QUndoStack": "QtWidgets.QUndoStack", 1079 | "QtGui.QActionGroup": "QtWidgets.QActionGroup", 1080 | "QtGui.QAbstractProxyModel": "QtCore.QAbstractProxyModel", 1081 | "QtGui.QSortFilterProxyModel": "QtCore.QSortFilterProxyModel", 1082 | "QtGui.QItemSelection": "QtCore.QItemSelection", 1083 | "QtGui.QStringListModel": "QtCore.QStringListModel", 1084 | "QtGui.QItemSelectionModel": "QtCore.QItemSelectionModel", 1085 | "QtCore.pyqtProperty": "QtCore.Property", 1086 | "QtCore.pyqtSignal": "QtCore.Signal", 1087 | "QtCore.pyqtSlot": "QtCore.Slot", 1088 | "QtGui.QItemSelectionRange": "QtCore.QItemSelectionRange", 1089 | "QtGui.QAbstractPrintDialog": "QtPrintSupport.QAbstractPrintDialog", 1090 | "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", 1091 | "QtGui.QPageSetupDialog": "QtPrintSupport.QPageSetupDialog", 1092 | "QtGui.QPrintDialog": "QtPrintSupport.QPrintDialog", 1093 | "QtGui.QPrintEngine": "QtPrintSupport.QPrintEngine", 1094 | "QtWidgets.QShortcut": "QtWidgets.QShortcut", 1095 | "QtGui.QPrintPreviewDialog": "QtPrintSupport.QPrintPreviewDialog", 1096 | "QtGui.QPrintPreviewWidget": "QtPrintSupport.QPrintPreviewWidget", 1097 | "QtGui.QPrinter": "QtPrintSupport.QPrinter", 1098 | "QtGui.QPrinterInfo": "QtPrintSupport.QPrinterInfo", 1099 | "uic.loadUi": ["QtCompat.loadUi", _loadUi], 1100 | "sip.wrapinstance": ["QtCompat.wrapInstance", _wrapinstance], 1101 | "sip.unwrapinstance": ["QtCompat.getCppPointer", _getcpppointer], 1102 | "sip.isdeleted": ["QtCompat.isValid", _isvalid], 1103 | "QtCore.QString": "str", 1104 | "QtGui.qApp": "QtWidgets.QApplication.instance()", 1105 | "QtCore.QRegExp": "QtCore.QRegExp", 1106 | "QtCore.QCoreApplication.translate": [ 1107 | "QtCompat.translate", _translate 1108 | ], 1109 | "QtGui.QApplication.translate": [ 1110 | "QtCompat.translate", _translate 1111 | ], 1112 | "QtCore.qInstallMsgHandler": [ 1113 | "QtCompat.qInstallMessageHandler", _qInstallMessageHandler 1114 | ], 1115 | "QtGui.QStyleOptionViewItemV4": "QtCompat.QStyleOptionViewItemV4", 1116 | } 1117 | } 1118 | 1119 | """ Compatibility Members 1120 | 1121 | This dictionary is used to build Qt.QtCompat objects that provide a consistent 1122 | interface for obsolete members, and differences in binding return values. 1123 | 1124 | { 1125 | "binding": { 1126 | "classname": { 1127 | "targetname": "binding_namespace", 1128 | } 1129 | } 1130 | } 1131 | """ 1132 | _compatibility_members = { 1133 | "PySide6": { 1134 | "QWidget": { 1135 | "grab": "QtWidgets.QWidget.grab", 1136 | }, 1137 | "QHeaderView": { 1138 | "sectionsClickable": "QtWidgets.QHeaderView.sectionsClickable", 1139 | "setSectionsClickable": 1140 | "QtWidgets.QHeaderView.setSectionsClickable", 1141 | "sectionResizeMode": "QtWidgets.QHeaderView.sectionResizeMode", 1142 | "setSectionResizeMode": 1143 | "QtWidgets.QHeaderView.setSectionResizeMode", 1144 | "sectionsMovable": "QtWidgets.QHeaderView.sectionsMovable", 1145 | "setSectionsMovable": "QtWidgets.QHeaderView.setSectionsMovable", 1146 | }, 1147 | "QFileDialog": { 1148 | "getOpenFileName": "QtWidgets.QFileDialog.getOpenFileName", 1149 | "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", 1150 | "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", 1151 | }, 1152 | "QFont":{ 1153 | "setWeight": "QtGui.QFont.setWeight", 1154 | }, 1155 | "Qt": { 1156 | "MidButton": "QtCore.Qt.MiddleButton", 1157 | }, 1158 | }, 1159 | "PySide2": { 1160 | "QWidget": { 1161 | "grab": "QtWidgets.QWidget.grab", 1162 | }, 1163 | "QHeaderView": { 1164 | "sectionsClickable": "QtWidgets.QHeaderView.sectionsClickable", 1165 | "setSectionsClickable": 1166 | "QtWidgets.QHeaderView.setSectionsClickable", 1167 | "sectionResizeMode": "QtWidgets.QHeaderView.sectionResizeMode", 1168 | "setSectionResizeMode": 1169 | "QtWidgets.QHeaderView.setSectionResizeMode", 1170 | "sectionsMovable": "QtWidgets.QHeaderView.sectionsMovable", 1171 | "setSectionsMovable": "QtWidgets.QHeaderView.setSectionsMovable", 1172 | }, 1173 | "QFileDialog": { 1174 | "getOpenFileName": "QtWidgets.QFileDialog.getOpenFileName", 1175 | "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", 1176 | "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", 1177 | }, 1178 | "QFont":{ 1179 | "setWeight": "QtGui.QFont.setWeight", 1180 | }, 1181 | "Qt": { 1182 | "MidButton": "QtCore.Qt.MiddleButton", 1183 | }, 1184 | }, 1185 | "PyQt5": { 1186 | "QWidget": { 1187 | "grab": "QtWidgets.QWidget.grab", 1188 | }, 1189 | "QHeaderView": { 1190 | "sectionsClickable": "QtWidgets.QHeaderView.sectionsClickable", 1191 | "setSectionsClickable": 1192 | "QtWidgets.QHeaderView.setSectionsClickable", 1193 | "sectionResizeMode": "QtWidgets.QHeaderView.sectionResizeMode", 1194 | "setSectionResizeMode": 1195 | "QtWidgets.QHeaderView.setSectionResizeMode", 1196 | "sectionsMovable": "QtWidgets.QHeaderView.sectionsMovable", 1197 | "setSectionsMovable": "QtWidgets.QHeaderView.setSectionsMovable", 1198 | }, 1199 | "QFileDialog": { 1200 | "getOpenFileName": "QtWidgets.QFileDialog.getOpenFileName", 1201 | "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", 1202 | "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", 1203 | }, 1204 | "QFont":{ 1205 | "setWeight": "QtGui.QFont.setWeight", 1206 | }, 1207 | "Qt": { 1208 | "MidButton": "QtCore.Qt.MiddleButton", 1209 | }, 1210 | }, 1211 | "PySide": { 1212 | "QWidget": { 1213 | "grab": "QtWidgets.QPixmap.grabWidget", 1214 | }, 1215 | "QHeaderView": { 1216 | "sectionsClickable": "QtWidgets.QHeaderView.isClickable", 1217 | "setSectionsClickable": "QtWidgets.QHeaderView.setClickable", 1218 | "sectionResizeMode": "QtWidgets.QHeaderView.resizeMode", 1219 | "setSectionResizeMode": "QtWidgets.QHeaderView.setResizeMode", 1220 | "sectionsMovable": "QtWidgets.QHeaderView.isMovable", 1221 | "setSectionsMovable": "QtWidgets.QHeaderView.setMovable", 1222 | }, 1223 | "QFileDialog": { 1224 | "getOpenFileName": "QtWidgets.QFileDialog.getOpenFileName", 1225 | "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", 1226 | "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", 1227 | }, 1228 | "QFont":{ 1229 | "setWeight": "QtGui.QFont.setWeight", 1230 | }, 1231 | "Qt": { 1232 | "MidButton": "QtCore.Qt.MiddleButton", 1233 | }, 1234 | }, 1235 | "PyQt4": { 1236 | "QWidget": { 1237 | "grab": "QtWidgets.QPixmap.grabWidget", 1238 | }, 1239 | "QHeaderView": { 1240 | "sectionsClickable": "QtWidgets.QHeaderView.isClickable", 1241 | "setSectionsClickable": "QtWidgets.QHeaderView.setClickable", 1242 | "sectionResizeMode": "QtWidgets.QHeaderView.resizeMode", 1243 | "setSectionResizeMode": "QtWidgets.QHeaderView.setResizeMode", 1244 | "sectionsMovable": "QtWidgets.QHeaderView.isMovable", 1245 | "setSectionsMovable": "QtWidgets.QHeaderView.setMovable", 1246 | }, 1247 | "QFileDialog": { 1248 | "getOpenFileName": "QtWidgets.QFileDialog.getOpenFileName", 1249 | "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", 1250 | "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", 1251 | }, 1252 | "QFont":{ 1253 | "setWeight": "QtGui.QFont.setWeight", 1254 | }, 1255 | "Qt": { 1256 | "MidButton": "QtCore.Qt.MiddleButton", 1257 | }, 1258 | }, 1259 | } 1260 | 1261 | 1262 | def _apply_site_config(): 1263 | try: 1264 | import QtSiteConfig 1265 | except ImportError: 1266 | # If no QtSiteConfig module found, no modifications 1267 | # to _common_members are needed. 1268 | pass 1269 | else: 1270 | # Provide the ability to modify the dicts used to build Qt.py 1271 | if hasattr(QtSiteConfig, 'update_members'): 1272 | QtSiteConfig.update_members(_common_members) 1273 | 1274 | if hasattr(QtSiteConfig, 'update_misplaced_members'): 1275 | QtSiteConfig.update_misplaced_members(members=_misplaced_members) 1276 | 1277 | if hasattr(QtSiteConfig, 'update_compatibility_members'): 1278 | QtSiteConfig.update_compatibility_members( 1279 | members=_compatibility_members) 1280 | 1281 | 1282 | def _new_module(name): 1283 | return types.ModuleType(__name__ + "." + name) 1284 | 1285 | 1286 | def _import_sub_module(module, name): 1287 | """import_sub_module will mimic the function of importlib.import_module""" 1288 | module = __import__(module.__name__ + "." + name) 1289 | for level in name.split("."): 1290 | module = getattr(module, level) 1291 | return module 1292 | 1293 | 1294 | def _setup(module, extras): 1295 | """Install common submodules""" 1296 | 1297 | Qt.__binding__ = module.__name__ 1298 | 1299 | def _warn_import_error(exc, module): 1300 | msg = str(exc) 1301 | if "No module named" in msg: 1302 | return 1303 | _warn("ImportError(%s): %s" % (module, msg)) 1304 | 1305 | for name in list(_common_members) + extras: 1306 | try: 1307 | submodule = _import_sub_module( 1308 | module, name) 1309 | except ImportError as e: 1310 | try: 1311 | # For extra modules like sip and shiboken that may not be 1312 | # children of the binding. 1313 | submodule = __import__(name) 1314 | except ImportError as e2: 1315 | _warn_import_error(e, name) 1316 | _warn_import_error(e2, name) 1317 | continue 1318 | 1319 | setattr(Qt, "_" + name, submodule) 1320 | 1321 | if name not in extras: 1322 | # Store reference to original binding, 1323 | # but don't store speciality modules 1324 | # such as uic or QtUiTools 1325 | setattr(Qt, name, _new_module(name)) 1326 | 1327 | 1328 | def _reassign_misplaced_members(binding): 1329 | """Apply misplaced members from `binding` to Qt.py 1330 | 1331 | Arguments: 1332 | binding (dict): Misplaced members 1333 | 1334 | """ 1335 | 1336 | 1337 | for src, dst in _misplaced_members[binding].items(): 1338 | dst_value = None 1339 | 1340 | src_parts = src.split(".") 1341 | src_module = src_parts[0] 1342 | src_member = None 1343 | if len(src_parts) > 1: 1344 | src_member = src_parts[1:] 1345 | 1346 | if isinstance(dst, (list, tuple)): 1347 | dst, dst_value = dst 1348 | 1349 | dst_parts = dst.split(".") 1350 | dst_module = dst_parts[0] 1351 | dst_member = None 1352 | if len(dst_parts) > 1: 1353 | dst_member = dst_parts[1] 1354 | 1355 | 1356 | # Get the member we want to store in the namesapce. 1357 | if not dst_value: 1358 | try: 1359 | _part = getattr(Qt, "_" + src_module) 1360 | while src_member: 1361 | member = src_member.pop(0) 1362 | _part = getattr(_part, member) 1363 | dst_value = _part 1364 | except AttributeError: 1365 | # If the member we want to store in the namespace does not 1366 | # exist, there is no need to continue. This can happen if a 1367 | # request was made to rename a member that didn't exist, for 1368 | # example if QtWidgets isn't available on the target platform. 1369 | _log("Misplaced member has no source: {0}".format(src)) 1370 | continue 1371 | 1372 | try: 1373 | src_object = getattr(Qt, dst_module) 1374 | except AttributeError: 1375 | if dst_module not in _common_members: 1376 | # Only create the Qt parent module if its listed in 1377 | # _common_members. Without this check, if you remove QtCore 1378 | # from _common_members, the default _misplaced_members will add 1379 | # Qt.QtCore so it can add Signal, Slot, etc. 1380 | msg = 'Not creating missing member module "{m}" for "{c}"' 1381 | _log(msg.format(m=dst_module, c=dst_member)) 1382 | continue 1383 | # If the dst is valid but the Qt parent module does not exist 1384 | # then go ahead and create a new module to contain the member. 1385 | setattr(Qt, dst_module, _new_module(dst_module)) 1386 | src_object = getattr(Qt, dst_module) 1387 | # Enable direct import of the new module 1388 | sys.modules[__name__ + "." + dst_module] = src_object 1389 | 1390 | if not dst_value: 1391 | dst_value = getattr(Qt, "_" + src_module) 1392 | if src_member: 1393 | dst_value = getattr(dst_value, src_member) 1394 | 1395 | setattr( 1396 | src_object, 1397 | dst_member or dst_module, 1398 | dst_value 1399 | ) 1400 | 1401 | 1402 | def _build_compatibility_members(binding, decorators=None): 1403 | """Apply `binding` to QtCompat 1404 | 1405 | Arguments: 1406 | binding (str): Top level binding in _compatibility_members. 1407 | decorators (dict, optional): Provides the ability to decorate the 1408 | original Qt methods when needed by a binding. This can be used 1409 | to change the returned value to a standard value. The key should 1410 | be the classname, the value is a dict where the keys are the 1411 | target method names, and the values are the decorator functions. 1412 | 1413 | """ 1414 | 1415 | decorators = decorators or dict() 1416 | 1417 | # Allow optional site-level customization of the compatibility members. 1418 | # This method does not need to be implemented in QtSiteConfig. 1419 | try: 1420 | import QtSiteConfig 1421 | except ImportError: 1422 | pass 1423 | else: 1424 | if hasattr(QtSiteConfig, 'update_compatibility_decorators'): 1425 | QtSiteConfig.update_compatibility_decorators(binding, decorators) 1426 | 1427 | _QtCompat = type("QtCompat", (object,), {}) 1428 | 1429 | for classname, bindings in _compatibility_members[binding].items(): 1430 | attrs = {} 1431 | for target, binding in bindings.items(): 1432 | namespaces = binding.split('.') 1433 | try: 1434 | src_object = getattr(Qt, "_" + namespaces[0]) 1435 | except AttributeError as e: 1436 | _log("QtCompat: AttributeError: %s" % e) 1437 | # Skip reassignment of non-existing members. 1438 | # This can happen if a request was made to 1439 | # rename a member that didn't exist, for example 1440 | # if QtWidgets isn't available on the target platform. 1441 | continue 1442 | 1443 | # Walk down any remaining namespace getting the object assuming 1444 | # that if the first namespace exists the rest will exist. 1445 | for namespace in namespaces[1:]: 1446 | src_object = getattr(src_object, namespace) 1447 | 1448 | # decorate the Qt method if a decorator was provided. 1449 | if target in decorators.get(classname, []): 1450 | # staticmethod must be called on the decorated method to 1451 | # prevent a TypeError being raised when the decorated method 1452 | # is called. 1453 | src_object = staticmethod( 1454 | decorators[classname][target](src_object)) 1455 | 1456 | attrs[target] = src_object 1457 | 1458 | # Create the QtCompat class and install it into the namespace 1459 | compat_class = type(classname, (_QtCompat,), attrs) 1460 | setattr(Qt.QtCompat, classname, compat_class) 1461 | 1462 | 1463 | def _pyside6(): 1464 | """Initialise PySide6 1465 | 1466 | These functions serve to test the existence of a binding 1467 | along with set it up in such a way that it aligns with 1468 | the final step; adding members from the original binding 1469 | to Qt.py 1470 | 1471 | """ 1472 | 1473 | import PySide6 as module 1474 | extras = ["QtUiTools"] 1475 | try: 1476 | import shiboken6 1477 | extras.append("shiboken6") 1478 | except ImportError as e: 1479 | print("ImportError: %s" % e) 1480 | 1481 | _setup(module, extras) 1482 | Qt.__binding_version__ = module.__version__ 1483 | 1484 | if hasattr(Qt, "_shiboken6"): 1485 | Qt.QtCompat.wrapInstance = _wrapinstance 1486 | Qt.QtCompat.getCppPointer = _getcpppointer 1487 | Qt.QtCompat.delete = shiboken6.delete 1488 | 1489 | if hasattr(Qt, "_QtUiTools"): 1490 | Qt.QtCompat.loadUi = _loadUi 1491 | 1492 | if hasattr(Qt, "_QtCore"): 1493 | Qt.__qt_version__ = Qt._QtCore.qVersion() 1494 | Qt.QtCompat.dataChanged = ( 1495 | lambda self, topleft, bottomright, roles=None: 1496 | self.dataChanged.emit(topleft, bottomright, roles or []) 1497 | ) 1498 | 1499 | if hasattr(Qt, "_QtWidgets"): 1500 | Qt.QtCompat.setSectionResizeMode = \ 1501 | Qt._QtWidgets.QHeaderView.setSectionResizeMode 1502 | 1503 | def setWeight(func): 1504 | def wrapper(self, weight): 1505 | weight = { 1506 | 100: Qt._QtGui.QFont.Thin, 1507 | 200: Qt._QtGui.QFont.ExtraLight, 1508 | 300: Qt._QtGui.QFont.Light, 1509 | 400: Qt._QtGui.QFont.Normal, 1510 | 500: Qt._QtGui.QFont.Medium, 1511 | 600: Qt._QtGui.QFont.DemiBold, 1512 | 700: Qt._QtGui.QFont.Bold, 1513 | 800: Qt._QtGui.QFont.ExtraBold, 1514 | 900: Qt._QtGui.QFont.Black, 1515 | }.get(weight, Qt._QtGui.QFont.Normal) 1516 | 1517 | return func(self, weight) 1518 | 1519 | wrapper.__doc__ = func.__doc__ 1520 | wrapper.__name__ = func.__name__ 1521 | 1522 | return wrapper 1523 | 1524 | 1525 | decorators = { 1526 | "QFont": { 1527 | "setWeight": setWeight, 1528 | } 1529 | } 1530 | 1531 | _reassign_misplaced_members("PySide6") 1532 | _build_compatibility_members("PySide6", decorators) 1533 | 1534 | 1535 | def _pyside2(): 1536 | """Initialise PySide2 1537 | 1538 | These functions serve to test the existence of a binding 1539 | along with set it up in such a way that it aligns with 1540 | the final step; adding members from the original binding 1541 | to Qt.py 1542 | 1543 | """ 1544 | 1545 | import PySide2 as module 1546 | extras = ["QtUiTools"] 1547 | try: 1548 | try: 1549 | # Before merge of PySide and shiboken 1550 | import shiboken2 1551 | except ImportError: 1552 | # After merge of PySide and shiboken, May 2017 1553 | from PySide2 import shiboken2 1554 | extras.append("shiboken2") 1555 | except ImportError: 1556 | pass 1557 | 1558 | _setup(module, extras) 1559 | Qt.__binding_version__ = module.__version__ 1560 | 1561 | if hasattr(Qt, "_shiboken2"): 1562 | Qt.QtCompat.wrapInstance = _wrapinstance 1563 | Qt.QtCompat.getCppPointer = _getcpppointer 1564 | Qt.QtCompat.delete = shiboken2.delete 1565 | 1566 | if hasattr(Qt, "_QtUiTools"): 1567 | Qt.QtCompat.loadUi = _loadUi 1568 | 1569 | if hasattr(Qt, "_QtCore"): 1570 | Qt.__qt_version__ = Qt._QtCore.qVersion() 1571 | Qt.QtCompat.dataChanged = ( 1572 | lambda self, topleft, bottomright, roles=None: 1573 | self.dataChanged.emit(topleft, bottomright, roles or []) 1574 | ) 1575 | 1576 | if hasattr(Qt, "_QtWidgets"): 1577 | Qt.QtCompat.setSectionResizeMode = \ 1578 | Qt._QtWidgets.QHeaderView.setSectionResizeMode 1579 | 1580 | _reassign_misplaced_members("PySide2") 1581 | _build_compatibility_members("PySide2") 1582 | 1583 | 1584 | def _pyside(): 1585 | """Initialise PySide""" 1586 | 1587 | import PySide as module 1588 | extras = ["QtUiTools"] 1589 | try: 1590 | try: 1591 | # Before merge of PySide and shiboken 1592 | import shiboken 1593 | except ImportError: 1594 | # After merge of PySide and shiboken, May 2017 1595 | from PySide import shiboken 1596 | extras.append("shiboken") 1597 | except ImportError: 1598 | pass 1599 | 1600 | _setup(module, extras) 1601 | Qt.__binding_version__ = module.__version__ 1602 | 1603 | if hasattr(Qt, "_shiboken"): 1604 | Qt.QtCompat.wrapInstance = _wrapinstance 1605 | Qt.QtCompat.getCppPointer = _getcpppointer 1606 | Qt.QtCompat.delete = shiboken.delete 1607 | 1608 | if hasattr(Qt, "_QtUiTools"): 1609 | Qt.QtCompat.loadUi = _loadUi 1610 | 1611 | if hasattr(Qt, "_QtGui"): 1612 | setattr(Qt, "QtWidgets", _new_module("QtWidgets")) 1613 | setattr(Qt, "_QtWidgets", Qt._QtGui) 1614 | if hasattr(Qt._QtGui, "QX11Info"): 1615 | setattr(Qt, "QtX11Extras", _new_module("QtX11Extras")) 1616 | Qt.QtX11Extras.QX11Info = Qt._QtGui.QX11Info 1617 | 1618 | Qt.QtCompat.setSectionResizeMode = Qt._QtGui.QHeaderView.setResizeMode 1619 | 1620 | if hasattr(Qt, "_QtCore"): 1621 | Qt.__qt_version__ = Qt._QtCore.qVersion() 1622 | Qt.QtCompat.dataChanged = ( 1623 | lambda self, topleft, bottomright, roles=None: 1624 | self.dataChanged.emit(topleft, bottomright) 1625 | ) 1626 | 1627 | _reassign_misplaced_members("PySide") 1628 | _build_compatibility_members("PySide") 1629 | 1630 | 1631 | def _pyqt5(): 1632 | """Initialise PyQt5""" 1633 | 1634 | import PyQt5 as module 1635 | extras = ["uic"] 1636 | 1637 | try: 1638 | # Relevant to PyQt5 5.11 and above 1639 | from PyQt5 import sip 1640 | extras += ["sip"] 1641 | except ImportError: 1642 | 1643 | try: 1644 | import sip 1645 | extras += ["sip"] 1646 | except ImportError: 1647 | sip = None 1648 | 1649 | _setup(module, extras) 1650 | if hasattr(Qt, "_sip"): 1651 | Qt.QtCompat.wrapInstance = _wrapinstance 1652 | Qt.QtCompat.getCppPointer = _getcpppointer 1653 | Qt.QtCompat.delete = sip.delete 1654 | 1655 | if hasattr(Qt, "_uic"): 1656 | Qt.QtCompat.loadUi = _loadUi 1657 | 1658 | if hasattr(Qt, "_QtCore"): 1659 | Qt.__binding_version__ = Qt._QtCore.PYQT_VERSION_STR 1660 | Qt.__qt_version__ = Qt._QtCore.QT_VERSION_STR 1661 | Qt.QtCompat.dataChanged = ( 1662 | lambda self, topleft, bottomright, roles=None: 1663 | self.dataChanged.emit(topleft, bottomright, roles or []) 1664 | ) 1665 | 1666 | if hasattr(Qt, "_QtWidgets"): 1667 | Qt.QtCompat.setSectionResizeMode = \ 1668 | Qt._QtWidgets.QHeaderView.setSectionResizeMode 1669 | 1670 | _reassign_misplaced_members("PyQt5") 1671 | _build_compatibility_members('PyQt5') 1672 | 1673 | 1674 | def _pyqt4(): 1675 | """Initialise PyQt4""" 1676 | 1677 | import sip 1678 | 1679 | # Validation of envivornment variable. Prevents an error if 1680 | # the variable is invalid since it's just a hint. 1681 | try: 1682 | hint = int(QT_SIP_API_HINT) 1683 | except TypeError: 1684 | hint = None # Variable was None, i.e. not set. 1685 | except ValueError: 1686 | raise ImportError("QT_SIP_API_HINT=%s must be a 1 or 2") 1687 | 1688 | for api in ("QString", 1689 | "QVariant", 1690 | "QDate", 1691 | "QDateTime", 1692 | "QTextStream", 1693 | "QTime", 1694 | "QUrl"): 1695 | try: 1696 | sip.setapi(api, hint or 2) 1697 | except AttributeError: 1698 | raise ImportError("PyQt4 < 4.6 isn't supported by Qt.py") 1699 | except ValueError: 1700 | actual = sip.getapi(api) 1701 | if not hint: 1702 | raise ImportError("API version already set to %d" % actual) 1703 | else: 1704 | # Having provided a hint indicates a soft constraint, one 1705 | # that doesn't throw an exception. 1706 | sys.stderr.write( 1707 | "Warning: API '%s' has already been set to %d.\n" 1708 | % (api, actual) 1709 | ) 1710 | 1711 | import PyQt4 as module 1712 | extras = ["uic"] 1713 | try: 1714 | import sip 1715 | extras.append(sip.__name__) 1716 | except ImportError: 1717 | sip = None 1718 | 1719 | _setup(module, extras) 1720 | if hasattr(Qt, "_sip"): 1721 | Qt.QtCompat.wrapInstance = _wrapinstance 1722 | Qt.QtCompat.getCppPointer = _getcpppointer 1723 | Qt.QtCompat.delete = sip.delete 1724 | 1725 | if hasattr(Qt, "_uic"): 1726 | Qt.QtCompat.loadUi = _loadUi 1727 | 1728 | if hasattr(Qt, "_QtGui"): 1729 | setattr(Qt, "QtWidgets", _new_module("QtWidgets")) 1730 | setattr(Qt, "_QtWidgets", Qt._QtGui) 1731 | if hasattr(Qt._QtGui, "QX11Info"): 1732 | setattr(Qt, "QtX11Extras", _new_module("QtX11Extras")) 1733 | Qt.QtX11Extras.QX11Info = Qt._QtGui.QX11Info 1734 | 1735 | Qt.QtCompat.setSectionResizeMode = \ 1736 | Qt._QtGui.QHeaderView.setResizeMode 1737 | 1738 | if hasattr(Qt, "_QtCore"): 1739 | Qt.__binding_version__ = Qt._QtCore.PYQT_VERSION_STR 1740 | Qt.__qt_version__ = Qt._QtCore.QT_VERSION_STR 1741 | Qt.QtCompat.dataChanged = ( 1742 | lambda self, topleft, bottomright, roles=None: 1743 | self.dataChanged.emit(topleft, bottomright) 1744 | ) 1745 | 1746 | _reassign_misplaced_members("PyQt4") 1747 | 1748 | # QFileDialog QtCompat decorator 1749 | def _standardizeQFileDialog(some_function): 1750 | """Decorator that makes PyQt4 return conform to other bindings""" 1751 | def wrapper(*args, **kwargs): 1752 | ret = (some_function(*args, **kwargs)) 1753 | 1754 | # PyQt4 only returns the selected filename, force it to a 1755 | # standard return of the selected filename, and a empty string 1756 | # for the selected filter 1757 | return ret, '' 1758 | 1759 | wrapper.__doc__ = some_function.__doc__ 1760 | wrapper.__name__ = some_function.__name__ 1761 | 1762 | return wrapper 1763 | 1764 | decorators = { 1765 | "QFileDialog": { 1766 | "getOpenFileName": _standardizeQFileDialog, 1767 | "getOpenFileNames": _standardizeQFileDialog, 1768 | "getSaveFileName": _standardizeQFileDialog, 1769 | } 1770 | } 1771 | _build_compatibility_members('PyQt4', decorators) 1772 | 1773 | 1774 | def _none(): 1775 | """Internal option (used in installer)""" 1776 | 1777 | Mock = type("Mock", (), {"__getattr__": lambda Qt, attr: None}) 1778 | 1779 | Qt.__binding__ = "None" 1780 | Qt.__qt_version__ = "0.0.0" 1781 | Qt.__binding_version__ = "0.0.0" 1782 | Qt.QtCompat.loadUi = lambda uifile, baseinstance=None: None 1783 | Qt.QtCompat.setSectionResizeMode = lambda *args, **kwargs: None 1784 | 1785 | for submodule in _common_members.keys(): 1786 | setattr(Qt, submodule, Mock()) 1787 | setattr(Qt, "_" + submodule, Mock()) 1788 | 1789 | 1790 | def _log(text): 1791 | if QT_VERBOSE: 1792 | sys.stdout.write("Qt.py [info]: %s\n" % text) 1793 | 1794 | 1795 | def _warn(text): 1796 | try: 1797 | sys.stderr.write("Qt.py [warning]: %s\n" % text) 1798 | except UnicodeDecodeError: 1799 | import locale 1800 | encoding = locale.getpreferredencoding() 1801 | sys.stderr.write("Qt.py [warning]: %s\n" % text.decode(encoding)) 1802 | 1803 | 1804 | def _convert(lines): 1805 | """Convert compiled .ui file from PySide2 to Qt.py 1806 | 1807 | Arguments: 1808 | lines (list): Each line of of .ui file 1809 | 1810 | Usage: 1811 | >> with open("myui.py") as f: 1812 | .. lines = _convert(f.readlines()) 1813 | 1814 | """ 1815 | 1816 | def parse(line): 1817 | line = line.replace("from PySide2 import", "from Qt import QtCompat,") 1818 | line = line.replace("QtWidgets.QApplication.translate", 1819 | "QtCompat.translate") 1820 | if "QtCore.SIGNAL" in line: 1821 | raise NotImplementedError("QtCore.SIGNAL is missing from PyQt5 " 1822 | "and so Qt.py does not support it: you " 1823 | "should avoid defining signals inside " 1824 | "your ui files.") 1825 | return line 1826 | 1827 | parsed = list() 1828 | for line in lines: 1829 | line = parse(line) 1830 | parsed.append(line) 1831 | 1832 | return parsed 1833 | 1834 | 1835 | def _cli(args): 1836 | """Qt.py command-line interface""" 1837 | import argparse 1838 | 1839 | parser = argparse.ArgumentParser() 1840 | parser.add_argument("--convert", 1841 | help="Path to compiled Python module, e.g. my_ui.py") 1842 | parser.add_argument("--compile", 1843 | help="Accept raw .ui file and compile with native " 1844 | "PySide2 compiler.") 1845 | parser.add_argument("--stdout", 1846 | help="Write to stdout instead of file", 1847 | action="store_true") 1848 | parser.add_argument("--stdin", 1849 | help="Read from stdin instead of file", 1850 | action="store_true") 1851 | 1852 | args = parser.parse_args(args) 1853 | 1854 | if args.stdout: 1855 | raise NotImplementedError("--stdout") 1856 | 1857 | if args.stdin: 1858 | raise NotImplementedError("--stdin") 1859 | 1860 | if args.compile: 1861 | raise NotImplementedError("--compile") 1862 | 1863 | if args.convert: 1864 | sys.stdout.write("#\n" 1865 | "# WARNING: --convert is an ALPHA feature.\n#\n" 1866 | "# See https://github.com/mottosso/Qt.py/pull/132\n" 1867 | "# for details.\n" 1868 | "#\n") 1869 | 1870 | # 1871 | # ------> Read 1872 | # 1873 | with open(args.convert) as f: 1874 | lines = _convert(f.readlines()) 1875 | 1876 | backup = "%s_backup%s" % os.path.splitext(args.convert) 1877 | sys.stdout.write("Creating \"%s\"..\n" % backup) 1878 | shutil.copy(args.convert, backup) 1879 | 1880 | # 1881 | # <------ Write 1882 | # 1883 | with open(args.convert, "w") as f: 1884 | f.write("".join(lines)) 1885 | 1886 | sys.stdout.write("Successfully converted \"%s\"\n" % args.convert) 1887 | 1888 | 1889 | class MissingMember(object): 1890 | """ 1891 | A placeholder type for a missing Qt object not 1892 | included in Qt.py 1893 | 1894 | Args: 1895 | name (str): The name of the missing type 1896 | details (str): An optional custom error message 1897 | """ 1898 | ERR_TMPL = ("{} is not a common object across PySide2 " 1899 | "and the other Qt bindings. It is not included " 1900 | "as a common member in the Qt.py layer") 1901 | 1902 | def __init__(self, name, details=''): 1903 | self.__name = name 1904 | self.__err = self.ERR_TMPL.format(name) 1905 | 1906 | if details: 1907 | self.__err = "{}: {}".format(self.__err, details) 1908 | 1909 | def __repr__(self): 1910 | return "<{}: {}>".format(self.__class__.__name__, self.__name) 1911 | 1912 | def __getattr__(self, name): 1913 | raise NotImplementedError(self.__err) 1914 | 1915 | def __call__(self, *a, **kw): 1916 | raise NotImplementedError(self.__err) 1917 | 1918 | 1919 | def _install(): 1920 | # Default order (customize order and content via QT_PREFERRED_BINDING) 1921 | default_order = ("PySide6", "PySide2", "PyQt5", "PySide", "PyQt4") 1922 | preferred_order = None 1923 | if QT_PREFERRED_BINDING_JSON: 1924 | # A per-vendor preferred binding customization was defined 1925 | # This should be a dictionary of the full Qt.py module namespace to 1926 | # apply binding settings to. The "default" key can be used to apply 1927 | # custom bindings to all modules not explicitly defined. If the json 1928 | # data is invalid this will raise a exception. 1929 | # Example: 1930 | # {"mylibrary.vendor.Qt": ["PySide2"], "default":["PyQt5","PyQt4"]} 1931 | try: 1932 | preferred_bindings = json.loads(QT_PREFERRED_BINDING_JSON) 1933 | except ValueError: 1934 | # Python 2 raises ValueError, Python 3 raises json.JSONDecodeError 1935 | # a subclass of ValueError 1936 | _warn("Failed to parse QT_PREFERRED_BINDING_JSON='%s'" 1937 | % QT_PREFERRED_BINDING_JSON) 1938 | _warn("Falling back to default preferred order") 1939 | else: 1940 | preferred_order = preferred_bindings.get(__name__) 1941 | # If no matching binding was used, optionally apply a default. 1942 | if preferred_order is None: 1943 | preferred_order = preferred_bindings.get("default", None) 1944 | if preferred_order is None: 1945 | # If a json preferred binding was not used use, respect the 1946 | # QT_PREFERRED_BINDING environment variable if defined. 1947 | preferred_order = list( 1948 | b for b in QT_PREFERRED_BINDING.split(os.pathsep) if b 1949 | ) 1950 | 1951 | order = preferred_order or default_order 1952 | 1953 | available = { 1954 | "PySide6": _pyside6, 1955 | "PySide2": _pyside2, 1956 | "PyQt5": _pyqt5, 1957 | "PySide": _pyside, 1958 | "PyQt4": _pyqt4, 1959 | "None": _none 1960 | } 1961 | 1962 | _log("Order: '%s'" % "', '".join(order)) 1963 | 1964 | # Allow site-level customization of the available modules. 1965 | _apply_site_config() 1966 | 1967 | found_binding = False 1968 | for name in order: 1969 | _log("Trying %s" % name) 1970 | 1971 | try: 1972 | available[name]() 1973 | found_binding = True 1974 | break 1975 | 1976 | except ImportError as e: 1977 | _log("ImportError: %s" % e) 1978 | 1979 | except KeyError: 1980 | _log("ImportError: Preferred binding '%s' not found." % name) 1981 | 1982 | if not found_binding: 1983 | # If not binding were found, throw this error 1984 | raise ImportError("No Qt binding were found.") 1985 | 1986 | # Install individual members 1987 | for name, members in _common_members.items(): 1988 | try: 1989 | their_submodule = getattr(Qt, "_%s" % name) 1990 | except AttributeError: 1991 | continue 1992 | 1993 | our_submodule = getattr(Qt, name) 1994 | 1995 | # Enable import * 1996 | __all__.append(name) 1997 | 1998 | # Enable direct import of submodule, 1999 | # e.g. import Qt.QtCore 2000 | sys.modules[__name__ + "." + name] = our_submodule 2001 | 2002 | for member in members: 2003 | # Accept that a submodule may miss certain members. 2004 | try: 2005 | their_member = getattr(their_submodule, member) 2006 | except AttributeError: 2007 | _log("'%s.%s' was missing." % (name, member)) 2008 | continue 2009 | 2010 | setattr(our_submodule, member, their_member) 2011 | 2012 | # Install missing member placeholders 2013 | for name, members in _missing_members.items(): 2014 | our_submodule = getattr(Qt, name) 2015 | 2016 | for member in members: 2017 | 2018 | # If the submodule already has this member installed, 2019 | # either by the common members, or the site config, 2020 | # then skip installing this one over it. 2021 | if hasattr(our_submodule, member): 2022 | continue 2023 | 2024 | placeholder = MissingMember("{}.{}".format(name, member), 2025 | details=members[member]) 2026 | setattr(our_submodule, member, placeholder) 2027 | 2028 | # Enable direct import of QtCompat 2029 | sys.modules[__name__ + ".QtCompat"] = Qt.QtCompat 2030 | 2031 | # Backwards compatibility 2032 | if hasattr(Qt.QtCompat, 'loadUi'): 2033 | Qt.QtCompat.load_ui = Qt.QtCompat.loadUi 2034 | 2035 | 2036 | _install() 2037 | 2038 | # Setup Binding Enum states 2039 | Qt.IsPySide6 = Qt.__binding__ == "PySide6" 2040 | Qt.IsPySide2 = Qt.__binding__ == 'PySide2' 2041 | Qt.IsPyQt5 = Qt.__binding__ == 'PyQt5' 2042 | Qt.IsPySide = Qt.__binding__ == 'PySide' 2043 | Qt.IsPyQt4 = Qt.__binding__ == 'PyQt4' 2044 | 2045 | """Augment QtCompat 2046 | 2047 | QtCompat contains wrappers and added functionality 2048 | to the original bindings, such as the CLI interface 2049 | and otherwise incompatible members between bindings, 2050 | such as `QHeaderView.setSectionResizeMode`. 2051 | 2052 | """ 2053 | 2054 | Qt.QtCompat._cli = _cli 2055 | Qt.QtCompat._convert = _convert 2056 | 2057 | # Enable command-line interface 2058 | if __name__ == "__main__": 2059 | _cli(sys.argv[1:]) 2060 | 2061 | 2062 | # The MIT License (MIT) 2063 | # 2064 | # Copyright (c) 2016-2017 Marcus Ottosson 2065 | # 2066 | # Permission is hereby granted, free of charge, to any person obtaining a copy 2067 | # of this software and associated documentation files (the "Software"), to deal 2068 | # in the Software without restriction, including without limitation the rights 2069 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 2070 | # copies of the Software, and to permit persons to whom the Software is 2071 | # furnished to do so, subject to the following conditions: 2072 | # 2073 | # The above copyright notice and this permission notice shall be included in 2074 | # all copies or substantial portions of the Software. 2075 | # 2076 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 2077 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 2078 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 2079 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 2080 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 2081 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 2082 | # SOFTWARE. 2083 | # 2084 | # In PySide(2), loadUi does not exist, so we implement it 2085 | # 2086 | # `_UiLoader` is adapted from the qtpy project, which was further influenced 2087 | # by qt-helpers which was released under a 3-clause BSD license which in turn 2088 | # is based on a solution at: 2089 | # 2090 | # - https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8 2091 | # 2092 | # The License for this code is as follows: 2093 | # 2094 | # qt-helpers - a common front-end to various Qt modules 2095 | # 2096 | # Copyright (c) 2015, Chris Beaumont and Thomas Robitaille 2097 | # 2098 | # All rights reserved. 2099 | # 2100 | # Redistribution and use in source and binary forms, with or without 2101 | # modification, are permitted provided that the following conditions are 2102 | # met: 2103 | # 2104 | # * Redistributions of source code must retain the above copyright 2105 | # notice, this list of conditions and the following disclaimer. 2106 | # * Redistributions in binary form must reproduce the above copyright 2107 | # notice, this list of conditions and the following disclaimer in the 2108 | # documentation and/or other materials provided with the 2109 | # distribution. 2110 | # * Neither the name of the Glue project nor the names of its contributors 2111 | # may be used to endorse or promote products derived from this software 2112 | # without specific prior written permission. 2113 | # 2114 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 2115 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 2116 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2117 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 2118 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 2119 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 2120 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 2121 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2122 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2123 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2124 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2125 | # 2126 | # Which itself was based on the solution at 2127 | # 2128 | # https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8 2129 | # 2130 | # which was released under the MIT license: 2131 | # 2132 | # Copyright (c) 2011 Sebastian Wiesner 2133 | # Modifications by Charl Botha 2134 | # 2135 | # Permission is hereby granted, free of charge, to any person obtaining a 2136 | # copy of this software and associated documentation files 2137 | # (the "Software"),to deal in the Software without restriction, 2138 | # including without limitation 2139 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 2140 | # and/or sell copies of the Software, and to permit persons to whom the 2141 | # Software is furnished to do so, subject to the following conditions: 2142 | # 2143 | # The above copyright notice and this permission notice shall be included 2144 | # in all copies or substantial portions of the Software. 2145 | # 2146 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 2147 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 2148 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 2149 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 2150 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 2151 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 2152 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2153 | --------------------------------------------------------------------------------