qcanvas 1.0.11__py3-none-any.whl → 2026.1.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. qcanvas/__init__.py +60 -0
  2. qcanvas/app.py +72 -0
  3. qcanvas/backend_connectors/frontend_resource_manager.py +13 -5
  4. qcanvas/backend_connectors/qcanvas_task_master.py +2 -2
  5. qcanvas/icons/__init__.py +55 -6
  6. qcanvas/icons/_icon_type.py +42 -0
  7. qcanvas/icons/icons.qrc +48 -8
  8. qcanvas/icons/rc_icons.py +2477 -566
  9. qcanvas/settings/__init__.py +6 -0
  10. qcanvas/{util/settings → settings}/_client_settings.py +15 -6
  11. qcanvas/settings/_course_settings.py +54 -0
  12. qcanvas/{util/settings → settings}/_mapped_setting.py +8 -6
  13. qcanvas/{util/settings → settings}/_ui_settings.py +5 -5
  14. qcanvas/theme.py +101 -0
  15. qcanvas/ui/course_viewer/content_tree.py +37 -19
  16. qcanvas/ui/course_viewer/course_tree/__init__.py +1 -0
  17. qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +86 -0
  18. qcanvas/ui/course_viewer/{course_tree.py → course_tree/course_tree.py} +29 -14
  19. qcanvas/ui/course_viewer/course_viewer.py +79 -46
  20. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
  21. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +19 -18
  22. qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
  23. qcanvas/ui/course_viewer/tabs/file_tab/__init__.py +1 -0
  24. qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +46 -0
  25. qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +96 -0
  26. qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +55 -0
  27. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
  28. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +18 -19
  29. qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
  30. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +18 -16
  31. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +61 -74
  32. qcanvas/ui/course_viewer/tree_widget_data_item.py +22 -0
  33. qcanvas/ui/memory_tree/_tree_memory.py +45 -41
  34. qcanvas/ui/memory_tree/memory_tree_widget.py +22 -18
  35. qcanvas/ui/memory_tree/memory_tree_widget_item.py +3 -3
  36. qcanvas/ui/qcanvas_window/__init__.py +1 -0
  37. qcanvas/ui/qcanvas_window/course_viewer_container.py +95 -0
  38. qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +8 -6
  39. qcanvas/ui/{main_ui → qcanvas_window}/options/quick_sync_option.py +7 -6
  40. qcanvas/ui/{main_ui → qcanvas_window}/options/sync_on_start_option.py +7 -6
  41. qcanvas/ui/{main_ui → qcanvas_window}/options/theme_selection_menu.py +12 -10
  42. qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +74 -45
  43. qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +20 -12
  44. qcanvas/ui/qml_components/__init__.py +4 -0
  45. qcanvas/ui/qml_components/attachments_pane.py +70 -0
  46. qcanvas/ui/qml_components/comments_pane.py +83 -0
  47. qcanvas/ui/qml_components/qml/AttachmentsList.ui.qml +15 -0
  48. qcanvas/ui/qml_components/qml/AttachmentsListDelegate.qml +77 -0
  49. qcanvas/ui/qml_components/qml/AttachmentsListModel.qml +19 -0
  50. qcanvas/ui/qml_components/qml/AttachmentsPane.qml +11 -0
  51. qcanvas/ui/qml_components/qml/CommentsList.ui.qml +15 -0
  52. qcanvas/ui/qml_components/qml/CommentsListDelegate.ui.qml +118 -0
  53. qcanvas/ui/qml_components/qml/CommentsListModel.qml +56 -0
  54. qcanvas/ui/qml_components/qml/CommentsPane.qml +11 -0
  55. qcanvas/ui/qml_components/qml/DecoratedText.ui.qml +44 -0
  56. qcanvas/ui/qml_components/qml/Spacer.ui.qml +7 -0
  57. qcanvas/ui/qml_components/qml/ThemedRectangle.qml +53 -0
  58. qcanvas/ui/qml_components/qml/__init__.py +3 -0
  59. qcanvas/ui/qml_components/qml/rc_qml.py +709 -0
  60. qcanvas/ui/qml_components/qml/rc_qml.qrc +16 -0
  61. qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
  62. qcanvas/ui/qml_components/qml_pane.py +21 -0
  63. qcanvas/ui/setup/setup_checker.py +3 -3
  64. qcanvas/ui/setup/setup_dialog.py +173 -80
  65. qcanvas/util/__init__.py +0 -2
  66. qcanvas/util/auto_downloader.py +9 -8
  67. qcanvas/util/basic_fonts.py +2 -2
  68. qcanvas/util/context_dict.py +12 -0
  69. qcanvas/util/file_icons.py +46 -0
  70. qcanvas/util/html_cleaner.py +2 -0
  71. qcanvas/util/layouts.py +9 -8
  72. qcanvas/util/paths.py +26 -22
  73. qcanvas/util/qurl_util.py +1 -1
  74. qcanvas/util/runtime.py +20 -0
  75. qcanvas/util/ui_tools.py +121 -7
  76. qcanvas/util/url_checker.py +1 -1
  77. qcanvas-2026.1.19.dist-info/METADATA +95 -0
  78. qcanvas-2026.1.19.dist-info/RECORD +92 -0
  79. {qcanvas-1.0.11.dist-info → qcanvas-2026.1.19.dist-info}/WHEEL +1 -1
  80. qcanvas-2026.1.19.dist-info/entry_points.txt +3 -0
  81. qcanvas/app_start/__init__.py +0 -54
  82. qcanvas/icons/file-download-failed.svg +0 -6
  83. qcanvas/icons/file-downloaded.svg +0 -6
  84. qcanvas/icons/file-not-downloaded.svg +0 -6
  85. qcanvas/icons/file-unknown.svg +0 -6
  86. qcanvas/icons/main_icon.svg +0 -325
  87. qcanvas/icons/sync.svg +0 -7
  88. qcanvas/run.py +0 -30
  89. qcanvas/ui/main_ui/__init__.py +0 -0
  90. qcanvas/ui/main_ui/course_viewer_container.py +0 -52
  91. qcanvas/util/settings/__init__.py +0 -9
  92. qcanvas/util/themes.py +0 -24
  93. qcanvas-1.0.11.dist-info/METADATA +0 -61
  94. qcanvas-1.0.11.dist-info/RECORD +0 -68
  95. qcanvas-1.0.11.dist-info/entry_points.txt +0 -3
  96. /qcanvas/ui/course_viewer/tabs/{util.py → constants.py} +0 -0
  97. /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
