qcanvas 1.0.12.dev3__py3-none-any.whl → 1.2.0a0__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.

Files changed (36) hide show
  1. qcanvas/app_start/__init__.py +6 -1
  2. qcanvas/icons/__init__.py +1 -0
  3. qcanvas/icons/file-download-failed.svg +22 -5
  4. qcanvas/icons/file-downloaded.svg +22 -5
  5. qcanvas/icons/file-not-downloaded.svg +22 -5
  6. qcanvas/icons/icons.qrc +1 -0
  7. qcanvas/icons/logo-transparent-light.svg +304 -0
  8. qcanvas/icons/rc_icons.py +599 -330
  9. qcanvas/run.py +26 -1
  10. qcanvas/ui/course_viewer/content_tree.py +21 -4
  11. qcanvas/ui/course_viewer/course_viewer.py +13 -14
  12. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +9 -12
  13. qcanvas/ui/course_viewer/tabs/file_tab/__init__.py +1 -0
  14. qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +46 -0
  15. qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +95 -0
  16. qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +50 -0
  17. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +4 -7
  18. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +7 -9
  19. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +17 -8
  20. qcanvas/ui/course_viewer/tabs/util.py +10 -0
  21. qcanvas/ui/main_ui/course_viewer_container.py +32 -2
  22. qcanvas/ui/main_ui/qcanvas_window.py +1 -1
  23. qcanvas/ui/main_ui/status_bar_progress_display.py +17 -8
  24. qcanvas/ui/setup/setup_checker.py +2 -2
  25. qcanvas/ui/setup/setup_dialog.py +144 -65
  26. qcanvas/util/__init__.py +0 -2
  27. qcanvas/util/auto_downloader.py +1 -2
  28. qcanvas/util/file_icons.py +36 -0
  29. qcanvas/util/paths.py +15 -26
  30. qcanvas/util/runtime.py +20 -0
  31. qcanvas/util/settings/_client_settings.py +11 -2
  32. qcanvas/util/themes.py +9 -0
  33. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/METADATA +7 -3
  34. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/RECORD +36 -29
  35. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/WHEEL +0 -0
  36. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/entry_points.txt +0 -0
qcanvas/run.py CHANGED
@@ -1,5 +1,29 @@
1
+ # nuitka-project: --enable-plugin=pyside6
2
+ # nuitka-project: --windows-icon-from-ico=./deploy/windows/qcanvas.ico
3
+ # nuitka-project: --windows-console-mode=attach
4
+ # nuitka-project: --linux-icon=deploy/appimage/qcanvas.svg
5
+ # nuitka-project: --standalone
6
+ # nuitka-project: --include-module=yt_dlp
7
+
8
+ # Anti-bloat
9
+
10
+ ##########################################################
11
+ # nuitka-project: --include-package=aiosqlite
12
+ # nuitka-project: --nofollow-import-to=aiosqlite.tests.*
13
+ ##########################################################
14
+ # nuitka-project: --nofollow-import-to=rich
15
+ ##########################################################
16
+ # nuitka-project: --nofollow-import-to=sqlalchemy.dialects.mssql
17
+ # nuitka-project: --nofollow-import-to=sqlalchemy.dialects.postgresql
18
+ # nuitka-project: --nofollow-import-to=sqlalchemy.dialects.oracle
19
+ # nuitka-project: --nofollow-import-to=sqlalchemy.dialects.mysql
20
+ ##########################################################
21
+ # nuitka-project: --nofollow-import-to=rich
22
+ ##########################################################
23
+ # nuitka-project: --nofollow-import-to=yt_dlp.extractor.lazy_extractors
24
+
1
25
  import logging
2
- from logging import INFO, WARNING
26
+ from logging import DEBUG, INFO, WARNING
3
27
 
4
28
  import qcanvas.app_start
5
29
  from qcanvas.util import logs, paths
@@ -18,6 +42,7 @@ logs.set_levels(
18
42
  "qcanvas.ui": WARNING,
19
43
  "qcanvas_backend": INFO,
20
44
  "qcanvas.ui.main_ui.status_bar_progress_display": INFO,
45
+ "qcanvas.util.themes": DEBUG,
21
46
  }
