qcanvas 1.2.2__py3-none-any.whl → 2.0.0__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.
Potentially problematic release.
This version of qcanvas might be problematic. Click here for more details.
- qcanvas/__init__.py +54 -0
- qcanvas/app.py +93 -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 +47 -43
- 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 +4 -4
- 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 +35 -43
- 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 +15 -21
- 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 +38 -33
- qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +5 -6
- qcanvas/ui/qml_components/AttachmentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/AttachmentsListDelegate.qml +77 -0
- qcanvas/ui/qml_components/AttachmentsListModel.qml +19 -0
- qcanvas/ui/qml_components/AttachmentsPane.qml +12 -0
- qcanvas/ui/qml_components/CommentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/CommentsListDelegate.ui.qml +118 -0
- qcanvas/ui/qml_components/CommentsListModel.qml +56 -0
- qcanvas/ui/qml_components/CommentsPane.qml +12 -0
- qcanvas/ui/qml_components/DarkTheme.qml +12 -0
- qcanvas/ui/qml_components/DecoratedText.ui.qml +44 -0
- qcanvas/ui/qml_components/LightTheme.qml +11 -0
- qcanvas/ui/qml_components/Spacer.ui.qml +7 -0
- qcanvas/ui/qml_components/ThemedRectangle.qml +37 -0
- qcanvas/ui/qml_components/__init__.py +3 -0
- qcanvas/ui/qml_components/attachments_pane.py +72 -0
- qcanvas/ui/qml_components/comments_pane.py +85 -0
- qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
- qcanvas/ui/qml_components/qml_pane.py +22 -0
- qcanvas/ui/setup/setup_checker.py +1 -1
- qcanvas/ui/setup/setup_dialog.py +27 -10
- 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-1.2.2.dist-info → qcanvas-2.0.0.dist-info}/METADATA +12 -10
- qcanvas-2.0.0.dist-info/RECORD +91 -0
- {qcanvas-1.2.2.dist-info → qcanvas-2.0.0.dist-info}/WHEEL +1 -1
- qcanvas-2.0.0.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.2.dist-info/RECORD +0 -118
- qcanvas-1.2.2.dist-info/entry_points.txt +0 -3
- /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
|
@@ -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)
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from threading import BoundedSemaphore
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional, Sequence
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
|
-
|
|
6
|
+
from libqcanvas import db
|
|
7
|
+
from libqcanvas.database.data_monolith import DataMonolith
|
|
8
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt, empty_receipt
|
|
9
|
+
from libqcanvas.qcanvas import QCanvas
|
|
10
|
+
from PySide6.QtCore import QUrl, Signal, Slot, Qt
|
|
11
|
+
from PySide6.QtGui import QDesktopServices, QKeySequence
|
|
12
|
+
from PySide6.QtWidgets import (
|
|
13
|
+
QErrorMessage,
|
|
14
|
+
QHBoxLayout,
|
|
15
|
+
QMainWindow,
|
|
16
|
+
QProgressDialog,
|
|
17
|
+
QPushButton,
|
|
18
|
+
QVBoxLayout,
|
|
19
|
+
QWidget,
|
|
20
|
+
)
|
|
7
21
|
from qasync import asyncSlot
|
|
8
|
-
from qcanvas_backend.database.data_monolith import DataMonolith
|
|
9
|
-
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt, empty_receipt
|
|
10
|
-
from qcanvas_backend.qcanvas import QCanvas
|
|
11
|
-
from qtpy.QtCore import QUrl, Signal, Slot
|
|
12
|
-
from qtpy.QtGui import QDesktopServices, QKeySequence
|
|
13
|
-
from qtpy.QtWidgets import *
|
|
14
22
|
|
|
15
23
|
from qcanvas import icons
|
|
16
24
|
from qcanvas.backend_connectors import FrontendResourceManager
|
|
25
|
+
from qcanvas.settings import course_configs
|
|
17
26
|
from qcanvas.ui.course_viewer import CourseTree
|
|
18
|
-
from
|
|
19
|
-
from
|
|
27
|
+
from .course_viewer_container import CourseViewerContainer
|
|
28
|
+
from .options.auto_download_resources_option import (
|
|
20
29
|
AutoDownloadResourcesMenu,
|
|
21
30
|
)
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from qcanvas.util import auto_downloader
|
|
31
|
+
from .options.quick_sync_option import QuickSyncOption
|
|
32
|
+
from .options.sync_on_start_option import SyncOnStartOption
|
|
33
|
+
from .options.theme_selection_menu import ThemeSelectionMenu
|
|
34
|
+
from .status_bar_progress_display import StatusBarProgressDisplay
|
|
35
|
+
from qcanvas.util import auto_downloader
|
|
36
|
+
import qcanvas.settings as settings
|
|
27
37
|
from qcanvas.util.qurl_util import file_url
|
|
28
38
|
from qcanvas.util.ui_tools import create_qaction
|
|
29
39
|
|
|
@@ -33,7 +43,7 @@ _logger = logging.getLogger(__name__)
|
|
|
33
43
|
class QCanvasWindow(QMainWindow):
|
|
34
44
|
_loaded = Signal()
|
|
35
45
|
|
|
36
|
-
def __init__(self):
|
|
46
|
+
def __init__(self, _qcanvas: QCanvas[FrontendResourceManager]):
|
|
37
47
|
super().__init__()
|
|
38
48
|
|
|
39
49
|
self.setWindowTitle("QCanvas")
|
|
@@ -41,12 +51,7 @@ class QCanvasWindow(QMainWindow):
|
|
|
41
51
|
|
|
42
52
|
self._operation_semaphore = BoundedSemaphore()
|
|
43
53
|
self._data: Optional[DataMonolith] = None
|
|
44
|
-
self._qcanvas =
|
|
45
|
-
canvas_config=settings.client.canvas_config,
|
|
46
|
-
panopto_config=settings.client.panopto_config,
|
|
47
|
-
storage_path=paths.data_storage(),
|
|
48
|
-
resource_manager_class=FrontendResourceManager,
|
|
49
|
-
)
|
|
54
|
+
self._qcanvas = _qcanvas
|
|
50
55
|
|
|
51
56
|
self._course_tree = CourseTree()
|
|
52
57
|
self._course_tree.item_selected.connect(self._on_course_selected)
|
|
@@ -62,7 +67,9 @@ class QCanvasWindow(QMainWindow):
|
|
|
62
67
|
self._setup_menu_bar()
|
|
63
68
|
self._restore_window_position()
|
|
64
69
|
|
|
65
|
-
self._loaded.connect(
|
|
70
|
+
self._loaded.connect(
|
|
71
|
+
self._on_app_loaded, Qt.ConnectionType.SingleShotConnection
|
|
72
|
+
)
|
|
66
73
|
self._loaded.emit()
|
|
67
74
|
|
|
68
75
|
def _setup_menu_bar(self) -> None:
|
|
@@ -109,7 +116,7 @@ class QCanvasWindow(QMainWindow):
|
|
|
109
116
|
)
|
|
110
117
|
|
|
111
118
|
options_menu = menu_bar.addMenu("Options")
|
|
112
|
-
|
|
119
|
+
options_menu.setToolTipsVisible(True)
|
|
113
120
|
options_menu.addAction(QuickSyncOption(options_menu))
|
|
114
121
|
options_menu.addAction(SyncOnStartOption(options_menu))
|
|
115
122
|
options_menu.addMenu(AutoDownloadResourcesMenu(options_menu))
|
|
@@ -147,7 +154,6 @@ class QCanvasWindow(QMainWindow):
|
|
|
147
154
|
|
|
148
155
|
@asyncSlot()
|
|
149
156
|
async def _on_app_loaded(self) -> None:
|
|
150
|
-
await self._qcanvas.init()
|
|
151
157
|
self._course_tree.reload(await self._get_terms(), sync_receipt=empty_receipt())
|
|
152
158
|
|
|
153
159
|
if settings.client.sync_on_start:
|
|
@@ -199,14 +205,14 @@ class QCanvasWindow(QMainWindow):
|
|
|
199
205
|
await self._get_courses(), sync_receipt=receipt
|
|
200
206
|
)
|
|
201
207
|
|
|
202
|
-
async def _get_resources(self) ->
|
|
203
|
-
return (await self._qcanvas.
|
|
208
|
+
async def _get_resources(self) -> dict[str, db.Resource]:
|
|
209
|
+
return (await self._qcanvas.load()).resources
|
|
204
210
|
|
|
205
211
|
async def _get_terms(self) -> Sequence[db.Term]:
|
|
206
|
-
return (await self._qcanvas.
|
|
212
|
+
return (await self._qcanvas.load()).terms
|
|
207
213
|
|
|
208
214
|
async def _get_courses(self) -> Sequence[db.Course]:
|
|
209
|
-
return (await self._qcanvas.
|
|
215
|
+
return (await self._qcanvas.load()).courses
|
|
210
216
|
|
|
211
217
|
@Slot(db.Course)
|
|
212
218
|
def _on_course_selected(self, course: Optional[db.Course]) -> None:
|
|
@@ -218,10 +224,9 @@ class QCanvasWindow(QMainWindow):
|
|
|
218
224
|
@asyncSlot(db.Course, str)
|
|
219
225
|
async def _on_course_renamed(self, course: db.Course, new_name: str) -> None:
|
|
220
226
|
_logger.debug("Rename %s -> %s", course.name, new_name)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
course.configuration.nickname = new_name
|
|
227
|
+
config = course_configs[course.id]
|
|
228
|
+
config.nickname = new_name
|
|
229
|
+
await config.save()
|
|
225
230
|
|
|
226
231
|
@asyncSlot()
|
|
227
232
|
async def _open_quick_auth_in_browser(self) -> None:
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from asyncio import Lock
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import *
|
|
5
4
|
|
|
5
|
+
from libqcanvas.task_master import TaskID
|
|
6
|
+
from PySide6.QtWidgets import QProgressBar, QStatusBar
|
|
6
7
|
from qasync import asyncSlot
|
|
7
|
-
from qcanvas_backend.task_master import TaskID
|
|
8
|
-
from qtpy.QtWidgets import *
|
|
9
8
|
|
|
10
9
|
from qcanvas.backend_connectors import task_master
|
|
11
10
|
|
|
@@ -90,7 +89,7 @@ class StatusBarProgressDisplay(QStatusBar):
|
|
|
90
89
|
self.showMessage("Done", 5000)
|
|
91
90
|
self._progress_bar.hide()
|
|
92
91
|
|
|
93
|
-
def _show_single_task_progress(self, task:
|
|
92
|
+
def _show_single_task_progress(self, task: tuple[TaskID, _TaskProgress]) -> None:
|
|
94
93
|
_logger.debug("Single task %s", task)
|
|
95
94
|
id, progress = task
|
|
96
95
|
|
|
@@ -98,7 +97,7 @@ class StatusBarProgressDisplay(QStatusBar):
|
|
|
98
97
|
self.showMessage(id.step_name)
|
|
99
98
|
|
|
100
99
|
def _show_multiple_tasks_progress(
|
|
101
|
-
self, tasks: list[
|
|
100
|
+
self, tasks: list[tuple[TaskID, _TaskProgress]]
|
|
102
101
|
) -> None:
|
|
103
102
|
_logger.debug("Multiple tasks %s", tasks)
|
|
104
103
|
self.showMessage(
|
|
@@ -107,7 +106,7 @@ class StatusBarProgressDisplay(QStatusBar):
|
|
|
107
106
|
self._show_progress(self._calculate_progress(tasks))
|
|
108
107
|
|
|
109
108
|
def _calculate_progress(
|
|
110
|
-
self, tasks: list[
|
|
109
|
+
self, tasks: list[tuple[TaskID, _TaskProgress]]
|
|
111
110
|
) -> _TaskProgress:
|
|
112
111
|
# Task progresses are floats from 0 to 1, multiplier is used to turn them into ints
|
|
113
112
|
multiplier = 1000
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
|
|
5
|
+
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
|
|
6
|
+
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
|
|
7
|
+
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
|
|
8
|
+
*/
|
|
9
|
+
import QtQuick
|
|
10
|
+
|
|
11
|
+
ListView {
|
|
12
|
+
id: view
|
|
13
|
+
model: AttachmentsListModel {}
|
|
14
|
+
delegate: AttachmentsListDelegate {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Layouts
|
|
3
|
+
|
|
4
|
+
Item {
|
|
5
|
+
id: delegate
|
|
6
|
+
height: childrenRect.height
|
|
7
|
+
anchors {
|
|
8
|
+
left: parent.left
|
|
9
|
+
right: parent.right
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ColumnLayout {
|
|
13
|
+
spacing: 0
|
|
14
|
+
|
|
15
|
+
anchors {
|
|
16
|
+
left: parent.left
|
|
17
|
+
right: parent.right
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
RowLayout {
|
|
21
|
+
height: childrenRect.implicitHeight
|
|
22
|
+
Layout.fillWidth: true
|
|
23
|
+
spacing: 10
|
|
24
|
+
|
|
25
|
+
Image {
|
|
26
|
+
function attachmentIcon(downloadStatus) {
|
|
27
|
+
switch(downloadStatus)
|
|
28
|
+
{
|
|
29
|
+
case "DOWNLOADED":
|
|
30
|
+
return "qrc:///icons/universal/downloads/downloaded.svg";
|
|
31
|
+
case "FAILED":
|
|
32
|
+
return "qrc:///icons/universal/downloads/download_failed.svg";
|
|
33
|
+
case "NOT_DOWNLOADED":
|
|
34
|
+
return "qrc:///icons/universal/downloads/not_downloaded.svg";
|
|
35
|
+
default:
|
|
36
|
+
return "qrc:///icons/universal/downloads/unknown.svg"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
id: fileIcon
|
|
41
|
+
source: attachmentIcon(modelData.download_state)
|
|
42
|
+
fillMode: Image.PreserveAspectFit
|
|
43
|
+
sourceSize.height: 17
|
|
44
|
+
sourceSize.width: 17
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Text {
|
|
48
|
+
id: file_name_text
|
|
49
|
+
text: modelData.file_name
|
|
50
|
+
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
51
|
+
Layout.fillWidth: true
|
|
52
|
+
font.underline: true
|
|
53
|
+
color: palette.link
|
|
54
|
+
|
|
55
|
+
MouseArea {
|
|
56
|
+
id: textClickArea
|
|
57
|
+
anchors.fill: parent
|
|
58
|
+
cursorShape: Qt.PointingHandCursor
|
|
59
|
+
hoverEnabled: true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Spacer {
|
|
65
|
+
size: 5
|
|
66
|
+
visible: index !== count - 1
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Connections {
|
|
71
|
+
target: textClickArea
|
|
72
|
+
|
|
73
|
+
function onClicked() {
|
|
74
|
+
modelData.opened(modelData.resource_id)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
|
|
3
|
+
ListModel {
|
|
4
|
+
ListElement {
|
|
5
|
+
file_name: "texas.pdf"
|
|
6
|
+
resource_id: "1"
|
|
7
|
+
download_state: "NOT_DOWNLOADED"
|
|
8
|
+
}
|
|
9
|
+
ListElement {
|
|
10
|
+
file_name: "oh_no_what_a_terribly_long_file_name_its_not_like_someone_would_actually_do_this.pdf"
|
|
11
|
+
resource_id: "2"
|
|
12
|
+
download_state: "FAILED"
|
|
13
|
+
}
|
|
14
|
+
ListElement {
|
|
15
|
+
file_name: "i was transported to another world where javascript doesn't exist.cbz"
|
|
16
|
+
resource_id: "3"
|
|
17
|
+
download_state: "DOWNLOADED"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
|
|
5
|
+
It is supposed to be strictly declarative and only uses a subset of QML. If you edit
|
|
6
|
+
this file manually, you might introduce QML code that is not supported by Qt Design Studio.
|
|
7
|
+
Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
|
|
8
|
+
*/
|
|
9
|
+
import QtQuick
|
|
10
|
+
|
|
11
|
+
ListView {
|
|
12
|
+
id: view
|
|
13
|
+
model: CommentsListModel {}
|
|
14
|
+
delegate: CommentsListDelegate {}
|
|
15
|
+
}
|