@@ -1,49 +1,41 @@
1
+ import asyncio
1
2
  import logging
2
3
  from pathlib import Path
3
- from typing import *
4
4
 
5
- from lightdb import LightDB, Model
5
+ from aiofile import async_open
6
6
 
7
7
  from qcanvas.util import paths
8
8
 
9
9
  _logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
- def _storage_path() -> Path:
13
- path = paths.ui_storage() / "TREE.DB"
14
- path.parent.mkdir(parents=True, exist_ok=True)
15
- return path
16
-
17
-
18
- _state_db = LightDB(str(_storage_path()))
19
-
20
-
21
- class _TreeState(Model, table="trees", db=_state_db):
22
- tree_name: str
23
- collapsed_items: List[str] = []
24
-
12
+ class TreeMemory:
13
+ def __init__(self, tree_name: str):
14
+ self._tree_name = tree_name
15
+ self._loaded = False
16
+ self._collapsed_items: set[str] = set()
25
17
 
26
- def _get_or_create_state(name: str) -> _TreeState:
27
- state = _TreeState.get(tree_name=name)
18
+ def load(self, force: bool = False):
19
+ if force or not self._loaded:
20
+ self._loaded = True
28
21
 
29
- if state is None:
30
- state = _TreeState.create(tree_name=name)
31
- # Initialise the list here! Or else every instance has the same list object
32
- state.collapsed_items = []
33
- # Important or instances will get duplicated data in some cases
34
- state.save()
35
- return state
36
- else:
37
- return state
22
+ if not self._storage_path.exists():
23
+ # Nothing to do
24
+ return
38
25
 
26
+ # fixme this blocks the event loop, but using aiofile will require significant changes to other
27
+ # parts of the code to accommodate these methods now being async.
28
+ # this will really only have any noticeable effect on slow disks, and only ever happens once anyway.
29
+ with open(self._storage_path, "rt") as file:
30
+ lines = file.read().splitlines()
31
+ _logger.debug("Tree % loaded %s", self._tree_name, lines)
32
+ self._collapsed_items.update(lines)
39
33
 
