Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions rascal2/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

from PyQt6 import QtWidgets

from rascal2.config import MatlabHelper, handle_scaling, setup_logging
from rascal2.settings import get_colour_scheme
from rascal2.config import SETTINGS, MatlabHelper, handle_scaling, setup_logging
from rascal2.settings import change_ui_style
from rascal2.theme import THEMES, set_stylesheet
from rascal2.ui.view import MainWindowView

Expand All @@ -21,7 +21,7 @@ def ui_execute(splash):
app = QtWidgets.QApplication.instance()
app.setStyle("Fusion")
app.installEventFilter(THEMES)
app.styleHints().setColorScheme(get_colour_scheme())
change_ui_style(SETTINGS.style)
set_stylesheet(app)

window = MainWindowView()
Expand Down
29 changes: 12 additions & 17 deletions rascal2/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,27 +229,22 @@ def update_recent_projects(path: str | None = None) -> list[str]:
return new_recent_projects


def get_colour_scheme():
"""Get the currently selected colour scheme and converts it to relevant Qt colour scheme flag."""
colour_scheme = get_global_settings().value("General/style", "system")
match colour_scheme:
case "system":
colour_scheme_default = QtCore.Qt.ColorScheme.Unknown
case "light":
colour_scheme_default = QtCore.Qt.ColorScheme.Light
case "dark":
colour_scheme_default = QtCore.Qt.ColorScheme.Dark
return colour_scheme_default
def change_ui_style(style: Styles) -> None:
"""Change the style of the app GUI to the given style.


def change_ui_style(style: Styles = Styles.System) -> None:
"""Change the style of the app GUI to the given style."""
Parameters
----------
style : Styles
The style to change to.
"""
app = QtWidgets.QApplication.instance()
match style:
case "system":
colour_scheme = QtCore.Qt.ColorScheme.Unknown
case "light":
colour_scheme = QtCore.Qt.ColorScheme.Light
case "dark":
colour_scheme = QtCore.Qt.ColorScheme.Dark
app.styleHints().setColorScheme(colour_scheme)
case _:
colour_scheme = QtCore.Qt.ColorScheme.Unknown
style_hints = app.styleHints()
style_hints.setProperty("colour_scheme", colour_scheme)
style_hints.setColorScheme(colour_scheme)
6 changes: 6 additions & 0 deletions rascal2/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,12 @@ QTableView QPushButton {
border: transparent;
}

#TableButton {
border: transparent;
padding:0;
margin:0;
}

/*****************************
QMenuBar Styles
*****************************/
Expand Down
72 changes: 47 additions & 25 deletions rascal2/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,43 @@
from rascal2.paths import IMAGES_PATH, STATIC_PATH, path_for


def get_correct_qt_color_scheme():
"""Get the correct colour scheme."""
# On linux PyQt 6.9.1 sometimes returns incorrect scheme.
# A workaround is to store the scheme to ensure it is correct.
# This issue is fixed in PyQt 6.10 but 6.10 is not available on IDAaas
# due to older GLIBC, so we are stuck on 6.9.1 for now.
app = QtWidgets.QApplication.instance()
scheme = app.styleHints().property("colour_scheme")
if scheme is None:
return QtCore.Qt.ColorScheme.Light
elif scheme == QtCore.Qt.ColorScheme.Unknown:
return app.styleHints().colorScheme()
return scheme


def colorize_icon(icon, colour):
"""Change icon colour to given colour.

Parameters
----------
icon : QtGui.QIcon
The icon to colourize
colour : QtGui.QColor
The new colour of the icon
"""
pixmap = icon.pixmap(200, 200)
mask = pixmap.createMaskFromColor(QtGui.QColor(QtCore.Qt.GlobalColor.transparent), QtCore.Qt.MaskMode.MaskInColor)
pixmap.fill(colour)
pixmap.setMask(mask)

return QtGui.QIcon(pixmap)