22
47
  )
23
48
 
@@ -45,8 +45,8 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
45
45
  *,
46
46
  header_text: str | Sequence[str],
47
47
  indentation: int = 20,
48
- max_width: int,
49
- min_width: int,
48
+ max_width: Optional[int] = None,
49
+ min_width: Optional[int] = None,
50
50
  ) -> None:
51
51
  if not isinstance(header_text, str) and isinstance(header_text, Sequence):
52
52
  self.setHeaderLabels(header_text)
@@ -54,8 +54,25 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
54
54
  self.setHeaderLabel(header_text)
55
55
 
56
56
  self.setIndentation(indentation)
57
- self.setMaximumWidth(max_width)
58
- self.setMinimumWidth(min_width)
57
+
58
+ if max_width is not None:
59
+ self.setMaximumWidth(max_width)
60
+
61
+ if min_width is not None:
62
+ self.setMinimumWidth(min_width)
63
+
64
+ def set_columns_resize_mode(
65
+ self,
66
+ resize_mode_for_columns: list[QHeaderView.ResizeMode],
67
+ *,
68
+ stretch_last: bool = False,
69
+ ) -> None:
70
+ header = self.header()
71
+
72
+ for index, mode in enumerate(resize_mode_for_columns):
73
+ header.setSectionResizeMode(index, mode)
74
+
75
+ header.setStretchLastSection(stretch_last)
59
76
 
60
77
  def reload(self, data: T, *, sync_receipt: SyncReceipt) -> None:
61
78
  self._reloading = True
@@ -7,6 +7,7 @@ from qtpy.QtCore import Slot
7
7
  from qtpy.QtWidgets import *
8
8
 
9
9
  from qcanvas.ui.course_viewer.tabs.assignment_tab import AssignmentTab
10
+ from qcanvas.ui.course_viewer.tabs.file_tab import FileTab
10
11
  from qcanvas.ui.course_viewer.tabs.mail_tab import MailTab
11
12
  from qcanvas.ui.course_viewer.tabs.page_tab import PageTab
12
13
  from qcanvas.util.basic_fonts import bold_font
@@ -47,18 +48,17 @@ class CourseViewer(QWidget):
47
48
  downloader=downloader,
48
49
  sync_receipt=sync_receipt,
49
50
  )
50
- # self._files_tab = FileTab.create_from_receipt(
51
- # course=course,
52
- # downloader=downloader,
53
- # sync_receipt=sync_receipt,
54
- # )
51
+ self._files_tab = FileTab.create_from_receipt(
52
+ course=course,
53
+ downloader=downloader,
54
+ sync_receipt=sync_receipt,
55
+ )
55
56
 
56
57
  self._tabs = QTabWidget()
58
+ self._tabs.addTab(self._files_tab, "Files")
57
59
  self._tabs.addTab(self._pages_tab, "Pages")
58
60
  self._tabs.addTab(self._assignments_tab, "Assignments")
59
61
  self._tabs.addTab(self._mail_tab, "Mail")
60
- self._tabs.addTab(QLabel("Not implemented"), "Files")
61
- # self._tabs.addTab(self._files_tab, "Files")
62
62
  # self._tabs.addTab(QLabel("Not implemented"), "Panopto") # The meme lives on!
63
63
 
64
64
  self.setLayout(layout(QVBoxLayout, self._course_label, self._tabs))
@@ -69,10 +69,10 @@ class CourseViewer(QWidget):
69
69
  self._unhighlight_tab(0) # Because the first tab always gets auto-selected
70
70
 
71
71
  def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
72
+ self._files_tab.reload(course, sync_receipt=sync_receipt)
72
73
  self._pages_tab.reload(course, sync_receipt=sync_receipt)
73
74
  self._assignments_tab.reload(course, sync_receipt=sync_receipt)
74
75
  self._mail_tab.reload(course, sync_receipt=sync_receipt)
75
-
76
76
  self._highlight_tabs(sync_receipt)
