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.
Files changed (132) hide show
  1. qcanvas/__init__.py +60 -0
  2. qcanvas/app.py +72 -0
  3. qcanvas/backend_connectors/frontend_resource_manager.py +13 -5
  4. qcanvas/backend_connectors/qcanvas_task_master.py +2 -2
  5. qcanvas/icons/__init__.py +5 -5
  6. qcanvas/icons/_icon_type.py +1 -1
  7. qcanvas/icons/icons.qrc +39 -35
  8. qcanvas/icons/rc_icons.py +1298 -1197
  9. qcanvas/settings/__init__.py +6 -0
  10. qcanvas/{util/settings → settings}/_client_settings.py +4 -4
  11. qcanvas/settings/_course_settings.py +54 -0
  12. qcanvas/{util/settings → settings}/_mapped_setting.py +2 -5
  13. qcanvas/{util/settings → settings}/_ui_settings.py +5 -5
  14. qcanvas/theme.py +101 -0
  15. qcanvas/ui/course_viewer/content_tree.py +9 -12
  16. qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +3 -3
  17. qcanvas/ui/course_viewer/course_tree/course_tree.py +9 -8
  18. qcanvas/ui/course_viewer/course_viewer.py +42 -56
  19. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
  20. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -4
  21. qcanvas/ui/course_viewer/tabs/constants.py +1 -0
  22. qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
  23. qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +4 -4
  24. qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +7 -10
  25. qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +6 -7
  26. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
  27. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +7 -8
  28. qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
  29. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +5 -5
  30. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +18 -32
  31. qcanvas/ui/course_viewer/tree_widget_data_item.py +1 -1
  32. qcanvas/ui/memory_tree/_tree_memory.py +45 -42
  33. qcanvas/ui/memory_tree/memory_tree_widget.py +22 -18
  34. qcanvas/ui/memory_tree/memory_tree_widget_item.py +3 -3
  35. qcanvas/ui/qcanvas_window/__init__.py +1 -0
  36. qcanvas/ui/{main_ui → qcanvas_window}/course_viewer_container.py +10 -10
  37. qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +5 -5
  38. qcanvas/ui/{main_ui → qcanvas_window}/options/quick_sync_option.py +7 -6
  39. qcanvas/ui/{main_ui → qcanvas_window}/options/sync_on_start_option.py +7 -6
  40. qcanvas/ui/{main_ui → qcanvas_window}/options/theme_selection_menu.py +10 -10
  41. qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +57 -41
  42. qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +5 -6
  43. qcanvas/ui/qml_components/__init__.py +4 -0
  44. qcanvas/ui/qml_components/attachments_pane.py +70 -0
  45. qcanvas/ui/qml_components/comments_pane.py +83 -0
  46. qcanvas/ui/qml_components/qml/AttachmentsList.ui.qml +15 -0
  47. qcanvas/ui/qml_components/qml/AttachmentsListDelegate.qml +77 -0
  48. qcanvas/ui/qml_components/qml/AttachmentsListModel.qml +19 -0
  49. qcanvas/ui/qml_components/qml/AttachmentsPane.qml +11 -0
  50. qcanvas/ui/qml_components/qml/CommentsList.ui.qml +15 -0
  51. qcanvas/ui/qml_components/qml/CommentsListDelegate.ui.qml +118 -0
  52. qcanvas/ui/qml_components/qml/CommentsListModel.qml +56 -0
  53. qcanvas/ui/qml_components/qml/CommentsPane.qml +11 -0
  54. qcanvas/ui/qml_components/qml/DecoratedText.ui.qml +44 -0
  55. qcanvas/ui/qml_components/qml/Spacer.ui.qml +7 -0
  56. qcanvas/ui/qml_components/qml/ThemedRectangle.qml +53 -0
  57. qcanvas/ui/qml_components/qml/__init__.py +3 -0
  58. qcanvas/ui/qml_components/qml/rc_qml.py +709 -0
  59. qcanvas/ui/qml_components/qml/rc_qml.qrc +16 -0
  60. qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
  61. qcanvas/ui/qml_components/qml_pane.py +21 -0
  62. qcanvas/ui/setup/setup_checker.py +1 -1
  63. qcanvas/ui/setup/setup_dialog.py +28 -14
  64. qcanvas/util/auto_downloader.py +9 -7
  65. qcanvas/util/basic_fonts.py +2 -2
  66. qcanvas/util/context_dict.py +12 -0
  67. qcanvas/util/file_icons.py +11 -19
  68. qcanvas/util/layouts.py +5 -7
  69. qcanvas/util/paths.py +17 -6
  70. qcanvas/util/qurl_util.py +1 -1
  71. qcanvas/util/ui_tools.py +118 -8
  72. qcanvas/util/url_checker.py +1 -1
  73. qcanvas-2026.1.19.dist-info/METADATA +95 -0
  74. qcanvas-2026.1.19.dist-info/RECORD +92 -0
  75. {qcanvas-1.2.0.dist-info → qcanvas-2026.1.19.dist-info}/WHEEL +1 -1
  76. qcanvas-2026.1.19.dist-info/entry_points.txt +3 -0
  77. qcanvas/app_start/__init__.py +0 -59
  78. qcanvas/icons/_update_icons.py +0 -89
  79. qcanvas/icons/dark/actions/exit.svg +0 -3
  80. qcanvas/icons/dark/actions/mark_all_read.svg +0 -3
  81. qcanvas/icons/dark/actions/open_downloads.svg +0 -3
  82. qcanvas/icons/dark/actions/quick_login.svg +0 -3
  83. qcanvas/icons/dark/actions/sync.svg +0 -3
  84. qcanvas/icons/dark/branding/logo_transparent.svg +0 -303
  85. qcanvas/icons/dark/options/auto_download.svg +0 -3
  86. qcanvas/icons/dark/options/theme.svg +0 -3
  87. qcanvas/icons/dark/tabs/assignments.svg +0 -3
  88. qcanvas/icons/dark/tabs/mail.svg +0 -3
  89. qcanvas/icons/dark/tabs/pages.svg +0 -3
  90. qcanvas/icons/dark/tree_items/assignment.svg +0 -3
  91. qcanvas/icons/dark/tree_items/mail.svg +0 -3
  92. qcanvas/icons/dark/tree_items/module.svg +0 -3
  93. qcanvas/icons/dark/tree_items/page.svg +0 -3
  94. qcanvas/icons/light/actions/exit.svg +0 -3
  95. qcanvas/icons/light/actions/mark_all_read.svg +0 -3
  96. qcanvas/icons/light/actions/open_downloads.svg +0 -3
  97. qcanvas/icons/light/actions/quick_login.svg +0 -3
  98. qcanvas/icons/light/actions/sync.svg +0 -3
  99. qcanvas/icons/light/branding/logo_transparent.svg +0 -304
  100. qcanvas/icons/light/options/auto_download.svg +0 -3
  101. qcanvas/icons/light/options/ignore_old.svg +0 -3
  102. qcanvas/icons/light/options/include_videos.svg +0 -3
  103. qcanvas/icons/light/options/theme.svg +0 -3
  104. qcanvas/icons/light/tabs/assignments.svg +0 -3
  105. qcanvas/icons/light/tabs/mail.svg +0 -3
  106. qcanvas/icons/light/tabs/pages.svg +0 -3
  107. qcanvas/icons/light/tree_items/assignment.svg +0 -3
  108. qcanvas/icons/light/tree_items/mail.svg +0 -3
  109. qcanvas/icons/light/tree_items/module.svg +0 -3
  110. qcanvas/icons/light/tree_items/page.svg +0 -3
  111. qcanvas/icons/universal/branding/main_icon.svg +0 -325
  112. qcanvas/icons/universal/downloads/download_failed.svg +0 -23
  113. qcanvas/icons/universal/downloads/downloaded.svg +0 -23
  114. qcanvas/icons/universal/downloads/not_downloaded.svg +0 -23
  115. qcanvas/icons/universal/downloads/unknown.svg +0 -6
  116. qcanvas/icons/universal/tabs/assignments_new_content.svg +0 -3
  117. qcanvas/icons/universal/tabs/mail_new_content.svg +0 -3
  118. qcanvas/icons/universal/tabs/pages_new_content.svg +0 -3
  119. qcanvas/icons/universal/tree_items/semester.svg +0 -108
  120. qcanvas/run.py +0 -54
  121. qcanvas/ui/course_viewer/tabs/util.py +0 -11
  122. qcanvas/ui/main_ui/__init__.py +0 -0
  123. qcanvas/util/settings/__init__.py +0 -9
  124. qcanvas/util/themes/__init__.py +0 -2
  125. qcanvas/util/themes/_colour_scheme_helper.py +0 -38
  126. qcanvas/util/themes/_selected_theme.py +0 -10
  127. qcanvas/util/themes/_theme_changed_event.py +0 -17
  128. qcanvas/util/themes/_theme_changer.py +0 -86
  129. qcanvas-1.2.0.dist-info/METADATA +0 -71
  130. qcanvas-1.2.0.dist-info/RECORD +0 -118
  131. qcanvas-1.2.0.dist-info/entry_points.txt +0 -3
  132. /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