def set_stylesheet(app):
"""Set the stylesheet of the app according to the given style.css file if available."""
with suppress(FileNotFoundError), open(STATIC_PATH / "style.css") as stylesheet:
app.setStyleSheet("* {}") # This is a hack to force PyQt 6.9.1 to update the palette on Linux
palette = app.palette()
replacements = {
"@Path": IMAGES_PATH.as_posix(),
Expand All @@ -26,13 +60,13 @@ class ThemeManager(QtCore.QObject):

def __init__(self):
super().__init__()
scheme = QtWidgets.QApplication.styleHints().colorScheme()
scheme = get_correct_qt_color_scheme()
self.cur_style = "light" if scheme == QtCore.Qt.ColorScheme.Light else "dark"

def eventFilter(self, obj, event):
"""Catch close event for overlay widget."""
if isinstance(obj, QtWidgets.QApplication) and event.type() == QtCore.QEvent.Type.ApplicationPaletteChange:
scheme = QtWidgets.QApplication.styleHints().colorScheme()
scheme = get_correct_qt_color_scheme()
style = "light" if scheme == QtCore.Qt.ColorScheme.Light else "dark"
if style != self.cur_style:
set_stylesheet(obj)
Expand All @@ -45,46 +79,34 @@ def eventFilter(self, obj, event):


class IconEngine(QtGui.QIconEngine):
"""Create the icons for the application."""
"""Load the appropriate icon for the application theme.

Parameters
----------
filename : str
The path of the icon
"""

def __init__(self, filename):
super().__init__()
self.name = re.split(r"-dark.png|-light.png", filename)[0]
self.update_icon()

def update_icon(self):
"""Update the Icon."""
scheme = QtWidgets.QApplication.styleHints().colorScheme()
"""Update the icon to match the current theme."""
scheme = get_correct_qt_color_scheme()
style = "light" if scheme == QtCore.Qt.ColorScheme.Light else "dark"

filename = f"{self.name}-light.png" if style == "light" else f"{self.name}-dark.png"
path = path_for(filename)
self.icon = QtGui.QIcon(path)

def pixmap(self, size, mode, state):
"""Create the pixmap.

:param size: size
:type size: QSize
:param mode: mode
:type mode: QIcon.Mode
:param state: state
:type state: QIcon.State
"""
"""Create the pixmap."""
self.update_icon()
return self.icon.pixmap(size, mode, state)

def paint(self, painter, rect, mode, state):
"""Paint the icon.

:param painter: painter
:type painter: QPainter
:param rect: rect
:type rect: QRect
:param mode: mode
:type mode: QIcon.Mode
:param state: state
:type state: QIcon.State
"""
"""Paint the icon."""
self.update_icon()
return self.icon.pixmap.paint(painter, rect, mode, state)
20 changes: 0 additions & 20 deletions rascal2/ui/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,6 @@
MAIN_WINDOW_TITLE = "RasCAL-2"


class TitleProxyStyle(QtWidgets.QProxyStyle):
"""Set style of Title bar."""

def drawComplexControl(self, control, option, painter, widget=None):
if control == QtWidgets.QStyle.ComplexControl.CC_TitleBar:
option.palette.setBrush(QtGui.QPalette.ColorRole.Window, option.palette.button().color())
super().drawComplexControl(control, option, painter, widget)


class MdiSubWindow(QtWidgets.QMdiSubWindow):
"""Class to handle MDI sub-windows."""

def __init__(self, parent=None, flags=QtCore.Qt.WindowType.Widget):
super().__init__(parent, flags)
style = TitleProxyStyle(self.style())
self.setStyle(style)


class MainWindowView(QtWidgets.QMainWindow):
"""Creates the main view for the RasCAL application."""

Expand Down Expand Up @@ -341,8 +323,6 @@ def setup_mdi(self):
window = self.mdi.addSubWindow(
widget, QtCore.Qt.WindowType.WindowMinMaxButtonsHint | QtCore.Qt.WindowType.WindowTitleHint
)
# window = MdiSubWindow(self.mdi)
# window.setWidget(widget)
window.setWindowTitle(title)
self.reset_mdi_layout()
self.startup_dlg = self.takeCentralWidget()
Expand Down
11 changes: 8 additions & 3 deletions rascal2/widgets/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from PyQt6 import QtCore, QtGui, QtWidgets

from rascal2.config import SETTINGS
from rascal2.theme import IconEngine
from rascal2.theme import IconEngine, colorize_icon, get_correct_qt_color_scheme
from rascal2.widgets.inputs import MultiSelectComboBox, ProgressButton


Expand Down Expand Up @@ -213,6 +213,7 @@ def __init__(self, parent):
super().__init__(parent)
self.parent = parent

self.toolbar = None
self.current_plot_data = None

main_layout = QtWidgets.QHBoxLayout()
Expand Down Expand Up @@ -391,12 +392,16 @@ def export(self):
self.figure.savefig(filepath, facecolor=SETTINGS.export_background_colour, dpi=dpi)

def changeEvent(self, event):
if event.type() == QtCore.QEvent.Type.PaletteChange:
scheme = QtWidgets.QApplication.styleHints().colorScheme()
if self.toolbar is not None and event.type() == QtCore.QEvent.Type.PaletteChange:
scheme = get_correct_qt_color_scheme()
if scheme == QtCore.Qt.ColorScheme.Light:
matplotlib.style.use("default")
else:
matplotlib.style.use("dark_background")
for name in ["pan", "zoom"]:
icon_color = self.palette().text().color()
icon = self.toolbar._actions[name].icon()
Comment thread
StephenNneji marked this conversation as resolved.
self.toolbar._actions[name].setIcon(colorize_icon(icon, icon_color))

super().changeEvent(event)

Expand Down
3 changes: 2 additions & 1 deletion rascal2/widgets/project/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ def make_delete_button(self, index):
The row to be deleted.

"""
button = QtWidgets.QPushButton(icon=QtGui.QIcon(IconEngine("delete-light.png")))
# The QTableView button style stopped working in PyQT 6.9, added name as workaround.
button = QtWidgets.QPushButton(icon=QtGui.QIcon(IconEngine("delete-light.png")), objectName="TableButton")
button.resize(button.sizeHint().width(), button.sizeHint().width())
button.pressed.connect(lambda: self.delete_item(index))

Expand Down
3 changes: 2 additions & 1 deletion rascal2/widgets/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from rascal2.dialogs.startup_dialog import LoadDialog, LoadR1Dialog, NewProjectDialog
from rascal2.paths import path_for
from rascal2.theme import get_correct_qt_color_scheme


class StartUpWidget(QtWidgets.QWidget):
Expand Down Expand Up @@ -81,7 +82,7 @@ def create_labels(self) -> None:

def changeEvent(self, event):
if event.type() == QtCore.QEvent.Type.PaletteChange:
scheme = QtWidgets.QApplication.styleHints().colorScheme()
scheme = get_correct_qt_color_scheme()
if scheme == QtCore.Qt.ColorScheme.Dark:
self.banner_label.setPixmap(QtGui.QPixmap(path_for("banner-dark.png")))
self.footer_label.setPixmap(QtGui.QPixmap(path_for("footer-dark.png")))
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PyInstaller==6.9.0
PyQt6==6.10.2
PyQt6-Qt6==6.10.2
ratapi==0.0.0.dev14
PyQt6==6.9.1
PyQt6-Qt6==6.9.2
ratapi==0.0.0.dev15
pydantic==2.8.2
PyQt6-QScintilla==2.14.1
nexusformat==1.0.7
Expand Down
Loading