77
77
 
78
78
  @Slot(int)
@@ -84,18 +84,17 @@ class CourseViewer(QWidget):
84
84
  updates = sync_receipt.updates_by_course.get(self._course_id, None)
85
85
 
86
86
  if updates is not None:
87
- # if len(updates.updated_resources) > 0:
88
- # raise Exception("Looks like you forgot to update the other numbers??????"")
89
- # self._highlight_tab(0)
87
+ if len(updates.updated_resources) > 0:
88
+ self._highlight_tab(0)
90
89
 
91
90
  if len(updates.updated_pages) > 0:
92
- self._highlight_tab(0)
91
+ self._highlight_tab(1)
93
92
 
94
93
  if len(updates.updated_assignments) > 0:
95
- self._highlight_tab(1)
94
+ self._highlight_tab(2)
96
95
 
97
96
  if len(updates.updated_messages) > 0:
98
- self._highlight_tab(2)
97
+ self._highlight_tab(3)
99
98
  else:
100
99
  for index in range(0, 4):
101
100
  self._unhighlight_tab(index)
@@ -18,6 +18,7 @@ class AssignmentTree(ContentTree[db.Course]):
18
18
  tree_name=f"course.{course_id}.assignment_groups",
19
19
  emit_selection_signal_for_type=db.Assignment,
20
20
  )
21
+
21
22
  self.ui_setup(
22
23
  header_text=["Assignments", "Weight"],
23
24
  indentation=15,
@@ -25,13 +26,9 @@ class AssignmentTree(ContentTree[db.Course]):
25
26
  min_width=150,
26
27
  )
27
28
 
28
- self._adjust_header()
29
-
30
- def _adjust_header(self) -> None:
31
- header = self.header()
32
- header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
33
- header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
34
- header.setStretchLastSection(False)
29
+ self.set_columns_resize_mode(
30
+ [QHeaderView.ResizeMode.Stretch, QHeaderView.ResizeMode.ResizeToContents]
31
+ )
35
32
 
