# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guidata/__init__.py for details)
"""
qthelpers
---------
The ``guidata.qthelpers`` module provides helper functions for developing
easily Qt-based graphical user interfaces.
"""
import os
import os.path as osp
import sys
from qtpy.QtCore import Qt
from qtpy.QtGui import QColor, QIcon, QKeySequence
from qtpy.QtWidgets import (
QAction,
QApplication,
QGroupBox,
QHBoxLayout,
QLabel,
QLineEdit,
QMenu,
QPushButton,
QStyle,
QToolButton,
QVBoxLayout,
QWidget,
)
from guidata.configtools import get_icon
# Local imports:
from guidata.external import darkdetect
[docs]def is_dark_mode():
"""Return True if current color mode is dark mode"""
try:
return os.environ["QT_COLOR_MODE"].lower() == "dark"
except KeyError:
return darkdetect.isDark()
[docs]def win32_fix_title_bar_background(widget):
"""Fix window title bar background for Windows 10+ dark theme"""
if os.name != "nt" or not is_dark_mode() or sys.maxsize == 2**31-1:
return
import ctypes
from ctypes import wintypes
class ACCENTPOLICY(ctypes.Structure):
_fields_ = [
("AccentState", ctypes.c_uint),
("AccentFlags", ctypes.c_uint),
("GradientColor", ctypes.c_uint),
("AnimationId", ctypes.c_uint),
]
class WINDOWCOMPOSITIONATTRIBDATA(ctypes.Structure):
_fields_ = [
("Attribute", ctypes.c_int),
("Data", ctypes.POINTER(ctypes.c_int)),
("SizeOfData", ctypes.c_size_t),
]
accent = ACCENTPOLICY()
accent.AccentState = 1 # Default window Blur #ACCENT_ENABLE_BLURBEHIND
data = WINDOWCOMPOSITIONATTRIBDATA()
data.Attribute = 26 # WCA_USEDARKMODECOLORS
data.SizeOfData = ctypes.sizeof(accent)
data.Data = ctypes.cast(ctypes.pointer(accent), ctypes.POINTER(ctypes.c_int))
set_win_cpa = ctypes.windll.user32.SetWindowCompositionAttribute
set_win_cpa.argtypes = (wintypes.HWND, WINDOWCOMPOSITIONATTRIBDATA)
set_win_cpa.restype = ctypes.c_int
set_win_cpa(int(widget.winId()), data)
[docs]def text_to_qcolor(text):
"""Create a QColor from specified string"""
color = QColor()
if text is not None and text.startswith("#") and len(text) == 7:
correct = "#0123456789abcdef"
for char in text:
if char.lower() not in correct:
return color
elif text not in list(QColor.colorNames()):
return color
color.setNamedColor(text)
return color
[docs]def create_action(
parent,
title,
triggered=None,
toggled=None,
shortcut=None,
icon=None,
tip=None,
checkable=None,
context=Qt.WindowShortcut,
enabled=None,
):
"""
Create a new QAction
"""
action = QAction(title, parent)
if triggered:
if checkable:
action.triggered.connect(triggered)
else:
action.triggered.connect(lambda checked=False: triggered())
if checkable is not None:
# Action may be checkable even if the toggled signal is not connected
action.setCheckable(checkable)
if toggled:
action.toggled.connect(toggled)
action.setCheckable(True)
if icon is not None:
assert isinstance(icon, QIcon)
action.setIcon(icon)
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
action.setStatusTip(tip)
if enabled is not None:
action.setEnabled(enabled)
action.setShortcutContext(context)
return action
[docs]def create_groupbox(
parent, title=None, toggled=None, checked=None, flat=False, layout=None
):
"""Create a QGroupBox"""
if title is None:
group = QGroupBox(parent)
else:
group = QGroupBox(title, parent)
group.setFlat(flat)
if toggled is not None:
group.setCheckable(True)
if checked is not None:
group.setChecked(checked)
group.toggled.connect(toggled)
if layout is not None:
group.setLayout(layout)
return group
[docs]def keybinding(attr):
"""Return keybinding"""
ks = getattr(QKeySequence, attr)
return QKeySequence.keyBindings(ks)[0].toString()
[docs]def add_separator(target):
"""Add separator to target only if last action is not a separator"""
target_actions = list(target.actions())
if target_actions:
if not target_actions[-1].isSeparator():
target.addSeparator()
[docs]def add_actions(target, actions):
"""
Add actions (list of QAction instances) to target (menu, toolbar)
"""
for action in actions:
if isinstance(action, QAction):
target.addAction(action)
elif isinstance(action, QMenu):
target.addMenu(action)
elif action is None:
add_separator(target)
def _process_mime_path(path, extlist):
if path.startswith(r"file://"):
if os.name == "nt":
# On Windows platforms, a local path reads: file:///c:/...
# and a UNC based path reads like: file://server/share
if path.startswith(r"file:///"): # this is a local path
path = path[8:]
else: # this is a unc path
path = path[5:]
else:
path = path[7:]
path = path.replace("%5C", os.sep) # Transforming backslashes
if osp.exists(path):
if extlist is None or osp.splitext(path)[1] in extlist:
return path
[docs]def mimedata2url(source, extlist=None):
"""
Extract url list from MIME data
extlist: for example ('.py', '.pyw')
"""
pathlist = []
if source.hasUrls():
for url in source.urls():
path = _process_mime_path(str(url.toString()), extlist)
if path is not None:
pathlist.append(path)
elif source.hasText():
for rawpath in str(source.text()).splitlines():
path = _process_mime_path(rawpath, extlist)
if path is not None:
pathlist.append(path)
if pathlist:
return pathlist
[docs]def get_std_icon(name, size=None):
"""
Get standard platform icon
Call 'show_std_icons()' for details
"""
if not name.startswith("SP_"):
name = "SP_" + name
icon = QWidget().style().standardIcon(getattr(QStyle, name))
if size is None:
return icon
else:
return QIcon(icon.pixmap(size, size))
[docs]class ShowStdIcons(QWidget):
"""
Dialog showing standard icons
"""
def __init__(self, parent):
QWidget.__init__(self, parent)
layout = QHBoxLayout()
row_nb = 14
cindex = 0
col_layout = QVBoxLayout()
for child in dir(QStyle):
if child.startswith("SP_"):
if cindex == 0:
col_layout = QVBoxLayout()
icon_layout = QHBoxLayout()
icon = get_std_icon(child)
label = QLabel()
label.setPixmap(icon.pixmap(32, 32))
icon_layout.addWidget(label)
icon_layout.addWidget(QLineEdit(child.replace("SP_", "")))
col_layout.addLayout(icon_layout)
cindex = (cindex + 1) % row_nb
if cindex == 0:
layout.addLayout(col_layout)
self.setLayout(layout)
self.setWindowTitle("Standard Platform Icons")
self.setWindowIcon(get_std_icon("TitleBarMenuButton"))
[docs]def show_std_icons():
"""
Show all standard Icons
"""
app = QApplication(sys.argv)
dialog = ShowStdIcons(None)
dialog.show()
sys.exit(app.exec_())
if __name__ == "__main__":
show_std_icons()