qcanvas 0.0.5.7a0__py3-none-any.whl → 1.0.3.post0__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 (114) hide show
  1. qcanvas/app_start/__init__.py +47 -0
  2. qcanvas/backend_connectors/__init__.py +2 -0
  3. qcanvas/backend_connectors/frontend_resource_manager.py +63 -0
  4. qcanvas/backend_connectors/qcanvas_task_master.py +28 -0
  5. qcanvas/icons/__init__.py +6 -0
  6. qcanvas/icons/file-download-failed.svg +6 -0
  7. qcanvas/icons/file-downloaded.svg +6 -0
  8. qcanvas/icons/file-not-downloaded.svg +6 -0
  9. qcanvas/icons/file-unknown.svg +6 -0
  10. qcanvas/icons/icons.qrc +4 -0
  11. qcanvas/icons/main_icon.svg +7 -7
  12. qcanvas/icons/rc_icons.py +580 -214
  13. qcanvas/icons/sync.svg +6 -6
  14. qcanvas/run.py +29 -0
  15. qcanvas/ui/course_viewer/__init__.py +2 -0
  16. qcanvas/ui/course_viewer/content_tree.py +123 -0
  17. qcanvas/ui/course_viewer/course_tree.py +93 -0
  18. qcanvas/ui/course_viewer/course_viewer.py +62 -0
  19. qcanvas/ui/course_viewer/tabs/__init__.py +3 -0
  20. qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py +1 -0
  21. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +168 -0
  22. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +104 -0
  23. qcanvas/ui/course_viewer/tabs/content_tab.py +96 -0
  24. qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py +1 -0
  25. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +68 -0
  26. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +70 -0
  27. qcanvas/ui/course_viewer/tabs/page_tab/__init__.py +1 -0
  28. qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +36 -0
  29. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +74 -0
  30. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +176 -0
  31. qcanvas/ui/course_viewer/tabs/util.py +1 -0
  32. qcanvas/ui/main_ui/course_viewer_container.py +52 -0
  33. qcanvas/ui/main_ui/options/__init__.py +3 -0
  34. qcanvas/ui/main_ui/options/quick_sync_option.py +25 -0
  35. qcanvas/ui/main_ui/options/sync_on_start_option.py +25 -0
  36. qcanvas/ui/main_ui/qcanvas_window.py +192 -0
  37. qcanvas/ui/main_ui/status_bar_progress_display.py +153 -0
  38. qcanvas/ui/memory_tree/__init__.py +2 -0
  39. qcanvas/ui/memory_tree/_tree_memory.py +66 -0
  40. qcanvas/ui/memory_tree/memory_tree_widget.py +133 -0
  41. qcanvas/ui/memory_tree/memory_tree_widget_item.py +19 -0
  42. qcanvas/ui/setup/__init__.py +2 -0
  43. qcanvas/ui/setup/setup_checker.py +17 -0
  44. qcanvas/ui/setup/setup_dialog.py +212 -0
  45. qcanvas/util/__init__.py +2 -0
  46. qcanvas/util/basic_fonts.py +12 -0
  47. qcanvas/util/fe_resource_manager.py +23 -0
  48. qcanvas/util/html_cleaner.py +25 -0
  49. qcanvas/util/layouts.py +52 -0
  50. qcanvas/util/logs.py +6 -0
  51. qcanvas/util/paths.py +41 -0
  52. qcanvas/util/settings/__init__.py +9 -0
  53. qcanvas/util/settings/_client_settings.py +29 -0
  54. qcanvas/util/settings/_mapped_setting.py +63 -0
  55. qcanvas/util/settings/_ui_settings.py +34 -0
  56. qcanvas/util/ui_tools.py +41 -0
  57. qcanvas/util/url_checker.py +13 -0
  58. qcanvas-1.0.3.post0.dist-info/METADATA +61 -0
  59. qcanvas-1.0.3.post0.dist-info/RECORD +64 -0
  60. {qcanvas-0.0.5.7a0.dist-info → qcanvas-1.0.3.post0.dist-info}/WHEEL +1 -1
  61. qcanvas-1.0.3.post0.dist-info/entry_points.txt +3 -0
  62. qcanvas/__main__.py +0 -155
  63. qcanvas/db/__init__.py +0 -5
  64. qcanvas/db/database.py +0 -338
  65. qcanvas/db/db_converter_helper.py +0 -81
  66. qcanvas/net/canvas/__init__.py +0 -2
  67. qcanvas/net/canvas/canvas_client.py +0 -209
  68. qcanvas/net/canvas/legacy_canvas_types.py +0 -124
  69. qcanvas/net/custom_httpx_async_transport.py +0 -34
  70. qcanvas/net/self_authenticating.py +0 -108
  71. qcanvas/queries/__init__.py +0 -4
  72. qcanvas/queries/all_courses.gql +0 -7
  73. qcanvas/queries/all_courses.py +0 -108
  74. qcanvas/queries/canvas_course_data.gql +0 -51
  75. qcanvas/queries/canvas_course_data.py +0 -143
  76. qcanvas/ui/container_item.py +0 -11
  77. qcanvas/ui/main_ui.py +0 -251
  78. qcanvas/ui/menu_bar/__init__.py +0 -0
  79. qcanvas/ui/menu_bar/grouping_preferences_menu.py +0 -61
  80. qcanvas/ui/menu_bar/theme_selection_menu.py +0 -39
  81. qcanvas/ui/setup_dialog.py +0 -190
  82. qcanvas/ui/status_bar_reporter.py +0 -40
  83. qcanvas/ui/viewer/__init__.py +0 -0
  84. qcanvas/ui/viewer/course_list.py +0 -96
  85. qcanvas/ui/viewer/file_list.py +0 -195
  86. qcanvas/ui/viewer/file_view_tab.py +0 -62
  87. qcanvas/ui/viewer/page_list_viewer.py +0 -150
  88. qcanvas/util/app_settings.py +0 -98
  89. qcanvas/util/constants.py +0 -5
  90. qcanvas/util/course_indexer/__init__.py +0 -1
  91. qcanvas/util/course_indexer/conversion_helpers.py +0 -78
  92. qcanvas/util/course_indexer/data_manager.py +0 -447
  93. qcanvas/util/course_indexer/resource_helpers.py +0 -191
  94. qcanvas/util/download_pool.py +0 -58
  95. qcanvas/util/helpers/__init__.py +0 -0
  96. qcanvas/util/helpers/canvas_sanitiser.py +0 -47
  97. qcanvas/util/helpers/file_icon_helper.py +0 -34
  98. qcanvas/util/helpers/qaction_helper.py +0 -25
  99. qcanvas/util/helpers/theme_helper.py +0 -48
  100. qcanvas/util/link_scanner/__init__.py +0 -2
  101. qcanvas/util/link_scanner/canvas_link_scanner.py +0 -41
  102. qcanvas/util/link_scanner/canvas_media_object_scanner.py +0 -60
  103. qcanvas/util/link_scanner/dropbox_scanner.py +0 -68
  104. qcanvas/util/link_scanner/resource_scanner.py +0 -69
  105. qcanvas/util/progress_reporter.py +0 -101
  106. qcanvas/util/self_updater.py +0 -55
  107. qcanvas/util/task_pool.py +0 -253
  108. qcanvas/util/tree_util/__init__.py +0 -3
  109. qcanvas/util/tree_util/expanding_tree.py +0 -165
  110. qcanvas/util/tree_util/model_helpers.py +0 -36
  111. qcanvas/util/tree_util/tree_model.py +0 -85
  112. qcanvas-0.0.5.7a0.dist-info/METADATA +0 -21
  113. qcanvas-0.0.5.7a0.dist-info/RECORD +0 -62
  114. /qcanvas/{net → ui/main_ui}/__init__.py +0 -0