36
33
  def create_tree_items(
37
34
  self, course: db.Course, sync_receipt: SyncReceipt
@@ -39,8 +36,11 @@ class AssignmentTree(ContentTree[db.Course]):
39
36
  widgets = []
40
37
 
41
38
  for assignment_group in course.assignment_groups: # type: db.AssignmentGroup
39
+ if len(assignment_group.assignments) == 0:
40
+ continue
41
+
42
42
  assignment_group_widget = self._create_assignment_group_widget(
43
- assignment_group, sync_receipt
43
+ assignment_group
44
44
  )
45
45
 
46
46
  widgets.append(assignment_group_widget)
@@ -54,7 +54,7 @@ class AssignmentTree(ContentTree[db.Course]):
54
54
  return widgets
55
55
 
56
56
  def _create_assignment_group_widget(
57
- self, assignment_group: db.AssignmentGroup, sync_receipt: SyncReceipt
57
+ self, assignment_group: db.AssignmentGroup
58
58
  ) -> MemoryTreeWidgetItem:
59
59
  assignment_group_widget = MemoryTreeWidgetItem(
60
60
  id=assignment_group.id,
@@ -64,9 +64,6 @@ class AssignmentTree(ContentTree[db.Course]):
64
64
 
65
65
  assignment_group_widget.setFlags(Qt.ItemFlag.ItemIsEnabled)
66
66
 
67
- if sync_receipt.was_updated(assignment_group):
68
- self.mark_as_unseen(assignment_group_widget)
69
-
70
67
  return assignment_group_widget
71
68
 
72
69
  def _create_assignment_widget(
@@ -0,0 +1 @@
1
+ from .file_tab import FileTab
@@ -0,0 +1,46 @@
1
+ import logging
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 *
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
@@ -0,0 +1,95 @@
1
+ import logging
2
+ from typing import *
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 *
9
+
10
+ from qcanvas.ui.course_viewer.content_tree import ContentTree
11
+ from qcanvas.ui.memory_tree import MemoryTreeWidgetItem
12
+ from qcanvas.util.file_icons import icon_for_filename
13
+ from qcanvas.util.ui_tools import create_qaction
14
+
15
+ _logger = logging.getLogger(__name__)
16
+
17
+
18
+ T = TypeVar("T")
19
+
20
+
21
+ class FileTree(ContentTree[db.Course]):
22
+ @classmethod
23
+ def create_from_receipt(
24
+ cls,
25
+ course: db.Course,
26
+ *,
27
+ sync_receipt: SyncReceipt,
28
+ resource_manager: ResourceManager,
29
+ ) -> "FileTree":
30
+ tree = cls(tree_name=course.id, resource_manager=resource_manager)
31
+ tree.reload(course, sync_receipt=sync_receipt)
32
+ return tree
33
+
34
+ def __init__(self, tree_name: str, *, resource_manager: ResourceManager):
35
+ super().__init__(tree_name, emit_selection_signal_for_type=object)
36
+ self._resource_manager = resource_manager
37
+
38
+ self.ui_setup(header_text=["File", "Date"])
39
+ self.set_columns_resize_mode(
40
+ [QHeaderView.ResizeMode.Stretch, QHeaderView.ResizeMode.ResizeToContents]
41
+ )
42
+
43
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
44
+ self.customContextMenuRequested.connect(self._context_menu)
45
+
46
+ def _create_group_widget(
47
+ self, group: db.ContentGroup, sync_receipt: SyncReceipt
48
+ ) -> MemoryTreeWidgetItem:
49
+ group_widget = MemoryTreeWidgetItem(
50
+ id=group.id, data=group, strings=[group.name]
51
+ )
52
+
53
+ group_widget.setFlags(Qt.ItemFlag.ItemIsEnabled)
54
+
55
+ if sync_receipt.was_updated(group):
56
+ self.mark_as_unseen(group_widget)
57
+
58
+ return group_widget
59
+
60
+ def _create_resource_widget(
61
+ self, resource: db.Resource, sync_receipt: SyncReceipt
62
+ ) -> QTreeWidgetItem:
63
+ # fixme the reesource widget items shouls NOT be a memory widget item because they can't be collapsed, but
64
+ # mostly because the same file can appear in the tree multiple times in different places, which memory tree
65
+ # can NOT deal with!
66
+ item_widget = QTreeWidgetItem(
67
+ [resource.file_name, str(resource.discovery_date.date())],
68
+ )
69
+ item_widget.setIcon(
70
+ 0,
71
+ icon_for_filename(resource.file_name),
72
+ )
73
+ item_widget.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
74
+
75
+ if sync_receipt.was_updated(resource):
76
+ self.mark_as_unseen(item_widget)
77
+
78
+ return item_widget
79
+
80
+ @Slot(QPoint)
81
+ def _context_menu(self, point: QPoint) -> None:
82
+ item = self.itemAt(point)
83
+
84
+ if item is None or not isinstance(item, MemoryTreeWidgetItem):
85
+ return
86
+
87
+ menu = QMenu()
88
+ create_qaction(
89
+ name="Test",
90
+ parent=menu,
91
+ triggered=lambda: print(f"Clicked {item.extra_data.file_name}"),
92
+ )
93
+ menu.addAction("Another thing")
94
+
95
+ menu.exec(self.mapToGlobal(point))
@@ -0,0 +1,50 @@
1
+ import logging
2
+ from typing import *
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
+
8
+ from qcanvas.ui.course_viewer.tabs.file_tab.file_tree import FileTree
9
+ from qcanvas.ui.memory_tree import MemoryTreeWidgetItem
10
+
11
+ _logger = logging.getLogger(__name__)
12
+
13
+
14
+ class PagesFileTree(FileTree):
15
+
16
+ def __init__(self, tree_name: str, *, resource_manager: ResourceManager):
17
+ super().__init__(
18
+ tree_name=f"{tree_name}.pages", resource_manager=resource_manager
19
+ )
20
+
21
+ def create_tree_items(
22
+ self, data: db.Course, sync_receipt: SyncReceipt
23
+ ) -> Sequence[MemoryTreeWidgetItem]:
24
+ widgets = []
25
+
26
+ for group in data.modules: # type: db.Module
27
+ if len(group.content_items) == 0:
28
+ continue
29
+
30
+ group_widget = self._create_group_widget(group, sync_receipt)
31
+ items_in_group = set()
32
+
33
+ for item in group.content_items:
34
+ for resource in item.resources: # type: db.Resource
35
+
36
+ if resource.id not in items_in_group:
37
+ items_in_group.add(resource.id)
38
+ else:
39
+ continue
40
+
41
+ resource_widget = self._create_resource_widget(
42
+ resource, sync_receipt
43
+ )
44
+
45
+ group_widget.addChild(resource_widget)
46
+
47
+ if group_widget.childCount() > 0:
48
+ widgets.append(group_widget)
49
+
50
+ return widgets
@@ -18,6 +18,7 @@ class MailTree(ContentTree[db.Course]):
18
18
  tree_name=f"course.{course_id}.mail",
19
19
  emit_selection_signal_for_type=db.CourseMessage,
20
20
  )
21
+
21
22
  self.ui_setup(
22
23
  header_text=["Subject", "Sender"],
23
24
  max_width=500,
@@ -25,13 +26,9 @@ class MailTree(ContentTree[db.Course]):
25
26
  indentation=20,
26
27
  )
27
28
 
28
- self._adjust_header()
29
-
30
- def _adjust_header(self) -> None:
31
- header = self.header()
32
- header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
33
- header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
34
- header.setStretchLastSection(False)
29
+ self.set_columns_resize_mode(
30
+ [QHeaderView.ResizeMode.Stretch, QHeaderView.ResizeMode.ResizeToContents]
31
+ )
35
32
 
36
33
  def create_tree_items(
37
34
  self, course: db.Course, sync_receipt: SyncReceipt
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Optional, Sequence
2
+ from typing import Sequence
3
3
 
4
4
  import qcanvas_backend.database.types as db
5
5
  from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
@@ -17,6 +17,7 @@ class PageTree(ContentTree[db.Course]):
17
17
  tree_name=f"course.{course_id}.modules",
18
18
  emit_selection_signal_for_type=db.ModulePage,
19
19
  )
20
+
20
21
  self.ui_setup(
21
22
  header_text="Content", indentation=15, max_width=300, min_width=150
22
23
  )
@@ -27,7 +28,10 @@ class PageTree(ContentTree[db.Course]):
27
28
  widgets = []
28
29
 
29
30
  for module in course.modules: # type: db.Module
30
- module_widget = self._create_module_widget(module, sync_receipt)
31
+ if len(module.pages) == 0:
32
+ continue
33
+
34
+ module_widget = self._create_module_widget(module)
31
35
  widgets.append(module_widget)
32
36
 
33
37
  for page in module.pages: # type: db.ModulePage
@@ -36,18 +40,12 @@ class PageTree(ContentTree[db.Course]):
36
40
 
37
41
  return widgets
38
42
 
39
- def _create_module_widget(
40
- self, module: db.Module, sync_receipt: SyncReceipt
41
- ) -> MemoryTreeWidgetItem:
43
+ def _create_module_widget(self, module: db.Module) -> MemoryTreeWidgetItem:
42
44
  module_widget = MemoryTreeWidgetItem(
43
45
  id=module.id, data=module, strings=[module.name]
44
46
  )
45
47
  module_widget.setFlags(Qt.ItemFlag.ItemIsEnabled)
46
48
 
47
- # Todo not sure if modules should get highlighted since they can't be unhighlighted by selecting them...
48
- if sync_receipt.was_updated(module):
49
- self.mark_as_unseen(module_widget)
50
-
51
49
  return module_widget
52
50
 
53
51
  def _create_page_widget(
@@ -50,12 +50,6 @@ class ResourceRichBrowser(QTextBrowser):
50
50
  self._downloader.download_finished.connect(self._download_updated)
51
51
  self._downloader.download_failed.connect(self._download_updated)
52
52
 
53
- # _dark_listener.theme_changed.connect(self._theme_changed)
54
-
55
- # @Slot()
56
- # def _theme_changed(self, theme: str) -> None:
57
- # print(theme)
58
-
59
53
  def show_blank(self, completely_blank: bool = False) -> None:
60
54
  if completely_blank:
61
55
  self.clear()
@@ -102,16 +96,31 @@ class ResourceRichBrowser(QTextBrowser):
102
96
  )
103
97
  continue
104
98
 
105
- file_link_tag = self._create_resource_link_tag(doc, resource_id)
99
+ file_link_tag = self._create_resource_link_tag(
100
+ doc, resource_id, resource_link.name == "img"
101
+ )
106
102
  resource_link.replace_with(file_link_tag)
107
103
  except NoExtractorError:
108
104
  pass
109
105
 
110
106
  return str(doc)
111
107
 
112
- def _create_resource_link_tag(self, doc: BeautifulSoup, resource_id: str) -> Tag:
108
+ def _create_resource_link_tag(
109
+ self, doc: BeautifulSoup, resource_id: str, is_image: bool
110
+ ) -> Tag:
113
111
  resource = self._current_content_resources[resource_id]
114
112
 
113
+ # todo not sure if this is a good idea or not
114
+ # if is_image and resource.download_state == db.ResourceDownloadState.DOWNLOADED:
115
+ # location = self._downloader.resource_download_location(resource)
116
+ #
117
+ # file_link_tag = doc.new_tag(
118
+ # "img",
119
+ # attrs={
120
+ # "source": location.absolute(),
121
+ # },
122
+ # )
123
+ # else:
115
124
  file_link_tag = doc.new_tag(
116
125
  "a",
117
126
  attrs={
@@ -1 +1,11 @@
1
+ from typing import Protocol
2
+
3
+ import qcanvas_backend.database.types as db
4
+ from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
5
+
1
6
  date_strftime_format = "%A, %Y-%m-%d, %H:%M:%S"
7
+
8
+
9
+ # todo what the hell is the point of this?
10
+ class SupportsReload(Protocol):
11
+ def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None: ...
@@ -1,17 +1,48 @@
1
1
  import logging
2
+ from math import floor
2
3
  from typing import *
3
4
 
4
5
  import qcanvas_backend.database.types as db
5
6
  from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
6
7
  from qcanvas_backend.net.sync.sync_receipt import SyncReceipt, empty_receipt
7
8
  from qtpy.QtCore import Qt
9
+ from qtpy.QtGui import QIcon
8
10
  from qtpy.QtWidgets import *
9
11
 
12
+ from qcanvas import icons
10
13
  from qcanvas.ui.course_viewer.course_viewer import CourseViewer
11
14
 
12
15
  _logger = logging.getLogger(__name__)
13
16
 
14
17
 
18
+ # todo needs to handle dark mode
19
+ class _PlaceholderLogo(QLabel):
20
+ """
21
+ Automatically resizing logo icon for when no course is selected
22
+ """
23
+
24
+ def __init__(self):
25
+ super().__init__()
26
+ self._icon = QIcon(icons.logo_transparent_light)
27
+ self._old_width = -1
28
+ self._old_height = -1
29
+ self.setAlignment(Qt.AlignmentFlag.AlignCenter)
30
+ self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
31
+
32
+ def resizeEvent(self, event) -> None:
33
+ # Calculate the size of the logo as half of the width/height with a max size of 1000x1000
34
+ width = min(floor(self.width() * 0.5), 500)
35
+ height = min(floor(self.height() * 0.5), 500)
36
+
37
+ if width == self._old_width or height == self._old_height:
38
+ return
39
+ else:
40
+ self._old_width = width
41
+ self._old_height = height
42
+
43
+ self.setPixmap(self._icon.pixmap(width, height))
44
+
45
+
15
46
  class CourseViewerContainer(QStackedWidget):
16
47
  def __init__(self, downloader: ResourceManager):
17
48
  super().__init__()
@@ -19,8 +50,7 @@ class CourseViewerContainer(QStackedWidget):
19
50
  self._downloader = downloader
20
51
  self._last_course_id: Optional[str] = None
21
52
  self._last_sync_receipt: SyncReceipt = empty_receipt()
22
- self._placeholder = QLabel("No Course Selected")
23
- self._placeholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
53
+ self._placeholder = _PlaceholderLogo()
24
54
  self.addWidget(self._placeholder)
25
55
 
26
56
  def show_blank(self) -> None:
@@ -23,7 +23,7 @@ from qcanvas.ui.main_ui.options.quick_sync_option import QuickSyncOption
23
23
  from qcanvas.ui.main_ui.options.sync_on_start_option import SyncOnStartOption
24
24
  from qcanvas.ui.main_ui.options.theme_selection_menu import ThemeSelectionMenu
25
25
  from qcanvas.ui.main_ui.status_bar_progress_display import StatusBarProgressDisplay
26
- from qcanvas.util import paths, settings, auto_downloader
26
+ from qcanvas.util import auto_downloader, paths, settings
27
27
  from qcanvas.util.qurl_util import file_url
28
28
  from qcanvas.util.ui_tools import create_qaction
29
29
 
@@ -77,10 +77,13 @@ class StatusBarProgressDisplay(QStatusBar):
77
77
  async with self._lock:
78
78
  if self._has_no_tasks:
79
79
  self._show_done()
80
- elif self._has_single_task:
81
- self._show_single_task_progress(list(self._tasks.items())[0])
82
80
  else:
83
- self._show_multiple_tasks_progress(list(self._tasks.values()))
81
+ tasks = list(self._tasks.items())
82
+
83
+ if self._has_single_task:
84
+ self._show_single_task_progress(tasks[0])
85
+ else:
86
+ self._show_multiple_tasks_progress(tasks)
84
87
 
85
88
  def _show_done(self) -> None:
86
89
  _logger.info("Finished tasks. Tasks: %s", self._tasks)
@@ -94,18 +97,24 @@ class StatusBarProgressDisplay(QStatusBar):
94
97
  self._show_progress(progress)
95
98
  self.showMessage(id.step_name)
96
99
 
97
- def _show_multiple_tasks_progress(self, tasks: list[_TaskProgress]) -> None:
100
+ def _show_multiple_tasks_progress(
101
+ self, tasks: list[Tuple[TaskID, _TaskProgress]]
102
+ ) -> None:
98
103
  _logger.debug("Multiple tasks %s", tasks)
99
- self.showMessage(f"{len(tasks)} tasks in progress")
104
+ self.showMessage(
105
+ f"{len(tasks)} tasks in progress - {', '.join([task[0].step_name for task in tasks])}"
106
+ )
100
107
  self._show_progress(self._calculate_progress(tasks))
101
108
 
102
- def _calculate_progress(self, tasks: list[_TaskProgress]) -> _TaskProgress:
103
- # Used to represent 0..1 progress as 0..multiplier
109
+ def _calculate_progress(
110
+ self, tasks: list[Tuple[TaskID, _TaskProgress]]
111
+ ) -> _TaskProgress:
112
+ # Task progresses are floats from 0 to 1, multiplier is used to turn them into ints
104
113
  multiplier = 1000
105
114
  current_sum = 0
106
115
  total_sum = 0
107
116
 
108
- for task in tasks:
117
+ for _, task in tasks:
109
118
  if task.total != 0:
110
119
  current_sum += (task.current / task.total) * multiplier
111
120
 
@@ -1,13 +1,13 @@
1
1
  import logging
2
2
 
3
3
  import qcanvas.util.settings as settings
4
- from qcanvas.util import is_url
4
+ from qcanvas.util.url_checker import is_url
5
5
 
6
6
  _logger = logging.getLogger(__name__)
7
7
 
8
8
 
9
9
  def needs_setup() -> bool:
10
- if not is_url(settings.client.panopto_url):
10
+ if not settings.client.panopto_disabled and not is_url(settings.client.panopto_url):
11
11
  return True
12
12
  elif not is_url(settings.client.canvas_url):
13
13
  return True