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.
- qcanvas/__init__.py +60 -0
- qcanvas/app.py +72 -0
- qcanvas/backend_connectors/frontend_resource_manager.py +13 -5
- qcanvas/backend_connectors/qcanvas_task_master.py +2 -2
- qcanvas/icons/__init__.py +55 -6
- qcanvas/icons/_icon_type.py +42 -0
- qcanvas/icons/icons.qrc +48 -8
- qcanvas/icons/rc_icons.py +2477 -566
- qcanvas/settings/__init__.py +6 -0
- qcanvas/{util/settings → settings}/_client_settings.py +15 -6
- qcanvas/settings/_course_settings.py +54 -0
- qcanvas/{util/settings → settings}/_mapped_setting.py +8 -6
- qcanvas/{util/settings → settings}/_ui_settings.py +5 -5
- qcanvas/theme.py +101 -0
- qcanvas/ui/course_viewer/content_tree.py +37 -19
- qcanvas/ui/course_viewer/course_tree/__init__.py +1 -0
- qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +86 -0
- qcanvas/ui/course_viewer/{course_tree.py → course_tree/course_tree.py} +29 -14
- qcanvas/ui/course_viewer/course_viewer.py +79 -46
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +19 -18
- qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
- qcanvas/ui/course_viewer/tabs/file_tab/__init__.py +1 -0
- qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +46 -0
- qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +96 -0
- qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +55 -0
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +18 -19
- qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +18 -16
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +61 -74
- qcanvas/ui/course_viewer/tree_widget_data_item.py +22 -0
- qcanvas/ui/memory_tree/_tree_memory.py +45 -41
- qcanvas/ui/memory_tree/memory_tree_widget.py +22 -18
- qcanvas/ui/memory_tree/memory_tree_widget_item.py +3 -3
- qcanvas/ui/qcanvas_window/__init__.py +1 -0
- qcanvas/ui/qcanvas_window/course_viewer_container.py +95 -0
- qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +8 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/quick_sync_option.py +7 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/sync_on_start_option.py +7 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/theme_selection_menu.py +12 -10
- qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +74 -45
- qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +20 -12
- qcanvas/ui/qml_components/__init__.py +4 -0
- qcanvas/ui/qml_components/attachments_pane.py +70 -0
- qcanvas/ui/qml_components/comments_pane.py +83 -0
- qcanvas/ui/qml_components/qml/AttachmentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/qml/AttachmentsListDelegate.qml +77 -0
- qcanvas/ui/qml_components/qml/AttachmentsListModel.qml +19 -0
- qcanvas/ui/qml_components/qml/AttachmentsPane.qml +11 -0
- qcanvas/ui/qml_components/qml/CommentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/qml/CommentsListDelegate.ui.qml +118 -0
- qcanvas/ui/qml_components/qml/CommentsListModel.qml +56 -0
- qcanvas/ui/qml_components/qml/CommentsPane.qml +11 -0
- qcanvas/ui/qml_components/qml/DecoratedText.ui.qml +44 -0
- qcanvas/ui/qml_components/qml/Spacer.ui.qml +7 -0
- qcanvas/ui/qml_components/qml/ThemedRectangle.qml +53 -0
- qcanvas/ui/qml_components/qml/__init__.py +3 -0
- qcanvas/ui/qml_components/qml/rc_qml.py +709 -0
- qcanvas/ui/qml_components/qml/rc_qml.qrc +16 -0
- qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
- qcanvas/ui/qml_components/qml_pane.py +21 -0
- qcanvas/ui/setup/setup_checker.py +3 -3
- qcanvas/ui/setup/setup_dialog.py +173 -80
- qcanvas/util/__init__.py +0 -2
- qcanvas/util/auto_downloader.py +9 -8
- qcanvas/util/basic_fonts.py +2 -2
- qcanvas/util/context_dict.py +12 -0
- qcanvas/util/file_icons.py +46 -0
- qcanvas/util/html_cleaner.py +2 -0
- qcanvas/util/layouts.py +9 -8
- qcanvas/util/paths.py +26 -22
- qcanvas/util/qurl_util.py +1 -1
- qcanvas/util/runtime.py +20 -0
- qcanvas/util/ui_tools.py +121 -7
- qcanvas/util/url_checker.py +1 -1
- qcanvas-2026.1.19.dist-info/METADATA +95 -0
- qcanvas-2026.1.19.dist-info/RECORD +92 -0
- {qcanvas-1.0.11.dist-info → qcanvas-2026.1.19.dist-info}/WHEEL +1 -1
- qcanvas-2026.1.19.dist-info/entry_points.txt +3 -0
- qcanvas/app_start/__init__.py +0 -54
- qcanvas/icons/file-download-failed.svg +0 -6
- qcanvas/icons/file-downloaded.svg +0 -6
- qcanvas/icons/file-not-downloaded.svg +0 -6
- qcanvas/icons/file-unknown.svg +0 -6
- qcanvas/icons/main_icon.svg +0 -325
- qcanvas/icons/sync.svg +0 -7
- qcanvas/run.py +0 -30
- qcanvas/ui/main_ui/__init__.py +0 -0
- qcanvas/ui/main_ui/course_viewer_container.py +0 -52
- qcanvas/util/settings/__init__.py +0 -9
- qcanvas/util/themes.py +0 -24
- qcanvas-1.0.11.dist-info/METADATA +0 -61
- qcanvas-1.0.11.dist-info/RECORD +0 -68
- qcanvas-1.0.11.dist-info/entry_points.txt +0 -3
- /qcanvas/ui/course_viewer/tabs/{util.py → constants.py} +0 -0
- /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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
27
|
-
|
|
18
|
+
def load(self, force: bool = False):
|
|
19
|
+
if force or not self._loaded:
|
|
20
|
+
self._loaded = True
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
47
|
+
self.load()
|
|
56
48
|
|
|
57
|
-
if expanded and
|
|
58
|
-
self.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
66
|
-
|
|
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
|
|
5
|
-
from
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
104
|
+
popped_widget = widget_stack.pop()
|
|
102
105
|
|
|
103
|
-
if
|
|
104
|
-
if
|
|
105
|
-
raise ValueError(
|
|
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
|
-
|
|
108
|
-
_logger.debug("Add %s to map",
|
|
112
|
+
self._id_map[popped_widget.id] = popped_widget
|
|
113
|
+
_logger.debug("Add %s to map", popped_widget.id)
|
|
109
114
|
|
|
110
|
-
if
|
|
115
|
+
if popped_widget.childCount() > 0:
|
|
111
116
|
widget_stack.extend(
|
|
112
|
-
|
|
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
|
|
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[
|
|
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
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from PySide6.QtCore import Slot
|
|
5
|
+
from PySide6.QtGui import QAction
|
|
6
|
+
from PySide6.QtWidgets import QMenu
|
|
7
7
|
|
|
8
|
-
from qcanvas
|
|
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__("
|
|
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
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from PySide6.QtCore import Slot
|
|
5
|
+
from PySide6.QtGui import QAction
|
|
6
|
+
from PySide6.QtWidgets import QMenu
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
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
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from PySide6.QtCore import Slot
|
|
5
|
+
from PySide6.QtGui import QAction
|
|
6
|
+
from PySide6.QtWidgets import QMenu
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
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
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from PySide6.QtCore import Slot
|
|
4
|
+
from PySide6.QtGui import QAction, QActionGroup
|
|
5
|
+
from PySide6.QtWidgets import QMenu
|
|
6
6
|
|
|
7
|
-
from qcanvas
|
|
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
|
-
|
|
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
|
-
|
|
39
|
+
select_theme_actions = [auto_theme, light_theme, dark_theme, native_theme]
|
|
39
40
|
|
|
40
|
-
self.addActions(
|
|
41
|
+
self.addActions(select_theme_actions)
|
|
42
|
+
self.setIcon(icons.options.theme)
|
|
41
43
|
|
|
42
|
-
for
|
|
43
|
-
action_group.addAction(
|
|
44
|
+
for selection_action in select_theme_actions:
|
|
45
|
+
action_group.addAction(selection_action)
|