40
- class TreeMemory:
41
- def __init__(self, tree_name: str):
42
- self._tree_name = tree_name
43
- self._state = _get_or_create_state(tree_name)
34
+ async def save(self):
35
+ assert self._loaded, "Memory is not loaded yet"
44
36
 
45
- def is_expanded(self, node_id: str) -> bool:
46
- return node_id in self._state.expanded_items
37
+ async with async_open(self._storage_path, "wt") as file:
38
+ await file.write("\n".join(self._collapsed_items))
47
39
 
48
40
  def expanded(self, node_id: str) -> None:
49
41
  self.set_expanded(node_id, True)
@@ -52,15 +44,27 @@ class TreeMemory:
52
44
  self.set_expanded(node_id, False)
53
45
 
54
46
  def set_expanded(self, node_id: str, expanded: bool) -> None:
55
- contains = node_id in self._state.collapsed_items
47
+ self.load()
56
48
 
57
- if expanded and contains:
58
- self._state.collapsed_items.remove(node_id)
59
- self._state.save()
60
- elif not expanded and not contains:
61
- self._state.collapsed_items.append(node_id)
62
- self._state.save()
49
+ if expanded and node_id in self._collapsed_items:
50
+ self._collapsed_items.remove(node_id)
51
+ else:
52
+ self._collapsed_items.add(node_id)
53
+
54
+ # hack: avoid blocking the event loop
55
+ asyncio.create_task(self.save())
56
+
57
+ @property
58
+ def collapsed_ids(self) -> set[str]:
59
+ assert self._loaded, "Memory not loaded yet"
60
+ return self._collapsed_items
63
61
 
64
62
  @property
65
- def collapsed_ids(self) -> List[str]:
66
- return self._state.collapsed_items
63
+ def _storage_path(self) -> Path:
64
+ path = (
65
+ paths.data_storage()
66
+ / "tree_state"
67
+ / f"{self._tree_name}_collapsed_items.txt"
68
+ )
69
+ path.parent.mkdir(parents=True, exist_ok=True)
70
+ return path
@@ -1,8 +1,8 @@
1
1
  import logging
2
- from typing import *
2
+ from typing import Optional, Sequence
3
3
 
4
- from qtpy.QtCore import QItemSelectionModel, Slot
5
- from qtpy.QtWidgets import QTreeWidget, QTreeWidgetItem, QWidget
4
+ from PySide6.QtCore import QItemSelectionModel, Slot
5
+ from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QWidget
6
6
 
7
7
  from qcanvas.ui.memory_tree._tree_memory import TreeMemory
8
8
  from qcanvas.ui.memory_tree.memory_tree_widget_item import MemoryTreeWidgetItem
@@ -10,6 +10,10 @@ from qcanvas.ui.memory_tree.memory_tree_widget_item import MemoryTreeWidgetItem
10
10
  _logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
+ class _HasID:
14
+ id: str
15
+
16
+
13
17
  class MemoryTreeWidget(QTreeWidget):
14
18
  def __init__(
15
19
  self,
@@ -17,7 +21,7 @@ class MemoryTreeWidget(QTreeWidget):
17
21
  parent: Optional[QWidget] = None,
18
22
  ):
19
23
  super().__init__(parent)
20
- self._id_map: dict[str, MemoryTreeWidgetItem] = {}
24
+ self._id_map: dict[str, QTreeWidgetItem | _HasID] = {}
21
25
  self._memory = TreeMemory(tree_name)
22
26
  self._suppress_expansion_signals = False
23
27
  self._suppress_selection_signal = False
@@ -30,7 +34,7 @@ class MemoryTreeWidget(QTreeWidget):
30
34
 
31
35
  try:
32
36
  self._suppress_expansion_signals = True
33
-
37
+ self._memory.load()
34
38
  collapsed_ids = self._memory.collapsed_ids
35
39
 
36
40
  for widget in self._id_map.values():
@@ -44,7 +48,7 @@ class MemoryTreeWidget(QTreeWidget):
44
48
  super().clear()
