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,36 +1,46 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
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 Slot
|
|
8
|
+
from PySide6.QtGui import QIcon
|
|
9
|
+
from PySide6.QtWidgets import QTabWidget, QVBoxLayout, QWidget
|
|
8
10
|
|
|
11
|
+
from qcanvas import icons
|
|
9
12
|
from qcanvas.ui.course_viewer.tabs.assignment_tab import AssignmentTab
|
|
10
13
|
from qcanvas.ui.course_viewer.tabs.mail_tab import MailTab
|
|
11
14
|
from qcanvas.ui.course_viewer.tabs.page_tab import PageTab
|
|
12
|
-
from qcanvas.util.basic_fonts import bold_font
|
|
13
15
|
from qcanvas.util.layouts import layout
|
|
14
|
-
|
|
16
|
+
import qcanvas.util.ui_tools as ui
|
|
15
17
|
|
|
16
18
|
_logger = logging.getLogger(__name__)
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
@dataclass
|
|
22
|
+
class _Tab:
|
|
23
|
+
icon: QIcon
|
|
24
|
+
highlighted_icon: QIcon
|
|
25
|
+
update_type: type
|
|
26
|
+
|
|
27
|
+
|
|
19
28
|
class CourseViewer(QWidget):
|
|
20
29
|
def __init__(
|
|
21
30
|
self,
|
|
22
31
|
course: db.Course,
|
|
23
32
|
downloader: ResourceManager,
|
|
24
33
|
*,
|
|
25
|
-
sync_receipt: SyncReceipt
|
|
34
|
+
sync_receipt: SyncReceipt,
|
|
26
35
|
):
|
|
27
36
|
super().__init__()
|
|
28
37
|
# todo this is a mess. there are several other messes like this too, do they all have to be a mess?
|
|
29
38
|
self._course_id = course.id
|
|
39
|
+
self._previous_tab_index = 0
|
|
30
40
|
|
|
31
|
-
self._course_label =
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
self._course_label = ui.label(
|
|
42
|
+
course.name, font=ui.font(point_size=13, bold=True), allow_truncation=True
|
|
43
|
+
)
|
|
34
44
|
|
|
35
45
|
self._pages_tab = PageTab.create_from_receipt(
|
|
36
46
|
course=course,
|
|
@@ -53,57 +63,80 @@ class CourseViewer(QWidget):
|
|
|
53
63
|
# sync_receipt=sync_receipt,
|
|
54
64
|
# )
|
|
55
65
|
|
|
56
|
-
self.
|
|
57
|
-
self._tabs
|
|
58
|
-
self._tabs.addTab(self._assignments_tab, "Assignments")
|
|
59
|
-
self._tabs.addTab(self._mail_tab, "Mail")
|
|
60
|
-
self._tabs.addTab(QLabel("Not implemented"), "Files")
|
|
61
|
-
# self._tabs.addTab(self._files_tab, "Files")
|
|
62
|
-
# self._tabs.addTab(QLabel("Not implemented"), "Panopto") # The meme lives on!
|
|
63
|
-
|
|
64
|
-
self.setLayout(layout(QVBoxLayout, self._course_label, self._tabs))
|
|
66
|
+
self._tab_widget = QTabWidget()
|
|
67
|
+
self._tabs: dict[int, _Tab] = {}
|
|
65
68
|
|
|
66
|
-
self.
|
|
69
|
+
# self._setup_tab(
|
|
70
|
+
# name="Files",
|
|
71
|
+
# widget=self._files_tab,
|
|
72
|
+
# icon=icons.tabs.pages,
|
|
73
|
+
# highlighted_icon=icons.tabs.pages_new_content,
|
|
74
|
+
# )
|
|
75
|
+
self._PAGES_TAB = self._set_up_tab(
|
|
76
|
+
name="Pages",
|
|
77
|
+
widget=self._pages_tab,
|
|
78
|
+
icon=icons.tabs.pages,
|
|
79
|
+
highlighted_icon=icons.tabs.pages_new_content,
|
|
80
|
+
content_update_key=db.Page,
|
|
81
|
+
)
|
|
82
|
+
self._ASSIGNMENTS_TAB = self._set_up_tab(
|
|
83
|
+
name="Assignments",
|
|
84
|
+
widget=self._assignments_tab,
|
|
85
|
+
icon=icons.tabs.assignments,
|
|
86
|
+
highlighted_icon=icons.tabs.assignments_new_content,
|
|
87
|
+
content_update_key=db.Assignment,
|
|
88
|
+
)
|
|
89
|
+
self._MAIL_TAB = self._set_up_tab(
|
|
90
|
+
name="Mail",
|
|
91
|
+
widget=self._mail_tab,
|
|
92
|
+
icon=icons.tabs.mail,
|
|
93
|
+
highlighted_icon=icons.tabs.mail_new_content,
|
|
94
|
+
content_update_key=db.Message,
|
|
95
|
+
)
|
|
96
|
+
# self._tabs.addTab(QLabel("Not implemented"), "Panopto") # The meme lives on!
|
|
67
97
|
|
|
98
|
+
self.setLayout(layout(QVBoxLayout, self._course_label, self._tab_widget))
|
|
99
|
+
self._tab_widget.currentChanged.connect(self._tab_changed)
|
|
68
100
|
self._highlight_tabs(sync_receipt)
|
|
69
|
-
|
|
101
|
+
|
|
102
|
+
def _set_up_tab(
|
|
103
|
+
self,
|
|
104
|
+
*,
|
|
105
|
+
widget: QWidget,
|
|
106
|
+
icon: QIcon,
|
|
107
|
+
highlighted_icon: QIcon,
|
|
108
|
+
name: str,
|
|
109
|
+
content_update_key: type,
|
|
110
|
+
) -> int:
|
|
111
|
+
index = self._tab_widget.addTab(widget, icon, name)
|
|
112
|
+
self._tabs[index] = _Tab(icon, highlighted_icon, update_type=content_update_key)
|
|
113
|
+
return index
|
|
70
114
|
|
|
71
115
|
def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
|
|
116
|
+
# self._files_tab.reload(course, sync_receipt=sync_receipt)
|
|
72
117
|
self._pages_tab.reload(course, sync_receipt=sync_receipt)
|
|
73
118
|
self._assignments_tab.reload(course, sync_receipt=sync_receipt)
|
|
74
119
|
self._mail_tab.reload(course, sync_receipt=sync_receipt)
|
|
75
|
-
|
|
76
120
|
self._highlight_tabs(sync_receipt)
|
|
77
121
|
|
|
78
122
|
@Slot(int)
|
|
79
123
|
def _tab_changed(self, index: int) -> None:
|
|
124
|
+
_logger.debug(f"Index = {index}")
|
|
80
125
|
if index != -1:
|
|
81
|
-
self.
|
|
126
|
+
_logger.debug(f"Previous tab = {self._previous_tab_index}")
|
|
127
|
+
self._set_tab_highlight(self._previous_tab_index, False)
|
|
128
|
+
self._previous_tab_index = index
|
|
82
129
|
|
|
83
130
|
def _highlight_tabs(self, sync_receipt: SyncReceipt) -> None:
|
|
84
131
|
updates = sync_receipt.updates_by_course.get(self._course_id, None)
|
|
85
132
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if len(updates.updated_pages) > 0:
|
|
92
|
-
self._highlight_tab(0)
|
|
93
|
-
|
|
94
|
-
if len(updates.updated_assignments) > 0:
|
|
95
|
-
self._highlight_tab(1)
|
|
96
|
-
|
|
97
|
-
if len(updates.updated_messages) > 0:
|
|
98
|
-
self._highlight_tab(2)
|
|
99
|
-
else:
|
|
100
|
-
for index in range(0, 4):
|
|
101
|
-
self._unhighlight_tab(index)
|
|
102
|
-
|
|
103
|
-
def _highlight_tab(self, tab_index: int) -> None:
|
|
104
|
-
self._tabs.setTabText(tab_index, "* " + self._tabs.tabText(tab_index))
|
|
133
|
+
for tab_index, tab in enumerate(self._tabs.values()):
|
|
134
|
+
self._set_tab_highlight(
|
|
135
|
+
tab_index, updates is not None and updates[tab.update_type] is not None
|
|
136
|
+
)
|
|
105
137
|
|
|
106
|
-
def
|
|
107
|
-
self._tabs
|
|
108
|
-
|
|
138
|
+
def _set_tab_highlight(self, tab_index: int, highlighted: bool) -> None:
|
|
139
|
+
tab = self._tabs[tab_index]
|
|
140
|
+
self._tab_widget.setTabIcon(
|
|
141
|
+
tab_index, tab.highlighted_icon if highlighted else tab.icon
|
|
109
142
|
)
|
|
@@ -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,12 +1,14 @@
|
|
|
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
|
+
from qcanvas import icons
|
|
9
10
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
11
|
+
from qcanvas.ui.course_viewer.tree_widget_data_item import TreeWidgetDataItem
|
|
10
12
|
from qcanvas.ui.memory_tree import MemoryTreeWidgetItem
|
|
11
13
|
|
|
12
14
|
_logger = logging.getLogger(__name__)
|
|
@@ -18,6 +20,7 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
18
20
|
tree_name=f"course.{course_id}.assignment_groups",
|
|
19
21
|
emit_selection_signal_for_type=db.Assignment,
|
|
20
22
|
)
|
|
23
|
+
|
|
21
24
|
self.ui_setup(
|
|
22
25
|
header_text=["Assignments", "Weight"],
|
|
23
26
|
indentation=15,
|
|
@@ -25,13 +28,9 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
25
28
|
min_width=150,
|
|
26
29
|
)
|
|
27
30
|
|
|
28
|
-
self.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
header = self.header()
|
|
32
|
-
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
|
33
|
-
header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
|
|
34
|
-
header.setStretchLastSection(False)
|
|
31
|
+
self.set_columns_resize_mode(
|
|
32
|
+
[QHeaderView.ResizeMode.Stretch, QHeaderView.ResizeMode.ResizeToContents]
|
|
33
|
+
)
|
|
35
34
|
|
|
36
35
|
def create_tree_items(
|
|
37
36
|
self, course: db.Course, sync_receipt: SyncReceipt
|
|
@@ -39,8 +38,11 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
39
38
|
widgets = []
|
|
40
39
|
|
|
41
40
|
for assignment_group in course.assignment_groups: # type: db.AssignmentGroup
|
|
41
|
+
if len(assignment_group.assignments) == 0:
|
|
42
|
+
continue
|
|
43
|
+
|
|
42
44
|
assignment_group_widget = self._create_assignment_group_widget(
|
|
43
|
-
assignment_group
|
|
45
|
+
assignment_group
|
|
44
46
|
)
|
|
45
47
|
|
|
46
48
|
widgets.append(assignment_group_widget)
|
|
@@ -54,7 +56,7 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
54
56
|
return widgets
|
|
55
57
|
|
|
56
58
|
def _create_assignment_group_widget(
|
|
57
|
-
self, assignment_group: db.AssignmentGroup
|
|
59
|
+
self, assignment_group: db.AssignmentGroup
|
|
58
60
|
) -> MemoryTreeWidgetItem:
|
|
59
61
|
assignment_group_widget = MemoryTreeWidgetItem(
|
|
60
62
|
id=assignment_group.id,
|
|
@@ -63,22 +65,21 @@ class AssignmentTree(ContentTree[db.Course]):
|
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
assignment_group_widget.setFlags(Qt.ItemFlag.ItemIsEnabled)
|
|
66
|
-
|
|
67
|
-
if sync_receipt.was_updated(assignment_group):
|
|
68
|
-
self.mark_as_unseen(assignment_group_widget)
|
|
68
|
+
assignment_group_widget.setIcon(0, icons.tree_items.module)
|
|
69
69
|
|
|
70
70
|
return assignment_group_widget
|
|
71
71
|
|
|
72
72
|
def _create_assignment_widget(
|
|
73
73
|
self, assignment: db.Assignment, sync_receipt: SyncReceipt
|
|
74
|
-
) ->
|
|
75
|
-
assignment_widget =
|
|
74
|
+
) -> TreeWidgetDataItem:
|
|
75
|
+
assignment_widget = TreeWidgetDataItem(
|
|
76
76
|
id=assignment.id, data=assignment, strings=[assignment.name]
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
assignment_widget.setFlags(
|
|
80
80
|
Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
|
|
81
81
|
)
|
|
82
|
+
assignment_widget.setIcon(0, icons.tree_items.assignment)
|
|
82
83
|
|
|
83
84
|
if sync_receipt.was_updated(assignment):
|
|
84
85
|
self.mark_as_unseen(assignment_widget)
|
|
@@ -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()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .file_tab import FileTab
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
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
|
+
|
|
8
|
+
from qcanvas.ui.course_viewer.tabs.file_tab.pages_file_tree import PagesFileTree
|
|
9
|
+
from qcanvas.util.layouts import layout
|
|
10
|
+
|
|
11
|
+
_logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileTab(QWidget):
|
|
15
|
+
@staticmethod
|
|
16
|
+
def create_from_receipt(
|
|
17
|
+
*,
|
|
18
|
+
course: db.Course,
|
|
19
|
+
sync_receipt: SyncReceipt,
|
|
20
|
+
downloader: ResourceManager,
|
|
21
|
+
) -> "FileTab":
|
|
22
|
+
return FileTab(course=course, sync_receipt=sync_receipt, downloader=downloader)
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
course: db.Course,
|
|
27
|
+
downloader: ResourceManager,
|
|
28
|
+
*,
|
|
29
|
+
sync_receipt: SyncReceipt = None,
|
|
30
|
+
):
|
|
31
|
+
super().__init__()
|
|
32
|
+
|
|
33
|
+
self._page_file_tree = PagesFileTree.create_from_receipt(
|
|
34
|
+
course, sync_receipt=sync_receipt, resource_manager=downloader
|
|
35
|
+
)
|
|
36
|
+
# self._assignment_file_tree = FileTree.create_from_receipt(
|
|
37
|
+
# course,
|
|
38
|
+
# sync_receipt=sync_receipt,
|
|
39
|
+
# mode="assignments",
|
|
40
|
+
# resource_manager=downloader,
|
|
41
|
+
# )
|
|
42
|
+
|
|
43
|
+
self.setLayout(layout(QHBoxLayout, self._page_file_tree))
|
|
44
|
+
|
|
45
|
+
def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
|
|
46
|
+
pass
|