@@ -1,143 +0,0 @@
1
- """
2
- Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
3
- """
4
- from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
- from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
- from enum import Enum # noqa: F401 # pylint: disable=W0611
7
- from typing import ( # noqa: F401 # pylint: disable=W0611
8
- Any,
9
- Optional,
10
- Union,
11
- )
12
-
13
- from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
- BaseModel,
15
- Extra,
16
- Field,
17
- Json,
18
- )
19
-
20
- DEFINITION = """
21
- fragment CanvasCourseData on Course {
22
- _id
23
- name
24
- courseNickname
25
- term {
26
- endAt
27
- startAt
28
- name
29
- id
30
- }
31
- assignmentsConnection @include(if: $detailed) {
32
- nodes {
33
- description
34
- courseId
35
- dueAt
36
- createdAt
37
- id
38
- name
39
- position
40
- updatedAt
41
- }
42
- }
43
- modulesConnection @include(if: $detailed) {
44
- nodes {
45
- name
46
- id
47
- moduleItems {
48
- content {
49
- ... on File {
50
- _id
51
- displayName
52
- createdAt
53
- updatedAt
54
- url
55
- size
56
- mimeClass
57
- contentType
58
- }
59
- ... on Page {
60
- _id
61
- title
62
- updatedAt
63
- createdAt
64
- }
65
- }
66
- }
67
- }
68
- }
69
- }
70
- """
71
-
72
-
73
- class ConfiguredBaseModel(BaseModel):
74
- class Config:
75
- smart_union = True
76
- extra = Extra.forbid
77
-
78
-
79
- class Term(ConfiguredBaseModel):
80
- end_at: Optional[datetime] = Field(..., alias="endAt")
81
- start_at: Optional[datetime] = Field(..., alias="startAt")
82
- name: Optional[str] = Field(..., alias="name")
83
- q_id: str = Field(..., alias="id")
84
-
85
-
86
- class Assignment(ConfiguredBaseModel):
87
- description: Optional[str] = Field(..., alias="description")
88
- course_id: Optional[str] = Field(..., alias="courseId")
89
- due_at: Optional[datetime] = Field(..., alias="dueAt")
90
- created_at: Optional[datetime] = Field(..., alias="createdAt")
91
- q_id: str = Field(..., alias="id")
92
- name: Optional[str] = Field(..., alias="name")
93
- position: Optional[int] = Field(..., alias="position")
94
- updated_at: Optional[datetime] = Field(..., alias="updatedAt")
95
-
96
-
97
- class AssignmentConnection(ConfiguredBaseModel):
98
- nodes: Optional[list[Optional[Assignment]]] = Field(..., alias="nodes")
99
-
100
-
101
- class ModuleItemInterface(ConfiguredBaseModel):
102
- ...
103
-
104
-
105
- class File(ModuleItemInterface):
106
- m_id: str = Field(..., alias="_id")
107
- display_name: Optional[str] = Field(..., alias="displayName")
108
- created_at: Optional[datetime] = Field(..., alias="createdAt")
109
- updated_at: Optional[datetime] = Field(..., alias="updatedAt")
110
- url: Optional[str] = Field(..., alias="url")
111
- size: Optional[str] = Field(..., alias="size")
112
- mime_class: Optional[str] = Field(..., alias="mimeClass")
113
- content_type: Optional[str] = Field(..., alias="contentType")
114
-
115
-
116
- class Page(ModuleItemInterface):
117
- m_id: str = Field(..., alias="_id")
118
- title: Optional[str] = Field(..., alias="title")
119
- updated_at: Optional[datetime] = Field(..., alias="updatedAt")
120
- created_at: Optional[datetime] = Field(..., alias="createdAt")
121
-
122
-
123
- class ModuleItem(ConfiguredBaseModel):
124
- content: Optional[Union[File, Page, ModuleItemInterface]] = Field(..., alias="content")
125
-
126
-
127
- class Module(ConfiguredBaseModel):
128
- name: Optional[str] = Field(..., alias="name")
129
- q_id: str = Field(..., alias="id")
130
- module_items: Optional[list[ModuleItem]] = Field(..., alias="moduleItems")
131
-
132
-
133
- class ModuleConnection(ConfiguredBaseModel):
134
- nodes: Optional[list[Optional[Module]]] = Field(..., alias="nodes")
135
-
136
-
137
- class CanvasCourseData(ConfiguredBaseModel):
138
- m_id: str = Field(..., alias="_id")
139
- name: str = Field(..., alias="name")
140
- course_nickname: Optional[str] = Field(..., alias="courseNickname")
141
- term: Optional[Term] = Field(..., alias="term")
142
- assignments_connection: Optional[AssignmentConnection] = Field(None, alias="assignmentsConnection")
143
- modules_connection: Optional[ModuleConnection] = Field(None, alias="modulesConnection")
@@ -1,11 +0,0 @@
1
- from PySide6.QtGui import QStandardItem
2
-
3
- from qcanvas.util import tree_util as tree
4
-
5
-
6
- class ContainerItem(QStandardItem):
7
- def __init__(self, data: tree.HasText):
8
- super().__init__()
9
- self.content = data
10
- self.setEditable(False)
11
- self.setText(data.text)
qcanvas/ui/main_ui.py DELETED
@@ -1,251 +0,0 @@
1
- import logging
2
- import sys
3
- import traceback
4
- from typing import Sequence, Optional
5
-
6
- from PySide6.QtCore import Slot, Signal, Qt, QUrl, QObject
7
- from PySide6.QtGui import QDesktopServices, QKeySequence
8
- from PySide6.QtWidgets import *
9
- from qasync import asyncSlot
10
-
11
- import qcanvas.db.database as db
12
- from qcanvas.ui.menu_bar.grouping_preferences_menu import GroupingPreferencesMenu
13
- from qcanvas.ui.menu_bar.theme_selection_menu import ThemeSelectionMenu
14
- from qcanvas.ui.status_bar_reporter import StatusBarReporter
15
- from qcanvas.ui.viewer.course_list import CourseList
16
- from qcanvas.ui.viewer.file_list import FileRow
17
- from qcanvas.ui.viewer.file_view_tab import FileViewTab
18
- from qcanvas.ui.viewer.page_list_viewer import AssignmentsViewer, PagesViewer, LinkTransformer
19
- from qcanvas.util import self_updater
20
- from qcanvas.util.app_settings import settings
21
- from qcanvas.util.constants import app_name
22
- from qcanvas.util.course_indexer import DataManager
23
- from qcanvas.util.helpers.qaction_helper import create_qaction
24
-
25
- _aux_settings = settings.auxiliary
26
- _no_course_selected_text = "No course selected"
27
-
28
- class AppMainWindow(QMainWindow):
29
- logger = logging.getLogger()
30
- loaded = Signal()
31
- files_grouping_preference_changed = Signal(db.GroupByPreference)
32
-
33
- def __init__(self, data_manager: DataManager, parent: QWidget | None = None):
34
- super().__init__(parent)
35
-
36
- self.selected_course: db.Course | None = None
37
- self.courses: Sequence[db.Course] = []
38
- self.resources: dict[str, db.Resource] = {}
39
- self.data_manager = data_manager
40
- self.link_transformer = LinkTransformer(self.data_manager.link_scanners, self.resources)
41
-
42
- right_splitter = QSplitter()
43
- right_splitter.setOrientation(Qt.Orientation.Vertical)
44
-
45
- self.sync_button = QPushButton("Synchronize")
46
- self.sync_button.clicked.connect(self.sync_data)
47
-
48
- self.course_list = CourseList(self.data_manager)
49
- self.course_list.course_selected.connect(self.on_course_selected)
50
-
51
- self.assignment_viewer = AssignmentsViewer(self.link_transformer)
52
- self.assignment_viewer.viewer.anchorClicked.connect(self.viewer_link_clicked)
53
-
54
- self.pages_viewer = PagesViewer(self.link_transformer)
55
- self.pages_viewer.viewer.anchorClicked.connect(self.viewer_link_clicked)
56
-
57
- self.file_viewer = FileViewTab(data_manager.download_pool)
58
- self.file_viewer.files_column.tree.itemActivated.connect(self.download_file_from_file_pane)
59
- self.file_viewer.assignment_files_column.tree.itemActivated.connect(self.download_file_from_file_pane)
60
-
61
- self.tab_widget = QTabWidget()
62
- self.tab_widget.insertTab(0, self.file_viewer, "Files")
63
- self.tab_widget.insertTab(1, self.assignment_viewer, "Assignments")
64
- self.tab_widget.insertTab(2, self.pages_viewer, "Pages")
65
-
66
- self.course_name_label = QLabel(_no_course_selected_text)
67
- self.course_name_label.setStyleSheet("font-weight: bold;")
68
- course_viewer_layout = self.create_layout_and_add_widgets(QVBoxLayout, self.course_name_label, self.tab_widget)
69
-
70
- v_layout = self.create_layout_and_add_widgets(QVBoxLayout, self.course_list, self.sync_button)
71
-
72
- widget = QWidget()
73
- h_layout = self.create_layout_and_add_widgets(QHBoxLayout, v_layout, course_viewer_layout)
74
- h_layout.setStretch(1, 1)
75
- widget.setLayout(h_layout)
76
-
77
- self.setCentralWidget(widget)
78
-
79
- self.setup_menu_bar()
80
-
81
- self.files_grouping_preference_changed.connect(self.on_grouping_preference_changed)
82
-
83
- self.loaded.connect(self.load_course_list)
84
- self.loaded.connect(self.check_for_update)
85
- self.loaded.emit()
86
-
87
- self.restore_window_position()
88
-
89
- # Activate the statusbar so it doesn't just appear randomly later
90
- bar: QStatusBar = self.statusBar()
91
- # Set its height so it doesn't get bigger when there's a progress bar in it
92
- bar.setFixedHeight(bar.height())
93
-
94
- @staticmethod
95
- def create_layout_and_add_widgets(layout_type: type, *widgets) -> QLayout:
96
- layout = layout_type()
97
-
98
- for widget in widgets:
99
- if isinstance(widget, QLayout):
100
- layout.addLayout(widget)
101
- else:
102
- layout.addWidget(widget)
103
-
104
- return layout
105
-
106
- def setup_menu_bar(self):
107
- menu_bar = self.menuBar()
108
-
109
- app_menu: QMenu = menu_bar.addMenu("App")
110
- view_menu: QMenu = menu_bar.addMenu("View")
111
-
112
- app_menu.addAction(self.setup_quick_authentication_action(app_menu))
113
- app_menu.addMenu(ThemeSelectionMenu())
114
- view_menu.addMenu(self.setup_group_by_menu())
115
-
116
- def setup_group_by_menu(self) -> QMenu:
117
- file_grouping_menu = GroupingPreferencesMenu(self.data_manager)
118
- self.course_list.course_selected.connect(file_grouping_menu.course_changed)
119
- file_grouping_menu.preference_changed.connect(self.on_grouping_preference_changed)
120
-
121
- return file_grouping_menu
122
-
123
- def setup_quick_authentication_action(self, parent: QObject):
124
- return create_qaction(
125
- name="Quick canvas login",
126
- shortcut=QKeySequence("Ctrl+O"),
127
- triggered=self.open_quick_auth_in_browser,
128
- parent=parent
129
- )
130
-
131
- @asyncSlot()
132
- async def open_quick_auth_in_browser(self):
133
- opening_progress_dialog = QProgressDialog("Opening canvas", None, 0, 0, self)
134
- opening_progress_dialog.setWindowTitle("Please wait")
135
- opening_progress_dialog.show()
136
- QDesktopServices.openUrl(await self.data_manager.client.get_temp_session_link())
137
- opening_progress_dialog.close()
138
-
139
- def closeEvent(self, event):
140
- settings.geometry = self.saveGeometry()
141
- settings.window_state = self.saveState()
142
-
143
- def restore_window_position(self):
144
- self.restoreGeometry(settings.geometry)
145
- self.restoreState(settings.window_state)
146
-
147
- @asyncSlot(QUrl)
148
- async def viewer_link_clicked(self, url: QUrl):
149
- # The url of a transformed link will start with a specific prefix
150
- if url.toString().startswith(LinkTransformer.transformed_url_prefix):
151
- # The rest of the 'url' is just the file id
152
- resource = self.resources[url.toString().removeprefix(LinkTransformer.transformed_url_prefix)]
153
-
154
- await self.data_manager.download_resource(resource)
155
- QDesktopServices.openUrl(QUrl.fromLocalFile(resource.download_location.absolute()))
156
- else:
157
- QDesktopServices.openUrl(url)
158
-
159
- @asyncSlot(QTreeWidgetItem, int)
160
- async def download_file_from_file_pane(self, item: QTreeWidgetItem, _: int):
161
- if isinstance(item, FileRow):
162
- await self.data_manager.download_resource(item.resource)
163
- QDesktopServices.openUrl(QUrl.fromLocalFile(item.resource.download_location.absolute()))
164
-
165
- @asyncSlot()
166
- async def sync_data(self):
167
- self.sync_button.setEnabled(False)
168
- self.sync_button.setText("Synchronizing")
169
- try:
170
- await self.data_manager.synchronize_with_canvas(StatusBarReporter(self.statusBar()))
171
- await self.load_course_list()
172
-
173
- finally:
174
- self.sync_button.setEnabled(True)
175
- self.sync_button.setText("Synchronize")
176
-
177
- @asyncSlot()
178
- async def load_course_list(self):
179
- self.courses = (await self.data_manager.get_data())
180
- self.selected_course = None
181
- self.resources.clear()
182
-
183
- for course in self.courses:
184
- self.resources.update({resource.id: resource for resource in course.resources})
185
-
186
- self.course_list.load_course_list(self.courses)
187
-
188
- @asyncSlot()
189
- async def check_for_update(self):
190
- try:
191
- newer_version, installed_version = await self_updater.get_newer_version()
192
-
193
- if newer_version is not None and newer_version != settings.ignored_update:
194
- msg_box = QMessageBox(
195
- QMessageBox.Icon.Question,
196
- "Update available",
197
- f"There is an update available ({installed_version} -> {newer_version})\nDo you want to update?\nThe program will close after the update is finished.",
198
- QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
199
- self
200
- )
201
-
202
- def ignore_update():
203
- settings.ignored_update = newer_version
204
-
205
- msg_box.accepted.connect(self.do_self_update)
206
- msg_box.rejected.connect(ignore_update)
207
- msg_box.show()
208
- else:
209
- print("No update available (or skipping this update)")
210
- except BaseException as e:
211
- sys.stderr.write(f"Could not check for updates: {e}\n")
212
- traceback.print_exc()
213
- sys.stderr.write("This can be ignored if in a dev environment\n")
214
-
215
- @asyncSlot()
216
- async def do_self_update(self):
217
- try:
218
- progress_diag = QProgressDialog("Updating", None, 0, 0, self)
219
- progress_diag.setWindowTitle(app_name)
220
- progress_diag.show()
221
- await self_updater.do_update()
222
- self.close()
223
- except BaseException as e:
224
- traceback.print_exc()
225
-
226
- QMessageBox(
227
- QMessageBox.Icon.Critical,
228
- "Error",
229
- "An error occurred during the update",
230
- parent=self
231
- ).show()
232
-
233
- @Slot(db.Course)
234
- def on_course_selected(self, course: Optional[db.Course]):
235
- if course is not None:
236
- self.selected_course = course
237
- # todo these should really be slots connected to this signal...
238
- self.pages_viewer.fill_tree(course)
239
- self.assignment_viewer.fill_tree(course)
240
- self.file_viewer.load_course_files(course)
241
- self.course_name_label.setText(course.name)
242
- else:
243
- self.selected_course = None
244
- self.file_viewer.clear()
245
- self.pages_viewer.clear()
246
- self.assignment_viewer.clear()
247
- self.course_name_label.setText(_no_course_selected_text)
248
-
249
- @asyncSlot(db.CoursePreferences)
250
- async def on_grouping_preference_changed(self):
251
- self.file_viewer.load_course_files(self.selected_course)
File without changes
@@ -1,61 +0,0 @@
1
- from PySide6.QtCore import Slot, Signal
2
- from PySide6.QtWidgets import QMenu, QWidget
3
- from qasync import asyncSlot
4
-
5
- import qcanvas.db as db
6
- from qcanvas.util.course_indexer import DataManager
7
- from qcanvas.util.helpers.qaction_helper import create_qaction
8
-
9
-
10
- class GroupingPreferencesMenu(QMenu):
11
- _preference_changed_private = Signal(db.GroupByPreference)
12
- preference_changed = Signal()
13
-
14
- def __init__(self, data_manager: DataManager, parent: QWidget | None = None):
15
- super().__init__("Group files by", parent)
16
- self.setEnabled(False)
17
-
18
- self.data_manager = data_manager
19
- self._selected_course: db.Course | None = None
20
-
21
- self.group_by_modules_action = self._make_action("Modules", db.GroupByPreference.GROUP_BY_MODULES)
22
- self.group_by_pages_action = self._make_action("Pages", db.GroupByPreference.GROUP_BY_PAGES)
23
-
24
- self.addActions([self.group_by_pages_action, self.group_by_modules_action])
25
-
26
- self._preference_changed_private.connect(self._on_preference_changed)
27
-
28
- def _make_action(self, text: str, preference_value: db.GroupByPreference):
29
- return create_qaction(
30
- name=text,
31
- parent=self,
32
- triggered=lambda: self._preference_changed_private.emit(preference_value),
33
- checkable=True,
34
- checked=False
35
- )
36
-
37
- @Slot()
38
- def course_changed(self, course: db.Course | None):
39
- if course is not None:
40
- self._selected_course = course
41
- self._update_actions()
42
- self.setEnabled(True)
43
- else:
44
- self._selected_course = None
45
- self.setEnabled(False)
46
-
47
- @asyncSlot()
48
- async def _on_preference_changed(self, preference: db.GroupByPreference):
49
- if self._selected_course is not None:
50
- self._selected_course.preferences.files_group_by_preference = preference
51
- self._update_actions()
52
- # todo not sure if this should go in main_ui or here...
53
- await self.data_manager.update_item(self._selected_course.preferences)
54
-
55
- self.preference_changed.emit()
56
-
57
- def _update_actions(self):
58
- preference = self._selected_course.preferences.files_group_by_preference
59
-
60
- self.group_by_pages_action.setChecked(preference == db.GroupByPreference.GROUP_BY_PAGES)
61
- self.group_by_modules_action.setChecked(preference == db.GroupByPreference.GROUP_BY_MODULES)
@@ -1,39 +0,0 @@
1
- from PySide6.QtGui import QActionGroup
2
- from PySide6.QtWidgets import QMenu, QWidget
3
-
4
- from qcanvas.util.app_settings import settings
5
- from qcanvas.util.helpers import theme_helper
6
- from qcanvas.util.helpers.qaction_helper import create_qaction
7
-
8
-
9
- def change_theme(theme_name: str):
10
- settings.theme = theme_name
11
- theme_helper.apply_selected_theme()
12
-
13
-
14
- class ThemeSelectionMenu(QMenu):
15
- def __init__(self, parent: QWidget | None = None):
16
- super().__init__("Theme", parent)
17
-
18
- action_group = QActionGroup(self)
19
-
20
- light_theme = self._create_action("Light", "light")
21
- dark_theme = self._create_action("Dark", "dark")
22
- auto_theme = self._create_action("Auto (YMMV)", "auto")
23
- native_theme = self._create_action("Native (requires restart)", "native")
24
-
25
- actions = [light_theme, dark_theme, auto_theme, native_theme]
26
-
27
- self.addActions(actions)
28
-
29
- for theme in actions:
30
- action_group.addAction(theme)
31
-
32
- def _create_action(self, text: str, theme_name: str):
33
- return create_qaction(
34
- name=text,
35
- parent=self,
36
- triggered=lambda: change_theme(theme_name),
37
- checkable=True,
38
- checked=settings.theme == theme_name
39
- )