45
49
  self._id_map.clear()
46
50
 
47
- def select_ids(self, ids: List[str]) -> bool:
51
+ def select_ids(self, ids: list[str]) -> bool:
48
52
  """
49
53
  :returns: True if all ids were still found in the tree, False if one or more was missing
50
54
  """
@@ -94,26 +98,26 @@ class MemoryTreeWidget(QTreeWidget):
94
98
  def _add_widget_to_id_map(
95
99
  self, widget: QTreeWidgetItem | Sequence[QTreeWidgetItem]
96
100
  ):
97
- map_updates = {}
98
- widget_stack = widget if isinstance(widget, List) else [widget]
101
+ widget_stack = widget if isinstance(widget, list) else [widget]
99
102
 
100
103
  while len(widget_stack) > 0:
101
- item = widget_stack.pop()
104
+ popped_widget = widget_stack.pop()
102
105
 
103
- if isinstance(item, MemoryTreeWidgetItem):
104
- if item.id in self._id_map or item.id in map_updates:
105
- raise ValueError(f"Item with ID {item.id} is already in the tree")
106
+ if hasattr(popped_widget, "id"):
107
+ if popped_widget.id in self._id_map:
108
+ raise ValueError(
109
+ f"Item with ID {popped_widget.id} is already in the tree"
110
+ )
106
111
 
107
- map_updates[item.id] = item
108
- _logger.debug("Add %s to map", item.id)
112
+ self._id_map[popped_widget.id] = popped_widget
113
+ _logger.debug("Add %s to map", popped_widget.id)
109
114
 
110
- if item.childCount() > 0:
115
+ if popped_widget.childCount() > 0:
111
116
  widget_stack.extend(
112
- [item.child(index) for index in range(0, item.childCount())]
117
+ popped_widget.child(index)
118
+ for index in range(0, popped_widget.childCount())
113
119
  )
114
120
 
115
- self._id_map.update(map_updates.items())
116
-
117
121
  @Slot(QTreeWidgetItem)
118
122
  def _expanded(self, item: QTreeWidgetItem):
119
123
  if self._suppress_expansion_signals:
@@ -1,14 +1,14 @@
1
1
  import logging
2
- from typing import *
2
+ from typing import Optional
3
3
 
4
- from qtpy.QtWidgets import QTreeWidgetItem
4
+ from PySide6.QtWidgets import QTreeWidgetItem
5
5
 
6
6
  _logger = logging.getLogger(__name__)
7
7
 
8
8
 
9
9
  class MemoryTreeWidgetItem(QTreeWidgetItem):
10
10
  def __init__(
11
- self, id: str, data: Optional[object], strings: Optional[List[str]] = None
11
+ self, id: str, data: Optional[object], strings: Optional[list[str]] = None
12
12
  ):
13
13
  super().__init__(strings)
14
14
  self._id = id
