Skip to content

Commit

Permalink
Refactor ThemeManager:
Browse files Browse the repository at this point in the history
- Improve thread safety with lock
- Keep 1 reference to manager
- Remove all imports the manager (except 1)
- Added logging if manager is not available, or themes are not found
- This fixes a freeze in Windows on some systems during the launch of OpenShot, related to oleaut32.dll.
  • Loading branch information
jonoomph committed Jul 7, 2024
1 parent 891421f commit f10ec7d
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 68 deletions.
7 changes: 3 additions & 4 deletions src/classes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def __init__(self, *args, **kwargs):

# Instantiate Theme Manager (Singleton)
from themes.manager import ThemeManager
ThemeManager(self)
self.theme_manager = ThemeManager(self)

def show_environment(self, info, openshot):
log = self.log
Expand Down Expand Up @@ -256,9 +256,8 @@ def gui(self):
self.window = MainWindow()

# Instantiate Theme Manager (Singleton)
from themes.manager import ThemeManager, ThemeName
theme_enum = ThemeName.find_by_name(self.settings.get("theme"))
theme = ThemeManager().apply_theme(theme_enum)
theme_name = self.settings.get("theme")
theme = self.theme_manager.apply_theme(theme_name)

# Update theme in settings
self.settings.set("theme", theme.name)
Expand Down
24 changes: 11 additions & 13 deletions src/themes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@

from classes import ui_util
from classes.info import PATH
from themes.manager import ThemeManager


class BaseTheme:
Expand Down Expand Up @@ -150,26 +149,25 @@ def set_toolbar_buttons(self, toolbar, icon_size=24, settings=None):
if button_stylesheet:
button.setStyleSheet(button_stylesheet)


def apply_theme(self):
# Get initial style and palette
manager = ThemeManager()

# Apply the stylesheet to the entire application
if manager.original_style:
self.app.setStyle(manager.original_style)
if manager.original_palette:
self.app.setPalette(manager.original_palette)
from classes import info
from classes.logger import log
from PyQt5.QtGui import QFont, QFontDatabase

if not self.app.theme_manager:
log.warning("ThemeManager not initialized yet. Skip applying a theme.")

if self.app.theme_manager.original_style:
self.app.setStyle(self.app.theme_manager.original_style)
if self.app.theme_manager.original_palette:
self.app.setPalette(self.app.theme_manager.original_palette)
self.app.setStyleSheet(self.style_sheet)

# Hide main window status bar
if hasattr(self.app, "window") and hasattr(self.app.window, "statusBar"):
self.app.window.statusBar.hide()

from classes import info
from classes.logger import log
from PyQt5.QtGui import QFont, QFontDatabase

# Load embedded font
font_path = os.path.join(info.IMAGES_PATH, "fonts", "Ubuntu-R.ttf")
if os.path.exists(font_path):
Expand Down
26 changes: 16 additions & 10 deletions src/themes/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""

import threading
from enum import Enum


Expand All @@ -51,31 +52,36 @@ def find_by_name(name):
class ThemeManager:
"""Singleton Theme Manager class, used to easily switch between UI themes"""
_instance = None
_lock = threading.Lock()

def __new__(cls, app=None):
"""Override new method, so the same instance is always returned (i.e. singleton)"""
if cls._instance is None:
cls._instance = super(ThemeManager, cls).__new__(cls)
cls._instance.app = app
cls._instance.original_style = app.style().objectName() if app else None
cls._instance.original_palette = app.palette() if app else None
cls._instance.current_theme = None
with cls._lock:
if cls._instance is None:
cls._instance = super(ThemeManager, cls).__new__(cls)
cls._instance.app = app
cls._instance.original_style = app.style().objectName() if app else None
cls._instance.original_palette = app.palette() if app else None
cls._instance.current_theme = None
return cls._instance

def apply_theme(self, theme_name):
def apply_theme(self, name):
"""Apply a new UI theme. Expects a ThemeName ENUM as the arg."""
if theme_name == ThemeName.HUMANITY_DARK:
theme_enum = ThemeName.find_by_name(name)

if theme_enum == ThemeName.HUMANITY_DARK:
from themes.humanity.theme import HumanityDarkTheme
self.current_theme = HumanityDarkTheme(self.app)
elif theme_name == ThemeName.RETRO:
elif theme_enum == ThemeName.RETRO:
from themes.humanity.theme import Retro
self.current_theme = Retro(self.app)
elif theme_name == ThemeName.COSMIC:
elif theme_enum == ThemeName.COSMIC:
from themes.cosmic.theme import CosmicTheme
self.current_theme = CosmicTheme(self.app)

# Set name on theme instance
self.current_theme.name = theme_name.value
self.current_theme.name = theme_enum.value

# Apply theme
self.current_theme.apply_theme()
Expand Down
45 changes: 24 additions & 21 deletions src/windows/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,10 +1134,10 @@ def onPlayCallback(self):
"""Handle when playback is started"""
# Set icon on Play button
if self.initialized:
from themes.manager import ThemeManager
theme = ThemeManager().get_current_theme()
if theme:
theme.togglePlayIcon(True)
if get_app().theme_manager:
theme = get_app().theme_manager.get_current_theme()
if theme:
theme.togglePlayIcon(True)

def onPauseCallback(self):
"""Handle when playback is paused"""
Expand All @@ -1146,10 +1146,10 @@ def onPauseCallback(self):

