qcanvas 1.1.0__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.

qcanvas/run.py CHANGED
@@ -23,7 +23,7 @@
23
23
  # nuitka-project: --nofollow-import-to=yt_dlp.extractor.lazy_extractors
24
24
 
25
25
  import logging
26
- from logging import INFO, WARNING
26
+ from logging import DEBUG, INFO, WARNING
27
27
 
28
28
  import qcanvas.app_start
29
29
  from qcanvas.util import logs, paths
@@ -42,6 +42,7 @@ logs.set_levels(
42
42
  "qcanvas.ui": WARNING,
43
43
  "qcanvas_backend": INFO,
44
44
  "qcanvas.ui.main_ui.status_bar_progress_display": INFO,
45
+ "qcanvas.util.themes": DEBUG,
45
46
  }
46
47
  )
47
48
 
@@ -61,6 +61,19 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
61
61
  if min_width is not None:
62
62
  self.setMinimumWidth(min_width)
63
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)
76
+
64
77
  def reload(self, data: T, *, sync_receipt: SyncReceipt) -> None:
65
78
  self._reloading = True
66
79
 
@@ -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,12 +48,17 @@ class CourseViewer(QWidget):
47
48
  downloader=downloader,
48
49
  sync_receipt=sync_receipt,
49
50
  )
51
+ self._files_tab = FileTab.create_from_receipt(
52
+ course=course,
53
+ downloader=downloader,
54
+ sync_receipt=sync_receipt,
55
+ )
50
56
 
51
57
  self._tabs = QTabWidget()
58
+ self._tabs.addTab(self._files_tab, "Files")
52
59
  self._tabs.addTab(self._pages_tab, "Pages")
53
60
  self._tabs.addTab(self._assignments_tab, "Assignments")
54
61
  self._tabs.addTab(self._mail_tab, "Mail")
55
- self._tabs.addTab(QLabel("Not implemented"), "Files")
56
62
  # self._tabs.addTab(QLabel("Not implemented"), "Panopto") # The meme lives on!
57
63
 
58
64
  self.setLayout(layout(QVBoxLayout, self._course_label, self._tabs))
@@ -63,10 +69,10 @@ class CourseViewer(QWidget):
63
69
  self._unhighlight_tab(0) # Because the first tab always gets auto-selected
64
70
 
65
71
  def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
72
+ self._files_tab.reload(course, sync_receipt=sync_receipt)
66
73
  self._pages_tab.reload(course, sync_receipt=sync_receipt)
67
74
  self._assignments_tab.reload(course, sync_receipt=sync_receipt)
68
75
  self._mail_tab.reload(course, sync_receipt=sync_receipt)
69
-
70
76
  self._highlight_tabs(sync_receipt)
71
77
 
72
78
  @Slot(int)
@@ -78,14 +84,17 @@ class CourseViewer(QWidget):
78
84
  updates = sync_receipt.updates_by_course.get(self._course_id, None)
79
85
 
80
86
  if updates is not None:
81
- if len(updates.updated_pages) > 0:
87
+ if len(updates.updated_resources) > 0:
82
88
  self._highlight_tab(0)
83
89
 
84
- if len(updates.updated_assignments) > 0:
90
+ if len(updates.updated_pages) > 0:
85
91
  self._highlight_tab(1)
86
92
 
87
- if len(updates.updated_messages) > 0:
93
+ if len(updates.updated_assignments) > 0:
88
94
  self._highlight_tab(2)
95
+
96
+ if len(updates.updated_messages) > 0:
97
+ self._highlight_tab(3)
89
98
  else:
90
99
  for index in range(0, 4):
91
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(
@@ -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:
@@ -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
 
@@ -0,0 +1,36 @@
1
+ import logging
2
+
3
+ from qtpy.QtCore import QFileInfo, QMimeDatabase
4
+ from qtpy.QtGui import QIcon
5
+ from qtpy.QtWidgets import QApplication, QFileIconProvider, QStyle
6
+
7
+ import qcanvas.util.runtime as runtime
8
+
9
+ _logger = logging.getLogger(__name__)
10
+
11
+ # Windows and linux have different ways of doing this
12
+ if runtime.is_running_on_windows:
13
+ _icon_provider = QFileIconProvider()
14
+
15
+ def icon_for_filename(file_name: str) -> QIcon:
16
+ return _icon_provider.icon(QFileInfo(file_name))
17
+
18
+ else:
19
+ _mime_database = QMimeDatabase()
20
+ _default_icon = None
21
+
22
+ def icon_for_filename(file_name: str) -> QIcon:
23
+ global _default_icon
24
+
25
+ for mime_type in _mime_database.mimeTypesForFileName(file_name):
26
+ icon = QIcon.fromTheme(mime_type.iconName())
27
+
28
+ if not icon.isNull():
29
+ return icon
30
+
31
+ if _default_icon is None:
32
+ _default_icon = QApplication.style().standardIcon(
33
+ QStyle.StandardPixmap.SP_FileIcon
34
+ )
35
+
36
+ return _default_icon
qcanvas/util/themes.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import logging
2
2
 
3
+ import darkdetect
3
4
  import qdarktheme
5
+ from qtpy.QtGui import QGuiApplication
4
6
  from qtpy.QtWidgets import QApplication, QStyleFactory
5
7
 
6
8
  _logger = logging.getLogger(__name__)
@@ -19,9 +21,13 @@ def apply(theme: str) -> None:
19
21
  theme = ensure_theme_is_valid(theme)
20
22
 
21
23
  if theme != "native":
22
- QApplication.setStyle(QStyleFactory.create("Fusion"))
23
24
 
24
25
  qdarktheme.setup_theme(
25
26
  theme,
26
27
  custom_colors={"primary": "e02424"},
27
28
  )
29
+
30
+ QApplication.setStyle(QStyleFactory.create("Fusion"))
31
+
32
+ _logger.debug("darkdetect says: %s", darkdetect.theme())
33
+ _logger.debug("qt says: %s", QGuiApplication.styleHints().colorScheme())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qcanvas
3
- Version: 1.1.0
3
+ Version: 1.2.0a0
4
4
  Summary: QCanvas is a desktop client for Canvas LMS.
5
5
  Author: QCanvas
6
6
  Author-email: QCanvas@noreply.codeberg.org
@@ -15,7 +15,7 @@ Requires-Dist: platformdirs (>=4.2.2,<5.0.0)
15
15
  Requires-Dist: pyqtdarktheme-fork (>=2.3.2,<3.0.0)
16
16
  Requires-Dist: qasync (>=0.27.1,<0.28.0)
17
17
  Requires-Dist: qcanvas-api-clients (>=0.3.0,<0.4.0)
18
- Requires-Dist: qcanvas-backend (>=0.2.0,<0.3.0)
18
+ Requires-Dist: qcanvas-backend (>=0.2.2,<0.3.0)
19
19
  Requires-Dist: qtpy (>=2.4.1,<3.0.0)
20
20
  Requires-Dist: sqlalchemy (>=2.0.31,<3.0.0)
21
21
  Requires-Dist: validators (>=0.33.0,<0.34.0)
@@ -25,6 +25,10 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  QCanvas is an **unofficial** desktop client for Canvas LMS.
27
27
 
28
+ https://codeberg.org/QCanvas/QCanvas
29
+
30
+ https://github.com/QCanvas/QCanvasApp
31
+
28
32
  # Downloads
29
33
 
30
34
  Download it from [releases](https://github.com/QCanvas/QCanvasApp/releases)