@@ -0,0 +1 @@
1
+ from .qcanvas_window import QCanvasWindow
@@ -0,0 +1,95 @@
1
+ import logging
2
+ from math import floor
3
+ from typing import Optional, Sequence
4
+
5
+ from libqcanvas import db
6
+ from libqcanvas.net.resources.download.resource_manager import ResourceManager
7
+ from libqcanvas.net.sync.sync_receipt import SyncReceipt, empty_receipt
8
+ from PySide6.QtCore import Qt, Slot
9
+ from PySide6.QtWidgets import QLabel, QSizePolicy, QStackedWidget
10
+
11
+ from qcanvas import icons
12
+ from qcanvas.ui.course_viewer.course_viewer import CourseViewer
13
+ from qcanvas.theme import app_theme
14
+
15
+ _logger = logging.getLogger(__name__)
16
+
17
+
18
+ class _PlaceholderLogo(QLabel):
19
+ """
20
+ Automatically resizing logo icon for when no course is selected
21
+ """
22
+
23
+ def __init__(self):
24
+ super().__init__()
25
+ self._icon = icons.branding.logo_transparent
26
+ self._old_width = -1
27
+ self._old_height = -1
28
+ self.setAlignment(Qt.AlignmentFlag.AlignCenter)
29
+ self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
30
+ # Because we are using a pixmap for the icon, it will not get updated like a normal QIcon when the theme changes,
31
+ # So we need to update it ourselves
32
+ app_theme.darkModeChanged.connect(self._dark_mode_changed)
33
+
34
+ def resizeEvent(self, event) -> None:
35
+ self._update_image()
36
+
37
+ @Slot()
38
+ def _dark_mode_changed(self) -> None:
39
+ self._update_image(force=True)
40
+
41
+ def _update_image(self, force: bool = False) -> None:
42
+ # Calculate the size of the logo as half of the width/height, but prevent it from becoming too large
43
+ width = min(floor(self.width() * 0.5), 500)
44
+ height = min(floor(self.height() * 0.5), 500)
45
+
46
+ if force or (width != self._old_width and height != self._old_height):
47
+ self._old_width = width
48
+ self._old_height = height
49
+ self.setPixmap(self._icon.pixmap(width, height))
50
+
51
+
52
+ class CourseViewerContainer(QStackedWidget):
53
+ def __init__(self, downloader: ResourceManager):
54
+ super().__init__()
55
+ self._course_viewers: dict[str, CourseViewer] = {}
56
+ self._downloader = downloader
57
+ self._last_course_id: Optional[str] = None
58
+ self._selected_course: Optional[db.Course] = None
59
+ self._last_sync_receipt: SyncReceipt = empty_receipt()
60
+ self._placeholder = _PlaceholderLogo()
61
+ self.addWidget(self._placeholder)
62
+
63
+ def show_blank(self) -> None:
64
+ self._last_course_id = None
65
+ self._selected_course = None
66
+ self.setCurrentWidget(self._placeholder)
67
+
68
+ def load_course(self, course: db.Course) -> None:
69
+ if course.id not in self._course_viewers:
70
+ viewer = CourseViewer(
71
+ course=course,
72
+ downloader=self._downloader,
73
+ sync_receipt=self._last_sync_receipt,
74
+ )
75
+ self._course_viewers[course.id] = viewer
76
+ self.addWidget(viewer)
77
+ else:
78
+ viewer = self._course_viewers[course.id]
79
+
80
+ self.setCurrentWidget(viewer)
81
+ self._selected_course = course
82
+ self._last_course_id = course.id
83
+
84
+ async def reload_all(
85
+ self, courses: Sequence[db.Course], *, sync_receipt: SyncReceipt
86
+ ) -> None:
87
+ self._last_sync_receipt = sync_receipt
88
+ for course in courses:
89
+ if course.id in self._course_viewers:
90
+ viewer = self._course_viewers[course.id]
91
+ viewer.reload(course, sync_receipt=sync_receipt)
92
+
93
+ @property
94
+ def selected_course(self) -> Optional[db.Course]:
95
+ return self._selected_course
@@ -1,11 +1,12 @@
1
1
  import logging
2
- from typing import *
2
+ from typing import Optional
3
3
 
4
- from qtpy.QtCore import Slot
5
- from qtpy.QtGui import QAction
6
- from qtpy.QtWidgets import QMenu
4
+ from PySide6.QtCore import Slot
5
+ from PySide6.QtGui import QAction
6
+ from PySide6.QtWidgets import QMenu
7
7
 
8
- from qcanvas.util import settings
8
+ from qcanvas import icons
9
+ import qcanvas.settings as settings
9
10
 
10
11
  _logger = logging.getLogger(__name__)
11
12
 
@@ -36,6 +37,7 @@ class _EnableVideoDownloadOption(QAction):
36
37
 
37
38
  class AutoDownloadResourcesMenu(QMenu):
38
39
  def __init__(self, parent: Optional[QMenu] = None):
39
- super().__init__("Download new resources", parent)
40
+ super().__init__("Auto download resources", parent)
40
41
  self.addAction(_EnableAutoDownloadOption(self))
41
42
  self.addAction(_EnableVideoDownloadOption(self))
43
+ self.setIcon(icons.options.auto_download)
@@ -1,11 +1,11 @@
1
1
  import logging
2
- from typing import *
2
+ from typing import Optional
3
3
 
4
- from qtpy.QtCore import Slot
5
- from qtpy.QtGui import QAction
6
- from qtpy.QtWidgets import QMenu
4
+ from PySide6.QtCore import Slot
5
+ from PySide6.QtGui import QAction
6
+ from PySide6.QtWidgets import QMenu
7
7
 
