1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4"""QDarkStyle is a dark stylesheet for Python and Qt applications. 5 6This module provides a function to transparently load the stylesheets 7with the correct rc file. 8 9First, start importing our module 10 11.. code-block:: python 12 13 import qdarkstyle 14 15Then you can get stylesheet provided by QDarkStyle for various Qt wrappers 16as shown bellow 17 18.. code-block:: python 19 20 # PySide 21 dark_stylesheet = qdarkstyle.load_stylesheet_pyside() 22 # PySide 2 23 dark_stylesheet = qdarkstyle.load_stylesheet_pyside2() 24 # PyQt4 25 dark_stylesheet = qdarkstyle.load_stylesheet_pyqt() 26 # PyQt5 27 dark_stylesheet = qdarkstyle.load_stylesheet_pyqt5() 28 29Or from environment variables provided for QtPy or PyQtGraph, see 30 31.. code-block:: python 32 33 # QtPy 34 dark_stylesheet = qdarkstyle.load_stylesheet_from_environment() 35 # PyQtGraph 36 dark_stylesheet = qdarkstyle.load_stylesheet_from_environment(is_pyqtgraph) 37 38Finally, set your QApplication with it 39 40.. code-block:: python 41 42 app.setStyleSheet(dark_stylesheet) 43 44Enjoy! 45 46""" 47 48import logging 49import os 50import platform 51import sys 52import warnings 53import copy 54 55if sys.version_info >= (3, 4): 56 import importlib 57 58__version__ = "2.6.5" 59 60 61QT_BINDINGS = ['PyQt4', 'PyQt5', 'PySide', 'PySide2'] 62"""list: values of all Qt bindings to import.""" 63 64QT_ABSTRACTIONS = ['qtpy', 'pyqtgraph', 'Qt'] 65"""list: values of all Qt abstraction layers to import.""" 66 67QT4_IMPORT_API = ['QtCore', 'QtGui'] 68"""list: which subpackage to import for Qt4 API.""" 69 70QT5_IMPORT_API = ['QtCore', 'QtGui', 'QtWidgets'] 71"""list: which subpackage to import for Qt5 API.""" 72 73QT_API_VALUES = ['pyqt', 'pyqt5', 'pyside', 'pyside2'] 74"""list: values for QT_API environment variable used by QtPy.""" 75 76QT_LIB_VALUES = ['PyQt', 'PyQt5', 'PySide', 'PySide2'] 77"""list: values for PYQTGRAPH_QT_LIB environment variable used by PyQtGraph.""" 78 79QT_BINDING = 'Not set or nonexistent' 80"""str: Qt binding in use.""" 81 82QT_ABSTRACTION = 'Not set or nonexistent' 83"""str: Qt abstraction layer in use.""" 84 85 86def _logger(): 87 return logging.getLogger('qdarkstyle') 88 89 90def _qt_wrapper_import(qt_api): 91 """ 92 Check if Qt API defined can be imported. 93 94 :param qt_api: Qt API string to test import 95 96 :return load function fot given qt_api, otherwise empty string 97 98 """ 99 qt_wrapper = '' 100 loader = "" 101 102 try: 103 if qt_api == 'PyQt' or qt_api == 'pyqt': 104 import PyQt4 105 qt_wrapper = 'PyQt4' 106 loader = load_stylesheet_pyqt() 107 elif qt_api == 'PyQt5' or qt_api == 'pyqt5': 108 import PyQt5 109 qt_wrapper = 'PyQt5' 110 loader = load_stylesheet_pyqt5() 111 elif qt_api == 'PySide' or qt_api == 'pyside': 112 import PySide 113 qt_wrapper = 'PySide' 114 loader = load_stylesheet_pyside() 115 elif qt_api == 'PySide2' or qt_api == 'pyside2': 116 import PySide2 117 qt_wrapper = 'PySide2' 118 loader = load_stylesheet_pyside2() 119 except ImportError as err: 120 _logger().error("Impossible import Qt wrapper.\n %s", str(err)) 121 else: 122 _logger().info("Using Qt wrapper = %s ", qt_wrapper) 123 QT_BINDING = qt_wrapper 124 finally: 125 return loader 126 127 128def load_stylesheet_from_environment(is_pyqtgraph=False): 129 """ 130 Load the stylesheet from QT_API (or PYQTGRAPH_QT_LIB) environment variable. 131 132 :param is_pyqtgraph: True if it is to be set using PYQTGRAPH_QT_LIB 133 134 :raise KeyError: if QT_API/PYQTGRAPH_QT_LIB does not exist 135 136 :return the stylesheet string 137 """ 138 warnings.warn( 139 "load_stylesheet_from_environment() will be deprecated in version 3," 140 "use load_stylesheet()", 141 PendingDeprecationWarning 142 ) 143 qt_api = '' 144 pyqtgraph_qt_lib = '' 145 146 loader = "" 147 148 # Get values from QT_API 149 try: 150 qt_api = os.environ['QT_API'] 151 except KeyError as err: 152 # Log this error just if using QT_API 153 if not is_pyqtgraph: 154 _logger().error("QT_API does not exist, do os.environ['QT_API']= " 155 "and choose one option from %s", QT_API_VALUES) 156 else: 157 if not is_pyqtgraph: 158 if qt_api in QT_API_VALUES: 159 QT_ABSTRACTION = "qtpy" 160 _logger().info("Found QT_API='%s'", qt_api) 161 loader = _qt_wrapper_import(qt_api) 162 else: 163 # Raise this error because the function need this key/value 164 raise KeyError("QT_API=%s is unknown, please use a value " 165 "from %s", 166 (qt_api, QT_API_VALUES)) 167 168 # Get values from PYQTGRAPH_QT_LIB 169 try: 170 pyqtgraph_qt_lib = os.environ['PYQTGRAPH_QT_LIB'] 171 except KeyError as err: 172 # Log this error just if using PYQTGRAPH_QT_LIB 173 if is_pyqtgraph: 174 _logger().error("PYQTGRAP_QT_API does not exist, do " 175 "os.environ['PYQTGRAPH_QT_LIB']= " 176 "and choose one option from %s", 177 QT_LIB_VALUES) 178 else: 179 180 if is_pyqtgraph: 181 if pyqtgraph_qt_lib in QT_LIB_VALUES: 182 QT_ABSTRACTION = "pyqtgraph" 183 _logger().info("Found PYQTGRAPH_QT_LIB='%s'", pyqtgraph_qt_lib) 184 loader = _qt_wrapper_import(pyqtgraph_qt_lib) 185 else: 186 # Raise this error because the function need this key/value 187 raise KeyError("PYQTGRAPH_QT_LIB=%s is unknown, please use a " 188 "value from %s", ( 189 pyqtgraph_qt_lib, 190 QT_LIB_VALUES)) 191 192 # Just a warning if both are set but differs each other 193 if qt_api and pyqtgraph_qt_lib: 194 if qt_api != pyqtgraph_qt_lib.lower(): 195 _logger().warning("Both QT_API=%s and PYQTGRAPH_QT_LIB=%s are set, " 196 "but with different values, this could cause " 197 "some issues if using them in the same project!", 198 qt_api, pyqtgraph_qt_lib) 199 200 return loader 201 202 203def load_stylesheet(pyside=True): 204 """ 205 Load the stylesheet. Takes care of importing the rc module. 206 207 :param pyside: True to load the pyside rc file, False to load the PyQt rc file 208 209 :return the stylesheet string 210 """ 211 warnings.warn( 212 "load_stylesheet() will not receive pyside parameter in version 3. " 213 "Set QtPy environment variable to specify the Qt binding insteady.", 214 FutureWarning 215 ) 216 # Smart import of the rc file 217 218 pyside_ver = None 219 220 if pyside: 221 222 # Detect the PySide version available 223 try: 224 import PySide 225 except ImportError: # Compatible with py27 226 import PySide2 227 pyside_ver = 2 228 else: 229 pyside_ver = 1 230 231 if pyside_ver == 1: 232 import qdarkstyle.pyside_style_rc 233 else: 234 import qdarkstyle.pyside2_style_rc 235 else: 236 import qdarkstyle.pyqt_style_rc 237 238 # Load the stylesheet content from resources 239 if not pyside: 240 from PyQt4.QtCore import QFile, QTextStream 241 else: 242 if pyside_ver == 1: 243 from PySide.QtCore import QFile, QTextStream 244 else: 245 from PySide2.QtCore import QFile, QTextStream 246 247 f = QFile(":qdarkstyle/style.qss") 248 if not f.exists(): 249 _logger().error("Unable to load stylesheet, file not found in " 250 "resources") 251 return "" 252 else: 253 f.open(QFile.ReadOnly | QFile.Text) 254 ts = QTextStream(f) 255 stylesheet = ts.readAll() 256 if platform.system().lower() == 'darwin': # see issue #12 on github 257 mac_fix = ''' 258 QDockWidget::title 259 { 260 background-color: #32414B; 261 text-align: center; 262 height: 12px; 263 } 264 ''' 265 stylesheet += mac_fix 266 return stylesheet 267 268 269def load_stylesheet_pyside(): 270 """ 271 Load the stylesheet for use in a pyside application. 272 273 :return the stylesheet string 274 """ 275 warnings.warn( 276 "load_stylesheet_pyside() will be deprecated in version 3," 277 "set QtPy environment variable to specify the Qt binding and " 278 "use load_stylesheet()", 279 PendingDeprecationWarning 280 ) 281 return load_stylesheet(pyside=True) 282 283 284def load_stylesheet_pyside2(): 285 """ 286 Load the stylesheet for use in a pyside2 application. 287 288 :raise NotImplementedError: Because it is not supported yet 289 """ 290 warnings.warn( 291 "load_stylesheet_pyside2() will be deprecated in version 3," 292 "set QtPy environment variable to specify the Qt binding and " 293 "use load_stylesheet()", 294 PendingDeprecationWarning 295 ) 296 return load_stylesheet(pyside=True) 297 298 299def load_stylesheet_pyqt(): 300 """ 301 Load the stylesheet for use in a pyqt4 application. 302 303 :return the stylesheet string 304 """ 305 warnings.warn( 306 "load_stylesheet_pyqt() will be deprecated in version 3," 307 "set QtPy environment variable to specify the Qt binding and " 308 "use load_stylesheet()", 309 PendingDeprecationWarning 310 ) 311 return load_stylesheet(pyside=False) 312 313 314def load_stylesheet_pyqt5(): 315 """ 316 Load the stylesheet for use in a pyqt5 application. 317 318 :param pyside: True to load the pyside rc file, False to load the PyQt rc file 319 320 :return the stylesheet string 321 """ 322 warnings.warn( 323 "load_stylesheet_pyqt5() will be deprecated in version 3," 324 "set QtPy environment variable to specify the Qt binding and " 325 "use load_stylesheet()", 326 PendingDeprecationWarning 327 ) 328 # Smart import of the rc file 329 import qdarkstyle.pyqt5_style_rc 330 331 # Load the stylesheet content from resources 332 from PyQt5.QtCore import QFile, QTextStream 333 334 f = QFile(":qdarkstyle/style.qss") 335 if not f.exists(): 336 _logger().error("Unable to load stylesheet, file not found in " 337 "resources") 338 return "" 339 else: 340 f.open(QFile.ReadOnly | QFile.Text) 341 ts = QTextStream(f) 342 stylesheet = ts.readAll() 343 if platform.system().lower() == 'darwin': # see issue #12 on github 344 mac_fix = ''' 345 QDockWidget::title 346 { 347 background-color: #32414B; 348 text-align: center; 349 height: 12px; 350 } 351 ''' 352 stylesheet += mac_fix 353 return stylesheet 354 355 356def information(): 357 """Get system and runtime information.""" 358 info = [] 359 qt_api = '' 360 qt_lib = '' 361 qt_bin = '' 362 363 try: 364 qt_api = os.environ['QT_API'] 365 except KeyError: 366 qt_api = 'Not set or nonexistent' 367 368 try: 369 from Qt import __binding__ 370 except Exception: 371 # It should be (KeyError, ModuleNotFoundError, ImportError) 372 # but each python version have a different one, and not define others 373 qt_lib = 'Not set or nonexistent' 374 else: 375 qt_lib = __binding__ 376 377 try: 378 qt_bin = os.environ['PYQTGRAPH_QT_LIB'] 379 except KeyError: 380 qt_bin = 'Not set or nonexistent' 381 382 info.append('QDarkStyle: %s' % __version__) 383 info.append('OS: %s %s %s' % (platform.system(), platform.release(), platform.machine())) 384 info.append('Platform: %s' % sys.platform) 385 info.append('Python: %s' % '.'.join(str(e) for e in sys.version_info[:])) 386 info.append('Python API: %s' % sys.api_version) 387 388 info.append('Binding in use: %s' % QT_BINDING) 389 info.append('Abstraction in use: %s' % QT_ABSTRACTION) 390 391 info.append('qtpy (QT_API): %s' % qt_api) 392 info.append('pyqtgraph (PYQTGRAPH_QT_LIB): %s' % qt_lib) 393 info.append('Qt.py (__binding__): %s' % qt_bin) 394 395 return info 396 397 398def qt_bindings(): 399 """Return a list of qt bindings available.""" 400 return _check_imports(import_list=QT_BINDINGS) 401 402 403def qt_abstractions(): 404 """Return a list of qt abstraction layers available.""" 405 return _check_imports(import_list=QT_ABSTRACTIONS) 406 407 408def _check_imports(import_list): 409 """Return a list of imports available.""" 410 411 # Disable warnings here 412 warnings.filterwarnings("ignore") 413 414 import_list_return = copy.deepcopy(import_list) 415 # Using import_list_return var in for, does not work in py2.7 416 # when removing the element, it reflects on for list 417 # so it skips next element 418 for current_import in import_list: 419 420 spec = True 421 # Copy the sys path to make sure to not insert anything 422 sys_path = sys.path 423 424 # Check import 425 if sys.version_info >= (3, 4): 426 spec = importlib.util.find_spec(current_import) 427 else: 428 try: 429 __import__(current_import) 430 except RuntimeWarning: 431 spec = True 432 except Exception: 433 spec = None 434 else: 435 spec = True 436 437 if spec is None: 438 # Remove if not available 439 import_list_return.remove(current_import) 440 441 # Restore sys path 442 sys.path = sys_path 443 444 # Restore warnings 445 warnings.resetwarnings() 446 447 return import_list_return 448 449 450def _import_qt_modules_from(use_binding='pyqt5', use_abstraction='qtpy'): 451 """New approach to import modules using importlib.""" 452 453 if not sys.version_info >= (3, 4): 454 print('Function not available for Python < 3.4') 455 456 spec_binding = importlib.util.find_spec(use_binding) 457 spec_abstraction = importlib.util.find_spec(use_abstraction) 458 459 if spec_binding is None: 460 print("Cannot find Qt binding: ", use_binding) 461 else: 462 module = importlib.util.module_from_spec(spec_binding) 463 spec.loader.exec_module(module) 464 # Adding the module to sys.modules is optional. 465 sys.modules[name] = module 466 467 if spec_abstraction is None: 468 print("Cannot find Qt abstraction layer: ", use_abstraction) 469 else: 470 module = importlib.util.module_from_spec(spec) 471 spec.loader.exec_module(module) 472 # Adding the module to sys.modules is optional. 473 sys.modules[name] = module 474