# Set icon on Pause button
if self.initialized:
from themes.manager import ThemeManager
theme = ThemeManager().get_current_theme()
if theme:
theme.togglePlayIcon(False)
if get_app().theme_manager:
theme = get_app().theme_manager.get_current_theme()
if theme:
theme.togglePlayIcon(False)

def actionSaveFrame_trigger(self, checked=True):
log.info("actionSaveFrame_trigger")
Expand Down Expand Up @@ -2992,19 +2992,22 @@ def foundCurrentVersion(self, version):
# Add toolbar button for non-cosmic dusk themes
# Cosmic dusk has a hidden toolbar button which is made visible
# by the setVisible() call above this
from themes.manager import ThemeManager, ThemeName
theme = ThemeManager().get_current_theme()
if theme and theme.name != ThemeName.COSMIC.value:
# Add spacer and 'New Version Available' toolbar button (default hidden)
spacer = QWidget(self)
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.toolBar.addWidget(spacer)

# Add update available button (with icon and text)
updateButton = QToolButton(self)
updateButton.setDefaultAction(self.actionUpdate)
updateButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toolBar.addWidget(updateButton)
if get_app().theme_manager:
from themes.manager import ThemeName
theme = get_app().theme_manager.get_current_theme()
if theme and theme.name != ThemeName.COSMIC.value:
# Add spacer and 'New Version Available' toolbar button (default hidden)
spacer = QWidget(self)
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.toolBar.addWidget(spacer)

# Add update available button (with icon and text)
updateButton = QToolButton(self)
updateButton.setDefaultAction(self.actionUpdate)
updateButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toolBar.addWidget(updateButton)
else:
log.warning("No ThemeManager loaded yet. Skip update available button.")

# Initialize sentry exception tracing (now that we know the current version)
from classes import sentry
Expand Down
5 changes: 2 additions & 3 deletions src/windows/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,9 +566,8 @@ def dropdown_index_changed(self, widget, param, index):

if param["setting"] == "theme":
# Apply selected theme to UI
from themes.manager import ThemeManager, ThemeName
theme_enum = ThemeName.find_by_name(value)
ThemeManager().apply_theme(theme_enum)
if get_app().theme_manager:
get_app().theme_manager.apply_theme(value)

# Check for restart
self.check_for_restart(param)
Expand Down
15 changes: 9 additions & 6 deletions src/windows/video_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
from classes.logger import log
from classes.app import get_app
from classes.query import Clip, Effect
from themes.manager import ThemeManager


class VideoWidget(QWidget, updates.UpdateInterface):
Expand Down Expand Up @@ -331,11 +330,15 @@ def paintEvent(self, event, *args):
True)

# Get theme colors (if any)
theme = ThemeManager().get_current_theme()
if not theme:
return
background_color = theme.get_color(".video_widget", "background-color")
painter.fillRect(event.rect(), background_color)
if get_app().theme_manager:
theme = get_app().theme_manager.get_current_theme()
if not theme:
log.warning("No theme loaded yet. Skip rendering video preview widget.")
return
background_color = theme.get_color(".video_widget", "background-color")
painter.fillRect(event.rect(), background_color)
else:
log.warning("No ThemeManager loaded yet. Skip rendering video preview widget.")

# Find centered viewport
viewport_rect = self.centeredViewport(self.width(), self.height())
Expand Down
15 changes: 9 additions & 6 deletions src/windows/views/properties_tableview.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@

from windows.models.properties_model import PropertiesModel
from windows.color_picker import ColorPicker
from themes.manager import ThemeManager
from .menu import StyledContextMenu

import openshot
Expand Down Expand Up @@ -109,11 +108,15 @@ def paint(self, painter, option, index):
value_percent = 0.0

# Get theme colors
theme = ThemeManager().get_current_theme()
if not theme:
return
foreground_color = theme.get_color(".property_value", "foreground-color")
background_color = theme.get_color(".property_value", "background-color")
if get_app().theme_manager:
theme = get_app().theme_manager.get_current_theme()
if not theme:
log.warning("No theme loaded yet. Skip rendering properties widget.")
return
foreground_color = theme.get_color(".property_value", "foreground-color")
background_color = theme.get_color(".property_value", "background-color")
else:
log.warning("No ThemeManager loaded yet. Skip rendering properties widget.")

# set background color
painter.setPen(QPen(Qt.NoPen))
Expand Down
14 changes: 9 additions & 5 deletions src/windows/views/zoom_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from classes import updates
from classes.app import get_app
from classes.query import Clip, Track, Transition, Marker
from themes.manager import ThemeManager
from classes.logger import log


class ZoomSlider(QWidget, updates.UpdateInterface):
Expand Down Expand Up @@ -108,10 +108,14 @@ def paintEvent(self, event, *args):
event.accept()

# Get theme colors
theme = ThemeManager().get_current_theme()
if not theme:
return
playhead_color = theme.get_color(".zoom_slider_playhead", "background-color")
if get_app().theme_manager:
theme = get_app().theme_manager.get_current_theme()
if not theme:
log.warning("No theme loaded yet. Skip rendering zoom slider widget.")
return
playhead_color = theme.get_color(".zoom_slider_playhead", "background-color")
else:
log.warning("No ThemeManager loaded yet. Skip rendering zoom slider widget.")

# Paint timeline preview on QWidget
painter = QPainter(self)
Expand Down

0 comments on commit f10ec7d

Please sign in to comment.