8
- from qcanvas.util import settings
8
+ import qcanvas.settings as settings
9
9
 
10
10
  _logger = logging.getLogger(__name__)
11
11
 
@@ -14,11 +14,12 @@ class QuickSyncOption(QAction):
14
14
  def __init__(self, parent: Optional[QMenu] = None):
15
15
  super().__init__("Ignore old courses", parent)
16
16
  self.setToolTip(
17
- "When this option is selected, old courses will not be synchronised. This will only effect the first sync."
17
+ "When enabled, old courses will not be synchronised. This won't hide any old courses that were previously synchronised."
18
18
  )
19
19
  self.setCheckable(True)
20
20
  self.setChecked(settings.client.quick_sync_enabled)
21
21
  self.triggered.connect(self._triggered)
22
+ # self.setIcon(icons.options.ignore_old)
22
23
 
23
24
  @Slot()
24
25
  def _triggered(self) -> None:
@@ -1,11 +1,11 @@
1
1
  import logging
2
- from typing import *
2
+ from typing import Optional
3
3
 
4
- from qtpy.QtCore import Slot
5
- from qtpy.QtGui import QAction
6
- from qtpy.QtWidgets import QMenu
4
+ from PySide6.QtCore import Slot
5
+ from PySide6.QtGui import QAction
6
+ from PySide6.QtWidgets import QMenu
7
7
 
8
- from qcanvas.util import settings
8
+ import qcanvas.settings as settings
9
9
 
10
10
  _logger = logging.getLogger(__name__)
11
11
 
@@ -14,11 +14,12 @@ class SyncOnStartOption(QAction):
14
14
  def __init__(self, parent: Optional[QMenu] = None):
15
15
  super().__init__("Sync on start", parent)
16
16
  self.setToolTip(
17
- "When this option is selected, synchronisation will be started automatically when the app starts."
17
+ "When enabled, synchronisation will start when the application is opened."
18
18
  )
19
19
  self.setCheckable(True)
20
20
  self.setChecked(settings.client.sync_on_start)
21
21
  self.triggered.connect(self._triggered)
22
+ # self.set(icons.options.sync_on_start)
22
23
 
23
24
  @Slot()
24
25
  def _triggered(self) -> None:
@@ -1,10 +1,12 @@
1
1
  import logging
2
2
 
3
- from qtpy.QtCore import Slot
4
- from qtpy.QtGui import QAction, QActionGroup
5
- from qtpy.QtWidgets import QMenu
3
+ from PySide6.QtCore import Slot
4
+ from PySide6.QtGui import QAction, QActionGroup
5
+ from PySide6.QtWidgets import QMenu
6
6
 
7
- from qcanvas.util import settings, themes
7
+ from qcanvas import icons
8
+ from qcanvas.theme import app_theme
9
+ import qcanvas.settings as settings
8
10
 
9
11
  _logger = logging.getLogger(__name__)
10
12
 
@@ -20,11 +22,10 @@ class _ThemeAction(QAction):
20
22
  @Slot()
21
23
  def _change_theme(self) -> None:
22
24
  settings.ui.theme = self._theme_name
23
- themes.apply(self._theme_name)
25
+ app_theme.theme = self._theme_name
24
26
 
25
27
 
26
28
  class ThemeSelectionMenu(QMenu):
27
-
28
29
  def __init__(self, parent: QMenu):
29
30
  super().__init__("Theme", parent)
30
31
 
@@ -35,9 +36,10 @@ class ThemeSelectionMenu(QMenu):
35
36
  dark_theme = _ThemeAction("Dark", "dark", self)
36
37
  native_theme = _ThemeAction("Native (requires restart)", "native", self)
37
38
 
38
- actions = [auto_theme, light_theme, dark_theme, native_theme]
39
+ select_theme_actions = [auto_theme, light_theme, dark_theme, native_theme]
39
40
 
40
- self.addActions(actions)
41
+ self.addActions(select_theme_actions)
42
+ self.setIcon(icons.options.theme)
41
43
 
42
- for theme in actions:
43
- action_group.addAction(theme)
44
+ for selection_action in select_theme_actions:
45
+ action_group.addAction(selection_action)