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
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
3
|
+
from PySide6.QtCore import Qt
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from libqcanvas.util import as_local
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QLabel,
|
|
9
|
+
QLayout,
|
|
10
|
+
QDockWidget,
|
|
11
|
+
QMainWindow,
|
|
12
|
+
)
|
|
13
|
+
from typing_extensions import override
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
import qcanvas.util.ui_tools as ui
|
|
16
|
+
from qcanvas.backend_connectors import FrontendResourceManager
|
|
17
|
+
from .assignment_tree import AssignmentTree
|
|
18
|
+
from qcanvas.ui.qml_components import CommentsPane, AttachmentsPane
|
|
9
19
|
from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
|
|
10
|
-
from qcanvas.ui.course_viewer.tabs.
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
from qcanvas.ui.course_viewer.tabs.constants import (
|
|
21
|
+
date_strftime_format,
|
|
22
|
+
)
|
|
13
23
|
|
|
14
24
|
_logger = logging.getLogger(__name__)
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
class AssignmentTab(ContentTab):
|
|
18
|
-
|
|
19
28
|
def __init__(
|
|
20
29
|
self,
|
|
21
30
|
*,
|
|
22
31
|
course: db.Course,
|
|
23
32
|
sync_receipt: SyncReceipt,
|
|
24
|
-
downloader:
|
|
33
|
+
downloader: FrontendResourceManager,
|
|
25
34
|
):
|
|
35
|
+
# must be before super init, otherwise _setup_layout will be called before it is initialised
|
|
36
|
+
self._main_container = QMainWindow()
|
|
37
|
+
|
|
26
38
|
super().__init__(
|
|
27
39
|
explorer=AssignmentTree.create_from_receipt(
|
|
28
40
|
course, sync_receipt=sync_receipt
|
|
@@ -31,38 +43,104 @@ class AssignmentTab(ContentTab):
|
|
|
31
43
|
downloader=downloader,
|
|
32
44
|
)
|
|
33
45
|
|
|
46
|
+
# fixme: can't figure out how to get the panes to have the right size without showing them when nothing is selected
|
|
47
|
+
self._comments_pane = CommentsPane(downloader)
|
|
48
|
+
self._comments_dock = ui.dock_widget(
|
|
49
|
+
title="Comments",
|
|
50
|
+
name="comments_dock",
|
|
51
|
+
widget=self._comments_pane,
|
|
52
|
+
min_size=ui.size(150, 150),
|
|
53
|
+
features=QDockWidget.DockWidgetFeature.DockWidgetMovable,
|
|
54
|
+
hide=False,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self._submission_files_pane = AttachmentsPane(downloader)
|
|
58
|
+
self._submission_files_dock = ui.dock_widget(
|
|
59
|
+
title="Submitted Files",
|
|
60
|
+
name="sub_files_dock",
|
|
61
|
+
widget=self._submission_files_pane,
|
|
62
|
+
min_size=ui.size(150, 150),
|
|
63
|
+
features=QDockWidget.DockWidgetFeature.DockWidgetMovable,
|
|
64
|
+
hide=False,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self._main_container.setCentralWidget(self._viewer)
|
|
68
|
+
self._main_container.addDockWidget(
|
|
69
|
+
Qt.DockWidgetArea.RightDockWidgetArea, self._submission_files_dock
|
|
70
|
+
)
|
|
71
|
+
self._main_container.addDockWidget(
|
|
72
|
+
Qt.DockWidgetArea.RightDockWidgetArea, self._comments_dock
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
self._main_container.resizeDocks(
|
|
76
|
+
[self._submission_files_dock, self._comments_dock],
|
|
77
|
+
[350, 350],
|
|
78
|
+
Qt.Orientation.Horizontal,
|
|
79
|
+
)
|
|
80
|
+
self._main_container.resizeDocks(
|
|
81
|
+
[self._submission_files_dock],
|
|
82
|
+
[200],
|
|
83
|
+
Qt.Orientation.Vertical,
|
|
84
|
+
)
|
|
85
|
+
|
|
34
86
|
self._due_date_label = QLabel("")
|
|
35
87
|
self._score_label = QLabel("")
|
|
36
|
-
|
|
37
88
|
self.enable_info_grid()
|
|
38
89
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
],
|
|
46
|
-
[
|
|
47
|
-
bold_label("Score:"),
|
|
48
|
-
self._score_label,
|
|
49
|
-
],
|
|
50
|
-
]
|
|
90
|
+
@override
|
|
91
|
+
def _setup_layout(self) -> None:
|
|
92
|
+
super()._setup_layout()
|
|
93
|
+
self.content_grid.replaceWidget(
|
|
94
|
+
self._viewer,
|
|
95
|
+
self._main_container,
|
|
51
96
|
)
|
|
52
97
|
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
self.content_grid.setColumnStretch(0, 1)
|
|
99
|
+
self.content_grid.setColumnStretch(1, 3)
|
|
55
100
|
|
|
56
|
-
|
|
101
|
+
@override
|
|
102
|
+
def setup_info_grid(self) -> QLayout:
|
|
103
|
+
return ui.form_layout(
|
|
104
|
+
{"Due": self._due_date_label, "Score": self._score_label},
|
|
105
|
+
)
|
|
57
106
|
|
|
107
|
+
# fixme: kind of a misleading name? it's not just updating the info "grid" anymore
|
|
108
|
+
@override
|
|
58
109
|
def update_info_grid(self, assignment: db.Assignment) -> None:
|
|
59
110
|
if assignment.due_date is not None:
|
|
60
|
-
due_text = assignment.due_date.strftime(date_strftime_format)
|
|
111
|
+
due_text = as_local(assignment.due_date).strftime(date_strftime_format)
|
|
61
112
|
else:
|
|
62
|
-
due_text = "
|
|
113
|
+
due_text = "No due date"
|
|
63
114
|
|
|
64
115
|
self._due_date_label.setText(due_text)
|
|
65
116
|
|
|
117
|
+
last_submission = assignment.submissions[-1] if assignment.submissions else None
|
|
118
|
+
submission_score = "-"
|
|
119
|
+
|
|
120
|
+
if last_submission and last_submission.score:
|
|
121
|
+
submission_score = last_submission.score
|
|
122
|
+
|
|
66
123
|
self._score_label.setText(
|
|
67
|
-
f"{
|
|
124
|
+
f"<b>{submission_score}</b>/{assignment.max_score or '?'}"
|
|
68
125
|
)
|
|
126
|
+
|
|
127
|
+
if last_submission and last_submission.attachments:
|
|
128
|
+
self._submission_files_pane.load_files(last_submission.attachments)
|
|
129
|
+
self._submission_files_dock.show()
|
|
130
|
+
else:
|
|
131
|
+
self._submission_files_pane.clear_files()
|
|
132
|
+
self._submission_files_dock.hide()
|
|
133
|
+
|
|
134
|
+
if last_submission and last_submission.comments:
|
|
135
|
+
self._comments_pane.load_comments(last_submission.comments)
|
|
136
|
+
self._comments_dock.show()
|
|
137
|
+
else:
|
|
138
|
+
self._comments_pane.clear_comments()
|
|
139
|
+
self._comments_dock.hide()
|
|
140
|
+
|
|
141
|
+
@override
|
|
142
|
+
def _show_blank(self) -> None:
|
|
143
|
+
super()._show_blank()
|
|
144
|
+
|
|
145
|
+
self._comments_dock.hide()
|
|
146
|
+
self._submission_files_dock.hide()
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtCore import Qt
|
|
7
|
+
from PySide6.QtWidgets import QHeaderView
|
|
8
8
|
|
|
9
9
|
from qcanvas import icons
|
|
10
10
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
date_strftime_format = "%A, %Y-%m-%d, %H:%M:%S"
|
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import *
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
3
|
+
from libqcanvas import db
|
|
4
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtCore import Slot
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QGridLayout,
|
|
9
|
+
QLabel,
|
|
10
|
+
QWidget,
|
|
11
|
+
QLayout,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
11
15
|
from qcanvas.ui.course_viewer.tabs.resource_rich_browser import ResourceRichBrowser
|
|
12
|
-
from qcanvas.util.basic_fonts import bold_font
|
|
13
|
-
from qcanvas.util.ui_tools import make_truncatable
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
import qcanvas.util.ui_tools as ui
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
_logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ContentTab(QWidget):
|
|
21
23
|
@classmethod
|
|
22
|
-
def create_from_receipt(
|
|
24
|
+
def create_from_receipt[T: ContentTab](
|
|
23
25
|
cls: T,
|
|
24
26
|
*,
|
|
25
27
|
course: db.Course,
|
|
26
28
|
sync_receipt: SyncReceipt,
|
|
27
29
|
downloader: ResourceManager,
|
|
28
|
-
) ->
|
|
30
|
+
) -> type[T]:
|
|
29
31
|
return cls(course=course, sync_receipt=sync_receipt, downloader=downloader)
|
|
30
32
|
|
|
31
33
|
def __init__(
|
|
@@ -36,49 +38,41 @@ class ContentTab(QWidget):
|
|
|
36
38
|
downloader: ResourceManager,
|
|
37
39
|
):
|
|
38
40
|
super().__init__()
|
|
39
|
-
self.
|
|
41
|
+
self.content_grid = QGridLayout()
|
|
40
42
|
self._placeholder_text = title_placeholder_text
|
|
41
43
|
self._title_label = self._create_title_label()
|
|
42
|
-
self.
|
|
44
|
+
self._use_info_grid = False
|
|
45
|
+
self._info_grid = QWidget()
|
|
46
|
+
self._info_grid.hide()
|
|
43
47
|
self._viewer = ResourceRichBrowser(downloader=downloader)
|
|
44
48
|
self._explorer = explorer
|
|
45
49
|
|
|
50
|
+
self.setLayout(self.content_grid)
|
|
46
51
|
self._setup_layout()
|
|
47
52
|
self._explorer.item_selected.connect(self._item_selected)
|
|
48
53
|
|
|
49
54
|
def enable_info_grid(self) -> None:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
grid_widget = QWidget()
|
|
54
|
-
grid_widget.setLayout(grid_layout)
|
|
55
|
-
grid_widget.hide()
|
|
56
|
-
|
|
57
|
-
self._info_grid = grid_widget
|
|
58
|
-
self._content_vbox.insertWidget(1, grid_widget)
|
|
55
|
+
self._info_grid.setLayout(self.setup_info_grid())
|
|
56
|
+
self._use_info_grid = True
|
|
59
57
|
|
|
60
58
|
def _create_title_label(self) -> QLabel:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
return ui.label(
|
|
60
|
+
self._placeholder_text,
|
|
61
|
+
font=ui.font(point_size=12, bold=True),
|
|
62
|
+
allow_truncation=True,
|
|
63
|
+
)
|
|
65
64
|
|
|
66
|
-
def setup_info_grid(self) ->
|
|
65
|
+
def setup_info_grid(self) -> QLayout:
|
|
67
66
|
"""
|
|
68
67
|
Override this if you need an info grid
|
|
69
68
|
"""
|
|
70
69
|
raise NotImplementedError()
|
|
71
70
|
|
|
72
71
|
def _setup_layout(self) -> None:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
self.
|
|
77
|
-
self._content_vbox.addWidget(self._viewer)
|
|
78
|
-
|
|
79
|
-
parent_layout.addLayout(self._content_vbox)
|
|
80
|
-
|
|
81
|
-
self.setLayout(parent_layout)
|
|
72
|
+
self.content_grid.addWidget(self._explorer, 0, 0, 4, 1)
|
|
73
|
+
self.content_grid.addWidget(self._title_label, 0, 1)
|
|
74
|
+
self.content_grid.addWidget(self._info_grid, 1, 1)
|
|
75
|
+
self.content_grid.addWidget(self._viewer, 2, 1)
|
|
82
76
|
|
|
83
77
|
def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
|
|
84
78
|
self._explorer.reload(course, sync_receipt=sync_receipt)
|
|
@@ -95,7 +89,7 @@ class ContentTab(QWidget):
|
|
|
95
89
|
self._title_label.setText(item.name)
|
|
96
90
|
self._viewer.show_content(item)
|
|
97
91
|
|
|
98
|
-
if self.
|
|
92
|
+
if self._use_info_grid:
|
|
99
93
|
self._info_grid.show()
|
|
100
94
|
self.update_info_grid(item)
|
|
101
95
|
|
|
@@ -106,5 +100,5 @@ class ContentTab(QWidget):
|
|
|
106
100
|
self._title_label.setText(self._placeholder_text)
|
|
107
101
|
self._viewer.show_blank(completely_blank=True)
|
|
108
102
|
|
|
109
|
-
if self.
|
|
103
|
+
if self._use_info_grid:
|
|
110
104
|
self._info_grid.hide()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
3
|
+
from libqcanvas import db
|
|
4
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtWidgets import QHBoxLayout, QWidget
|
|
7
7
|
|
|
8
8
|
from qcanvas.ui.course_viewer.tabs.file_tab.pages_file_tree import PagesFileTree
|
|
9
9
|
from qcanvas.util.layouts import layout
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
2
|
+
from abc import ABC
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
6
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
7
|
+
from PySide6.QtCore import QPoint, Qt, Slot
|
|
8
|
+
from PySide6.QtWidgets import QHeaderView, QMenu, QTreeWidgetItem
|
|
9
9
|
|
|
10
10
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
11
11
|
from qcanvas.ui.course_viewer.tree_widget_data_item import (
|
|
@@ -19,10 +19,7 @@ from qcanvas.util.ui_tools import create_qaction
|
|
|
19
19
|
_logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class FileTree(ContentTree[db.Course]):
|
|
22
|
+
class FileTree(ContentTree[db.Course], ABC):
|
|
26
23
|
@classmethod
|
|
27
24
|
def create_from_receipt(
|
|
28
25
|
cls,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
6
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
7
7
|
|
|
8
8
|
from qcanvas.ui.course_viewer.tabs.file_tab.file_tree import FileTree
|
|
9
9
|
from qcanvas.ui.memory_tree import MemoryTreeWidgetItem
|
|
@@ -12,7 +12,6 @@ _logger = logging.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PagesFileTree(FileTree):
|
|
15
|
-
|
|
16
15
|
def __init__(self, tree_name: str, *, resource_manager: ResourceManager):
|
|
17
16
|
super().__init__(
|
|
18
17
|
tree_name=f"{tree_name}.pages", resource_manager=resource_manager
|
|
@@ -24,14 +23,14 @@ class PagesFileTree(FileTree):
|
|
|
24
23
|
widgets = []
|
|
25
24
|
|
|
26
25
|
for group in data.modules: # type: db.Module
|
|
27
|
-
if
|
|
26
|
+
if not group.pages:
|
|
28
27
|
continue
|
|
29
28
|
|
|
30
29
|
# Init group_widget lazily to prevent creating pointless tree widgets
|
|
31
30
|
group_widget: MemoryTreeWidgetItem | None = None
|
|
32
31
|
items_in_group = set()
|
|
33
32
|
|
|
34
|
-
for item in group.
|
|
33
|
+
for item in group.pages:
|
|
35
34
|
resource_widgets = []
|
|
36
35
|
|
|
37
36
|
for resource in item.resources: # type: db.Resource
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import override
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
4
|
+
from PySide6.QtCore import Qt
|
|
5
|
+
from libqcanvas import db
|
|
6
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
7
|
+
from libqcanvas.util import as_local
|
|
8
|
+
from PySide6.QtWidgets import QLabel, QLayout, QMainWindow, QDockWidget
|
|
8
9
|
|
|
10
|
+
from qcanvas.backend_connectors import FrontendResourceManager
|
|
9
11
|
from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
|
|
10
12
|
from qcanvas.ui.course_viewer.tabs.mail_tab.mail_tree import MailTree
|
|
11
|
-
from qcanvas.ui.course_viewer.tabs.
|
|
12
|
-
|
|
13
|
-
from qcanvas.
|
|
13
|
+
from qcanvas.ui.course_viewer.tabs.constants import date_strftime_format
|
|
14
|
+
import qcanvas.util.ui_tools as ui
|
|
15
|
+
from qcanvas.ui.qml_components import AttachmentsPane
|
|
14
16
|
|
|
15
17
|
_logger = logging.getLogger(__name__)
|
|
16
18
|
|
|
@@ -22,38 +24,59 @@ class MailTab(ContentTab):
|
|
|
22
24
|
*,
|
|
23
25
|
course: db.Course,
|
|
24
26
|
sync_receipt: SyncReceipt,
|
|
25
|
-
downloader:
|
|
27
|
+
downloader: FrontendResourceManager,
|
|
26
28
|
):
|
|
29
|
+
self._main_container = QMainWindow()
|
|
30
|
+
|
|
27
31
|
super().__init__(
|
|
28
32
|
explorer=MailTree.create_from_receipt(course, sync_receipt=sync_receipt),
|
|
29
33
|
title_placeholder_text="No mail selected",
|
|
30
34
|
downloader=downloader,
|
|
31
35
|
)
|
|
32
36
|
|
|
37
|
+
self._main_container.setCentralWidget(self._viewer)
|
|
38
|
+
self._files_pane = AttachmentsPane(downloader)
|
|
39
|
+
self._files_dock = ui.dock_widget(
|
|
40
|
+
widget=self._files_pane,
|
|
41
|
+
title="Attachments",
|
|
42
|
+
name="attachments",
|
|
43
|
+
min_size=ui.size(150, 100),
|
|
44
|
+
features=QDockWidget.DockWidgetFeature.DockWidgetMovable,
|
|
45
|
+
)
|
|
46
|
+
self._main_container.addDockWidget(
|
|
47
|
+
Qt.DockWidgetArea.TopDockWidgetArea, self._files_dock
|
|
48
|
+
)
|
|
33
49
|
self._date_sent_label = QLabel("")
|
|
34
50
|
self._sender_label = QLabel("")
|
|
35
51
|
|
|
36
52
|
self.enable_info_grid()
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
],
|
|
45
|
-
[
|
|
46
|
-
bold_label("Date:"),
|
|
47
|
-
self._date_sent_label,
|
|
48
|
-
],
|
|
49
|
-
]
|
|
54
|
+
@override
|
|
55
|
+
def _setup_layout(self) -> None:
|
|
56
|
+
super()._setup_layout()
|
|
57
|
+
self.content_grid.replaceWidget(
|
|
58
|
+
self._viewer,
|
|
59
|
+
self._main_container,
|
|
50
60
|
)
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
def setup_info_grid(self) -> QLayout:
|
|
63
|
+
return ui.form_layout(
|
|
64
|
+
{"From": self._sender_label, "Date": self._date_sent_label},
|
|
65
|
+
)
|
|
56
66
|
|
|
57
|
-
def update_info_grid(self, mail: db.
|
|
58
|
-
self._date_sent_label.setText(
|
|
67
|
+
def update_info_grid(self, mail: db.Message) -> None:
|
|
68
|
+
self._date_sent_label.setText(
|
|
69
|
+
as_local(mail.creation_date).strftime(date_strftime_format)
|
|
70
|
+
)
|
|
59
71
|
self._sender_label.setText(mail.sender_name)
|
|
72
|
+
|
|
73
|
+
if mail.attachments:
|
|
74
|
+
self._files_pane.load_files(mail.attachments)
|
|
75
|
+
self._files_dock.show()
|
|
76
|
+
else:
|
|
77
|
+
self._files_dock.hide()
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def _show_blank(self) -> None:
|
|
81
|
+
super()._show_blank()
|
|
82
|
+
self._files_dock.hide()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtWidgets import QHeaderView
|
|
7
7
|
|
|
8
8
|
from qcanvas import icons
|
|
9
9
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
@@ -13,11 +13,10 @@ _logger = logging.getLogger(__name__)
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MailTree(ContentTree[db.Course]):
|
|
16
|
-
|
|
17
16
|
def __init__(self, course_id: str):
|
|
18
17
|
super().__init__(
|
|
19
18
|
tree_name=f"course.{course_id}.mail",
|
|
20
|
-
emit_selection_signal_for_type=db.
|
|
19
|
+
emit_selection_signal_for_type=db.Message,
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
self.ui_setup(
|
|
@@ -37,7 +36,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
37
36
|
) -> Sequence[TreeWidgetDataItem]:
|
|
38
37
|
widgets = []
|
|
39
38
|
|
|
40
|
-
for message in course.messages: # type: db.
|
|
39
|
+
for message in course.messages: # type: db.Message
|
|
41
40
|
message_widget = self._create_mail_widget(message, sync_receipt)
|
|
42
41
|
message_widget.setIcon(0, icons.tree_items.mail)
|
|
43
42
|
widgets.append(message_widget)
|
|
@@ -45,7 +44,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
45
44
|
return widgets
|
|
46
45
|
|
|
47
46
|
def _create_mail_widget(
|
|
48
|
-
self, message: db.
|
|
47
|
+
self, message: db.Message, sync_receipt: SyncReceipt
|
|
49
48
|
) -> TreeWidgetDataItem:
|
|
50
49
|
message_widget = TreeWidgetDataItem(
|
|
51
50
|
id=message.id,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from libqcanvas import db
|
|
4
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
6
|
|
|
7
7
|
from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
|
|
8
8
|
from qcanvas.ui.course_viewer.tabs.page_tab.page_tree import PageTree
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtCore import Qt
|
|
7
7
|
|
|
8
8
|
from qcanvas import icons
|
|
9
9
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
@@ -17,7 +17,7 @@ class PageTree(ContentTree[db.Course]):
|
|
|
17
17
|
def __init__(self, course_id: str):
|
|
18
18
|
super().__init__(
|
|
19
19
|
tree_name=f"course.{course_id}.modules",
|
|
20
|
-
emit_selection_signal_for_type=db.
|
|
20
|
+
emit_selection_signal_for_type=db.Page,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
self.ui_setup(
|
|
@@ -52,7 +52,7 @@ class PageTree(ContentTree[db.Course]):
|
|
|
52
52
|
return module_widget
|
|
53
53
|
|
|
54
54
|
def _create_page_widget(
|
|
55
|
-
self, page: db.
|
|
55
|
+
self, page: db.Page, sync_receipt: SyncReceipt
|
|
56
56
|
) -> TreeWidgetDataItem:
|
|
57
57
|
page_widget = TreeWidgetDataItem(id=page.id, data=page, strings=[page.name])
|
|
58
58
|
page_widget.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
|