@@ -1,28 +1,40 @@
1
1
  import logging
2
2
 
3
- import qcanvas_backend.database.types as db
4
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
5
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
6
- from qtpy.QtWidgets import *
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
- from qcanvas.ui.course_viewer.tabs.assignment_tab.assignment_tree import AssignmentTree
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.util import date_strftime_format
11
- from qcanvas.util.basic_fonts import bold_label
12
- from qcanvas.util.layouts import grid_layout
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: ResourceManager,
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
- def setup_info_grid(self) -> QGridLayout:
40
- grid = grid_layout(
41
- [
42
- [
43
- bold_label("Due:"),
44
- self._due_date_label,
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
- grid.setColumnStretch(0, 0)
54
- grid.setColumnStretch(1, 1)
98
+ self.content_grid.setColumnStretch(0, 1)
99
+ self.content_grid.setColumnStretch(1, 3)
55
100
 
56
- return grid
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"{assignment.mark or '?'}/{assignment.max_mark_possible or '?'}"
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
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
6
- from qtpy.QtCore import Qt
7
- from qtpy.QtWidgets import QHeaderView
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
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
6
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
7
- from qtpy.QtCore import Slot
8
- from qtpy.QtWidgets import *
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
- _logger = logging.getLogger(__name__)
17
+ import qcanvas.util.ui_tools as ui
16
18
 
17
- T = TypeVar("T", bound=Type["ContentTab"])
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
- ) -> Type[T]:
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._content_vbox = QVBoxLayout()
41
+ self.content_grid = QGridLayout()
40
42
  self._placeholder_text = title_placeholder_text
41
43
  self._title_label = self._create_title_label()
42
- self._info_grid: Optional[QWidget] = None
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
- # Info grid needs to be a widget, so it can be hidden/shown
51
- grid_layout = self.setup_info_grid()
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
- title_label = QLabel(self._placeholder_text)
62
- title_label.setFont(bold_font)
63
- make_truncatable(title_label)
64
- return title_label
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) -> QGridLayout:
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
- parent_layout = QHBoxLayout()
74
- parent_layout.addWidget(self._explorer)
75
-
76
- self._content_vbox.addWidget(self._title_label)
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._info_grid is not None:
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._info_grid is not None:
103
+ if self._use_info_grid:
110
104
  self._info_grid.hide()
@@ -1,9 +1,9 @@
1
1
  import logging
2
2
 
3
- import qcanvas_backend.database.types as db
4
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
5
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
6
- from qtpy.QtWidgets import *
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 typing import *
2
+ from abc import ABC
3
3
 
4
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
6
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
7
- from qtpy.QtCore import QPoint, Qt, Slot
8
- from qtpy.QtWidgets import *
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
- T = TypeVar("T")
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
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
6
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
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 len(group.content_items) == 0:
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.content_items:
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
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
6
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
7
- from qtpy.QtWidgets import *
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.util import date_strftime_format
12
- from qcanvas.util.basic_fonts import bold_label
13
- from qcanvas.util.layouts import grid_layout
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: ResourceManager,
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
- def setup_info_grid(self) -> QGridLayout:
39
- grid = grid_layout(
40
- [
41
- [
42
- bold_label("From:"),
43
- self._sender_label,
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
- grid.setColumnStretch(0, 0)
53
- grid.setColumnStretch(1, 1)
54
-
55
- return grid
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.CourseMessage) -> None:
58
- self._date_sent_label.setText(mail.creation_date.strftime(date_strftime_format))
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
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
6
- from qtpy.QtWidgets import *
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.CourseMessage,
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.CourseMessage
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.CourseMessage, sync_receipt: SyncReceipt
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
- import qcanvas_backend.database.types as db
4
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
5
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
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
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
6
- from qtpy.QtCore import Qt
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.ModulePage,
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.ModulePage, sync_receipt: SyncReceipt
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)