qcanvas 1.2.0__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 +5 -5
- qcanvas/icons/_icon_type.py +1 -1
- qcanvas/icons/icons.qrc +39 -35
- qcanvas/icons/rc_icons.py +1298 -1197
- qcanvas/settings/__init__.py +6 -0
- qcanvas/{util/settings → settings}/_client_settings.py +4 -4
- qcanvas/settings/_course_settings.py +54 -0
- qcanvas/{util/settings → settings}/_mapped_setting.py +2 -5
- qcanvas/{util/settings → settings}/_ui_settings.py +5 -5
- qcanvas/theme.py +101 -0
- qcanvas/ui/course_viewer/content_tree.py +9 -12
- qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +3 -3
- qcanvas/ui/course_viewer/course_tree/course_tree.py +9 -8
- qcanvas/ui/course_viewer/course_viewer.py +42 -56
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -4
- qcanvas/ui/course_viewer/tabs/constants.py +1 -0
- qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
- qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +4 -4
- qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +7 -10
- qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +6 -7
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +7 -8
- qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +5 -5
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +18 -32
- qcanvas/ui/course_viewer/tree_widget_data_item.py +1 -1
- qcanvas/ui/memory_tree/_tree_memory.py +45 -42
- 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/{main_ui → qcanvas_window}/course_viewer_container.py +10 -10
- qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +5 -5
- 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 +10 -10
- qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +57 -41
- qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +5 -6
- 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 +1 -1
- qcanvas/ui/setup/setup_dialog.py +28 -14
- qcanvas/util/auto_downloader.py +9 -7
- qcanvas/util/basic_fonts.py +2 -2
- qcanvas/util/context_dict.py +12 -0
- qcanvas/util/file_icons.py +11 -19
- qcanvas/util/layouts.py +5 -7
- qcanvas/util/paths.py +17 -6
- qcanvas/util/qurl_util.py +1 -1
- qcanvas/util/ui_tools.py +118 -8
- 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.2.0.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 -59
- qcanvas/icons/_update_icons.py +0 -89
- qcanvas/icons/dark/actions/exit.svg +0 -3
- qcanvas/icons/dark/actions/mark_all_read.svg +0 -3
- qcanvas/icons/dark/actions/open_downloads.svg +0 -3
- qcanvas/icons/dark/actions/quick_login.svg +0 -3
- qcanvas/icons/dark/actions/sync.svg +0 -3
- qcanvas/icons/dark/branding/logo_transparent.svg +0 -303
- qcanvas/icons/dark/options/auto_download.svg +0 -3
- qcanvas/icons/dark/options/theme.svg +0 -3
- qcanvas/icons/dark/tabs/assignments.svg +0 -3
- qcanvas/icons/dark/tabs/mail.svg +0 -3
- qcanvas/icons/dark/tabs/pages.svg +0 -3
- qcanvas/icons/dark/tree_items/assignment.svg +0 -3
- qcanvas/icons/dark/tree_items/mail.svg +0 -3
- qcanvas/icons/dark/tree_items/module.svg +0 -3
- qcanvas/icons/dark/tree_items/page.svg +0 -3
- qcanvas/icons/light/actions/exit.svg +0 -3
- qcanvas/icons/light/actions/mark_all_read.svg +0 -3
- qcanvas/icons/light/actions/open_downloads.svg +0 -3
- qcanvas/icons/light/actions/quick_login.svg +0 -3
- qcanvas/icons/light/actions/sync.svg +0 -3
- qcanvas/icons/light/branding/logo_transparent.svg +0 -304
- qcanvas/icons/light/options/auto_download.svg +0 -3
- qcanvas/icons/light/options/ignore_old.svg +0 -3
- qcanvas/icons/light/options/include_videos.svg +0 -3
- qcanvas/icons/light/options/theme.svg +0 -3
- qcanvas/icons/light/tabs/assignments.svg +0 -3
- qcanvas/icons/light/tabs/mail.svg +0 -3
- qcanvas/icons/light/tabs/pages.svg +0 -3
- qcanvas/icons/light/tree_items/assignment.svg +0 -3
- qcanvas/icons/light/tree_items/mail.svg +0 -3
- qcanvas/icons/light/tree_items/module.svg +0 -3
- qcanvas/icons/light/tree_items/page.svg +0 -3
- qcanvas/icons/universal/branding/main_icon.svg +0 -325
- qcanvas/icons/universal/downloads/download_failed.svg +0 -23
- qcanvas/icons/universal/downloads/downloaded.svg +0 -23
- qcanvas/icons/universal/downloads/not_downloaded.svg +0 -23
- qcanvas/icons/universal/downloads/unknown.svg +0 -6
- qcanvas/icons/universal/tabs/assignments_new_content.svg +0 -3
- qcanvas/icons/universal/tabs/mail_new_content.svg +0 -3
- qcanvas/icons/universal/tabs/pages_new_content.svg +0 -3
- qcanvas/icons/universal/tree_items/semester.svg +0 -108
- qcanvas/run.py +0 -54
- qcanvas/ui/course_viewer/tabs/util.py +0 -11
- qcanvas/ui/main_ui/__init__.py +0 -0
- qcanvas/util/settings/__init__.py +0 -9
- qcanvas/util/themes/__init__.py +0 -2
- qcanvas/util/themes/_colour_scheme_helper.py +0 -38
- qcanvas/util/themes/_selected_theme.py +0 -10
- qcanvas/util/themes/_theme_changed_event.py +0 -17
- qcanvas/util/themes/_theme_changer.py +0 -86
- qcanvas-1.2.0.dist-info/METADATA +0 -71
- qcanvas-1.2.0.dist-info/RECORD +0 -118
- qcanvas-1.2.0.dist-info/entry_points.txt +0 -3
- /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
|
@@ -2,25 +2,23 @@ import html
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
from libqcanvas import db
|
|
6
6
|
from bs4 import BeautifulSoup, Tag
|
|
7
|
+
from libqcanvas.net.resources.extracting.no_extractor_error import NoExtractorError
|
|
8
|
+
from libqcanvas.util import is_link_invisible
|
|
9
|
+
from PySide6.QtCore import QUrl, Slot
|
|
10
|
+
from PySide6.QtGui import QDesktopServices
|
|
11
|
+
from PySide6.QtWidgets import QTextBrowser
|
|
7
12
|
from qasync import asyncSlot
|
|
8
|
-
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
9
|
-
from qcanvas_backend.net.resources.extracting.no_extractor_error import NoExtractorError
|
|
10
|
-
from qcanvas_backend.util import is_link_invisible
|
|
11
|
-
from qtpy.QtCore import QUrl, Slot
|
|
12
|
-
from qtpy.QtGui import QDesktopServices
|
|
13
|
-
from qtpy.QtWidgets import QTextBrowser
|
|
14
13
|
|
|
15
14
|
from qcanvas.backend_connectors import FrontendResourceManager
|
|
16
15
|
from qcanvas.util.html_cleaner import clean_up_html
|
|
17
|
-
from qcanvas.util.qurl_util import file_url
|
|
18
16
|
|
|
19
17
|
_logger = logging.getLogger(__name__)
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
class ResourceRichBrowser(QTextBrowser):
|
|
23
|
-
def __init__(self, downloader:
|
|
21
|
+
def __init__(self, downloader: FrontendResourceManager):
|
|
24
22
|
super().__init__()
|
|
25
23
|
self._downloader = downloader
|
|
26
24
|
self._content: Optional[db.CourseContentItem] = None
|
|
@@ -30,9 +28,8 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
30
28
|
self.setOpenLinks(False)
|
|
31
29
|
self.anchorClicked.connect(self._open_url)
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
self._downloader.download_failed.connect(self._download_updated)
|
|
31
|
+
self._downloader.download_finished.connect(self._download_updated)
|
|
32
|
+
self._downloader.download_failed.connect(self._download_updated)
|
|
36
33
|
|
|
37
34
|
def show_blank(self, completely_blank: bool = False) -> None:
|
|
38
35
|
if completely_blank:
|
|
@@ -43,14 +40,14 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
43
40
|
self._content = None
|
|
44
41
|
self._current_content_resources.clear()
|
|
45
42
|
|
|
46
|
-
def show_content(self, page: db.
|
|
43
|
+
def show_content(self, page: db.AnyContentItem) -> None:
|
|
47
44
|
if page.body is None:
|
|
48
45
|
self.show_blank()
|
|
49
46
|
else:
|
|
50
47
|
self._collect_resources(page)
|
|
51
48
|
self._show_page_content(page)
|
|
52
49
|
|
|
53
|
-
def _collect_resources(self, page: db.
|
|
50
|
+
def _collect_resources(self, page: db.AnyContentItem):
|
|
54
51
|
self._current_content_resources = {
|
|
55
52
|
resource.id: resource for resource in page.resources
|
|
56
53
|
}
|
|
@@ -105,17 +102,9 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
105
102
|
|
|
106
103
|
return BeautifulSoup(
|
|
107
104
|
markup=f"""
|
|
108
|
-
<a href="data:{html.escape(resource_id)}" style="font-weight: normal">
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<td style="text-decoration: none;">-->
|
|
112
|
-
<img height="16" src="{html.escape(self._download_state_icon(resource.download_state))}"/>
|
|
113
|
-
<!--</td>
|
|
114
|
-
<td>-->
|
|
115
|
-
{html.escape(resource.file_name)}
|
|
116
|
-
<!--</td>
|
|
117
|
-
</tr>
|
|
118
|
-
</table>-->
|
|
105
|
+
<a href="data:{html.escape(resource_id)}" style="font-weight: normal;">
|
|
106
|
+
<img height="18" src="{html.escape(self._download_state_icon(resource.download_state))}"/>
|
|
107
|
+
{html.escape(resource.file_name)}
|
|
119
108
|
</a>
|
|
120
109
|
""",
|
|
121
110
|
features="html.parser",
|
|
@@ -145,16 +134,13 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
145
134
|
resource = self._current_content_resources[resource_id]
|
|
146
135
|
|
|
147
136
|
try:
|
|
148
|
-
await self._downloader.
|
|
137
|
+
await self._downloader.download_and_open(resource)
|
|
149
138
|
except Exception as e:
|
|
150
139
|
_logger.warning(
|
|
151
140
|
"Download of resource id=%s failed", resource_id, exc_info=e
|
|
152
141
|
)
|
|
153
142
|
return
|
|
154
143
|
|
|
155
|
-
resource_path = file_url(self._downloader.resource_download_location(resource))
|
|
156
|
-
QDesktopServices.openUrl(resource_path)
|
|
157
|
-
|
|
158
144
|
@Slot(db.Resource)
|
|
159
145
|
def _download_updated(self, resource: db.Resource) -> None:
|
|
160
146
|
if self._content is not None and resource.id in self._current_content_resources:
|
|
@@ -171,8 +157,8 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
171
157
|
_logger.warning(
|
|
172
158
|
"Resource has diverged from current loaded data, applying bandaid fix"
|
|
173
159
|
)
|
|
174
|
-
self._current_content_resources[
|
|
175
|
-
resource.
|
|
176
|
-
|
|
160
|
+
self._current_content_resources[
|
|
161
|
+
resource.id
|
|
162
|
+
].download_state = resource.download_state
|
|
177
163
|
|
|
178
164
|
self._show_page_content(self._content)
|
|
@@ -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,16 +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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
self.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
64
61
|
|
|
65
62
|
@property
|
|
66
|
-
def
|
|
67
|
-
|
|
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, QTreeWidgetItem] = {}
|
|
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 hasattr(
|
|
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
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from math import floor
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional, Sequence
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
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
10
|
|
|
11
11
|
from qcanvas import icons
|
|
12
12
|
from qcanvas.ui.course_viewer.course_viewer import CourseViewer
|
|
13
|
-
from qcanvas.
|
|
13
|
+
from qcanvas.theme import app_theme
|
|
14
14
|
|
|
15
15
|
_logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -29,17 +29,17 @@ class _PlaceholderLogo(QLabel):
|
|
|
29
29
|
self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
|
|
30
30
|
# Because we are using a pixmap for the icon, it will not get updated like a normal QIcon when the theme changes,
|
|
31
31
|
# So we need to update it ourselves
|
|
32
|
-
|
|
32
|
+
app_theme.darkModeChanged.connect(self._dark_mode_changed)
|
|
33
33
|
|
|
34
34
|
def resizeEvent(self, event) -> None:
|
|
35
35
|
self._update_image()
|
|
36
36
|
|
|
37
37
|
@Slot()
|
|
38
|
-
def
|
|
38
|
+
def _dark_mode_changed(self) -> None:
|
|
39
39
|
self._update_image(force=True)
|
|
40
40
|
|
|
41
41
|
def _update_image(self, force: bool = False) -> None:
|
|
42
|
-
# Calculate the size of the logo as half of the width/height
|
|
42
|
+
# Calculate the size of the logo as half of the width/height, but prevent it from becoming too large
|
|
43
43
|
width = min(floor(self.width() * 0.5), 500)
|
|
44
44
|
height = min(floor(self.height() * 0.5), 500)
|
|
45
45
|
|
|
@@ -1,12 +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
8
|
from qcanvas import icons
|
|
9
|
-
|
|
9
|
+
import qcanvas.settings as settings
|
|
10
10
|
|
|
11
11
|
_logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
@@ -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,11 +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
7
|
from qcanvas import icons
|
|
8
|
-
from qcanvas.
|
|
8
|
+
from qcanvas.theme import app_theme
|
|
9
|
+
import qcanvas.settings as settings
|
|
9
10
|
|
|
10
11
|
_logger = logging.getLogger(__name__)
|
|
11
12
|
|
|
@@ -21,11 +22,10 @@ class _ThemeAction(QAction):
|
|
|
21
22
|
@Slot()
|
|
22
23
|
def _change_theme(self) -> None:
|
|
23
24
|
settings.ui.theme = self._theme_name
|
|
24
|
-
|
|
25
|
+
app_theme.theme = self._theme_name
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class ThemeSelectionMenu(QMenu):
|
|
28
|
-
|
|
29
29
|
def __init__(self, parent: QMenu):
|
|
30
30
|
super().__init__("Theme", parent)
|
|
31
31
|
|
|
@@ -36,10 +36,10 @@ class ThemeSelectionMenu(QMenu):
|
|
|
36
36
|
dark_theme = _ThemeAction("Dark", "dark", self)
|
|
37
37
|
native_theme = _ThemeAction("Native (requires restart)", "native", self)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
select_theme_actions = [auto_theme, light_theme, dark_theme, native_theme]
|
|
40
40
|
|
|
41
|
-
self.addActions(
|
|
41
|
+
self.addActions(select_theme_actions)
|
|
42
42
|
self.setIcon(icons.options.theme)
|
|
43
43
|
|
|
44
|
-
for
|
|
45
|
-
action_group.addAction(
|
|
44
|
+
for selection_action in select_theme_actions:
|
|
45
|
+
action_group.addAction(selection_action)
|