qcanvas 0.0.5.7a0__py3-none-any.whl → 1.0.3.post1__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.post1.dist-info/METADATA +59 -0
  59. qcanvas-1.0.3.post1.dist-info/RECORD +64 -0
  60. {qcanvas-0.0.5.7a0.dist-info → qcanvas-1.0.3.post1.dist-info}/WHEEL +1 -1
  61. qcanvas-1.0.3.post1.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
qcanvas/util/task_pool.py DELETED
@@ -1,253 +0,0 @@
1
- import asyncio
2
- import logging
3
- from typing import TypeVar, Generic, Callable
4
-
5
- T = TypeVar("T")
6
-
7
-
8
- class TaskPool(Generic[T]):
9
- """
10
- A TaskPool is a utility that receives submitted tasks which have an identity and which should be executed no more
11
- than once. It can be configured to wait for the task to finish when started, wait for the task to finish when it
12
- is in progress, and record the result of the task when it finishes and return it when a task with the same ID is
13
- submitted again.
14
- """
15
-
16
- _logger = logging.getLogger(__name__)
17
-
18
- def __init__(self, remember_result: bool = True, wait_if_in_progress: bool = True,
19
- wait_if_just_started: bool = True, restart_if_finished: bool = False):
20
- """
21
- Parameters
22
- ----------
23
- remember_result : bool
24
- Whether to store the result of submitted tasks
25
-
26
- wait_if_just_started : bool
27
- Whether to await a newly started task or return immediately
28
-
29
- wait_if_in_progress : bool
30
- Whether to await a task that is in progress or return immediately
31
- """
32
-
33
- if not wait_if_in_progress and remember_result:
34
- raise ValueError("Can't remember result without waiting")
35
-
36
- self._results: dict[object, asyncio.Event | T | None] = {}
37
- self._semaphore = asyncio.Semaphore()
38
- self._remember_result: bool = remember_result
39
- self._wait_if_in_progress: bool = wait_if_in_progress
40
- self._wait_if_just_started: bool = wait_if_just_started
41
- self._restart_if_finished: bool = restart_if_finished
42
-
43
- def add_values(self, results: dict[object, T]) -> None:
44
- """
45
- Adds the specified values from the dictionary as stored results.
46
-
47
- Parameters
48
- ----------
49
- results
50
- The results to store, where the key is the ID and the value is the result
51
-
52
- Returns
53
- -------
54
- None
55
- """
56
- self._results.update(**results)
57
-
58
- async def submit(self, task_id: object, func, **kwargs) -> T | None:
59
- """
60
- Submits a task and executes it. Depending on configuration, waits for it and returns the result of it.
61
-
62
- Parameters
63
- ----------
64
- task_id: object
65
- The identity of the task. E.g. "task_get_file_1" or "task_get_file_2"
66
-
67
- func : lambda
68
- The function to execute.
69
- Should be provided in the form of a lambda which executes some async function without using the await keyword.
70
- E.g::
71
- # Right
72
- taskpool.submit(..., func=lambda: some_async_function(some_val))
73
- # Wrong
74
- taskpool.submit(..., func=lambda: async some_async_function(some_val))
75
- # ^^^^^
76
- kwargs
77
- Extra arguments for the lambda
78
- Returns
79
- -------
80
- T | None
81
- None, if the configuration states that we should not wait for the function to finish or not to remember the result.
82
- Returns the result of the function otherwise.
83
- """
84
- sem = self._semaphore
85
- await sem.acquire()
86
-
87
- if task_id in self._results.keys():
88
- if not self._wait_if_in_progress and isinstance(self._results[task_id], asyncio.Event):
89
- self._logger.debug("Task %s in progress but configured to not wait, returning None.", task_id)
90
- sem.release()
91
- return None
92
-
93
- if not isinstance(self._results[task_id], asyncio.Event):
94
- if self._restart_if_finished:
95
- self._logger.debug("Task %s already finished but configured to restart if finished, restarting.",
96
- task_id)
97
- # start_task releases the semaphore, no need to do it here
98
- return await self._start_task(task_id, func, **kwargs)
99
-
100
- self._logger.debug("Task %s already finished, returning.", task_id)
101
- sem.release()
102
- return self._results[task_id]
103
-
104
- self._logger.debug("Task %s in progress. Waiting.", task_id)
105
-
106
- event: asyncio.Event = self._results[task_id]
107
- sem.release()
108
-
109
- await event.wait()
110
-
111
- self._logger.debug("Finished waiting for %s.", task_id)
112
-
113
- return self._results[task_id]
114
- else:
115
- # start_task releases the semaphore, no need to do it here
116
- return await self._start_task(task_id, func, **kwargs)
117
-
118
- async def _start_task(self, task_id: object, func, **kwargs):
119
- """
120
- Starts a task and **releases the results list semaphore**
121
-
122
- Parameters
123
- ----------
124
- task
125
- The task to start
126
- task_id
127
- The ID of the task
128
- event
129
- The event that the task is attached to
130
-
131
- Returns
132
- -------
133
- T
134
- The result of the task
135
- """
136
- sem = self._semaphore
137
-
138
- self._logger.debug("Task %s started.", task_id)
139
-
140
- event = asyncio.Event()
141
- self._results[task_id] = event
142
- sem.release()
143
-
144
- if not self._wait_if_just_started:
145
- # noinspection PyAsyncCall
146
- asyncio.create_task(self._handle_task(func, task_id, event, func_args=kwargs))
147
- return None
148
-
149
- return await self._handle_task(func, task_id, event, func_args=kwargs)
150
-
151
- async def _handle_task(self, func: Callable, task_id: object, event: asyncio.Event, func_args: dict) -> T:
152
- """
153
- Handles the specified task
154
-
155
- Parameters
156
- ----------
157
- func
158
- The task to handle
159
- task_id
160
- The ID of the task
161
- event
162
- The event that the task is attached to
163
-
164
- Returns
165
- -------
166
- T
167
- The result of the task
168
- """
169
- sem = self._semaphore
170
-
171
- result = await func(**func_args)
172
-
173
- if isinstance(result, asyncio.Event):
174
- self._logger.warning("Result was of type asyncio.Event, this will break things!")
175
-
176
- async with sem:
177
- self._logger.debug("Task %s finished", task_id)
178
- # Record this task as done, storing the result if configured to
179
- self._results[task_id] = result if self._remember_result else None
180
- event.set()
181
-
182
- return result
183
-
184
- async def wait_if_in_progress(self, task_id: object):
185
- """
186
- Waits for a task if it is in progress. Returns immediately otherwise.
187
-
188
- Parameters
189
- ----------
190
- task_id
191
- The task id to wait for
192
- """
193
- sem = self._semaphore
194
-
195
- await sem.acquire()
196
-
197
- if task_id in self._results and isinstance(self._results[task_id], asyncio.Event):
198
- event: asyncio.Event = self._results[task_id]
199
- sem.release()
200
-
201
- await event.wait()
202
-
203
- def clear(self) -> None:
204
- """
205
- Deletes the stored results
206
-
207
- Returns
208
- -------
209
- None
210
- """
211
- self._results.clear()
212
-
213
- def results(self) -> list[T]:
214
- """
215
- Gets the results of all currently completed tasks
216
-
217
- Returns
218
- -------
219
- list[T]
220
- The results of all currently completed tasks, excluding Nones
221
- """
222
-
223
- def filter_func(it):
224
- return not isinstance(it, asyncio.Event) and it is not None
225
-
226
- return list(filter(filter_func, self._results.values()))
227
-
228
- def get_completed_result_or_nothing(self, task_id: object, default: T | None = None) -> T | None:
229
- """
230
- Returns the result of an already completed task, or nothing at all if result with that id exists or is still in progress
231
-
232
- Returns
233
- -------
234
- object
235
- The result of the task
236
-
237
- Raises
238
- ------
239
- ValueError
240
- If the task is still in progress
241
-
242
- KeyError
243
- If the task has not been started yet or does not exist
244
- """
245
- if task_id in self._results:
246
- result = self._results[task_id]
247
-
248
- if isinstance(result, asyncio.Event):
249
- return None
250
-
251
- return result
252
- else:
253
- return None
@@ -1,3 +0,0 @@
1
- from .expanding_tree import ExpandingTreeView
2
- from .model_helpers import HasColumnData, HasParent, HasChildren, HasText
3
- from .tree_model import TreeModel
@@ -1,165 +0,0 @@
1
- from typing import Any, TypeVar, Sequence, Optional
2
-
3
- from PySide6.QtCore import QModelIndex, Slot, QItemSelectionModel
4
- from PySide6.QtWidgets import QTreeView, QWidget
5
-
6
- from .model_helpers import HasChildren, HasParent
7
- from .tree_model import TreeModel
8
-
9
- T = TypeVar("T")
10
-
11
-
12
- class ExpandingTreeView(QTreeView):
13
- """
14
- Provides a way to retain the collapsed or expanded state of tree items after a model reset. Expects the tree's
15
- model to implement `AbstractItemModelHasListRoot` and any children to implement `HasChildren`. Also provides
16
- some methods that can be used to get a model index for an arbitrary object in a tree. These will NOT work when
17
- duplicate objects are present in the tree. Does NOT support multi-selection.
18
- """
19
-
20
- def __init__(self, parent: Optional[QWidget] = None):
21
- """
22
- Constructor
23
- :param session_factory: The session that will be used to update an item's expanded/collapsed state when it is changed
24
- :param parent: The parent of this treeview
25
- """
26
- super().__init__(parent)
27
- self._connect_expanded_listeners()
28
-
29
- def reexpand(self) -> None:
30
- """
31
- Re-expands all items in the tree that have children based on their saved collapsed/expanded state.
32
- """
33
- self._disconnect_expanded_listeners()
34
-
35
- try:
36
- root: Sequence[Any] = self.model().get_root()
37
-
38
- # Go through every item in the root and reexpand it and any children it has
39
- for index, item in enumerate(root):
40
- if isinstance(item, HasChildren):
41
- self._reexpand_recur(item, self.model().index(index, 0, QModelIndex()))
42
- finally:
43
- self._connect_expanded_listeners()
44
-
45
- def _connect_expanded_listeners(self):
46
- self.expanded.connect(self.tree_item_expanded)
47
- self.collapsed.connect(self.tree_item_collapsed)
48
-
49
- def _disconnect_expanded_listeners(self):
50
- self.expanded.disconnect(self.tree_item_expanded)
51
- self.collapsed.disconnect(self.tree_item_collapsed)
52
-
53
- def _reexpand_recur(self, item: HasChildren, index: QModelIndex) -> None:
54
- """
55
- Internal function to expand an item in the tree. Recurs to any children it has.
56
- :param item: The object to expand in the tree
57
- :param index: The index of that object
58
- """
59
- # Sanity check
60
- if not isinstance(item, HasChildren):
61
- raise TypeError("item must be an instance of HasChildren")
62
-
63
- # Expand this item
64
- self.setExpanded(index, not item.collapsed)
65
-
66
- for child_index, child in enumerate(item.get_children()):
67
- # Check if the child item can also have children and be collapsed. Also check that it actually has
68
- # any children.
69
- if isinstance(child, HasChildren) and len(child.get_children()) > 0:
70
- # Expand it and any children it has
71
- self._reexpand_recur(child, self.model().index(child_index, 0, index))
72
-
73
- @Slot()
74
- def tree_item_expanded(self, index: QModelIndex) -> None:
75
- """
76
- Slot that is connected to the tree's `expanded` signal
77
- :param index: The index that has been expanded
78
- """
79
- item = self.model().get_item(index)
80
-
81
- # Update the item's state
82
- if isinstance(item, HasChildren):
83
- item.collapsed = False
84
-
85
- @Slot()
86
- def tree_item_collapsed(self, index: QModelIndex) -> None:
87
- """
88
- Slot that is connected to the tree's `collapsed` signal
89
- :param index: The index that was collapsed
90
- """
91
- item = self.model().get_item(index)
92
-
93
- # Update the item's state
94
- if isinstance(item, HasChildren):
95
- item.collapsed = True
96
-
97
- def get_path_of_indexes_for_item(self, item: Any) -> list[int]:
98
- """
99
- Gets the path of numerical indexes for an arbitrary object in a tree.
100
- For example, second child of the first item would be [0, 1].
101
-
102
- :param item: The item to find the path for. The item must belong to the tree
103
- :return: The path to the item as a list of indexes to follow
104
- """
105
- path: list[int] = []
106
-
107
- # Keep going until we reach the root
108
- while isinstance(item, HasParent) and item.parent is not None:
109
- # Add the index of the item to the path and then go to the parent of that child until there is no parent
110
- path.insert(0, item.index_of_self)
111
- item = item.parent
112
-
113
- # Insert the index of the item belonging to the root list to the path
114
- path.insert(0, self.model().get_root().index(item))
115
-
116
- return path
117
-
118
- def select_object(self, item: Any) -> None:
119
- """
120
- Selects an arbitrary item that belongs to the tree. Will not work for branches that have duplicate items.
121
- :param item: The item to select. Must belong to the tree
122
- """
123
- self.select_item_by_path(self.get_path_of_indexes_for_item(item))
124
-
125
- def select_item_by_path(self, item_path: list) -> None:
126
- """
127
- Selects an item based on its path
128
- :param item_path: The path of the item
129
- """
130
- selection_model = self.selectionModel()
131
-
132
- # Convert the path to a model index
133
- # Clear previous selection and select the whole row
134
- selection_model.select(self.get_model_index_for_path(item_path),
135
- QItemSelectionModel.SelectionFlag.ClearAndSelect | QItemSelectionModel.SelectionFlag.Rows)
136
-
137
- def get_model_index_for_path(self, item_path: list[int]) -> QModelIndex:
138
- """
139
- Converts an item's path into a QModelIndex
140
- :param item_path: The path of the item to convert
141
- :return: The path as a QModelIndex
142
- """
143
- model: TreeModel[Any] = self.model()
144
- # Get the first index
145
- item_index = item_path.pop(0)
146
- # Create the base QModelIndex
147
- model_index = model.index(item_index, 0, QModelIndex())
148
- # Get the children of the first item
149
- children = model.get_root()[item_index]
150
-
151
- while len(item_path) > 0:
152
- # Get the next item
153
- item_index = item_path.pop(0)
154
- # Update the QModelIndex
155
- model_index = model.index(item_index, 0, model_index)
156
-
157
- if len(item_path) > 0:
158
- # Sanity check
159
- if not isinstance(children, HasChildren):
160
- raise TypeError("Parent object does not have any children but a child was expected")
161
-
162
- # Go to the children of this child next
163
- children = children.children[item_index]
164
-
165
- return model_index
@@ -1,36 +0,0 @@
1
- from typing import Sequence, Any
2
-
3
-
4
- class HasColumnData:
5
- def get_column_data(self, column: int, role: int) -> str | None:
6
- raise NotImplementedError()
7
-
8
-
9
- class HasText:
10
- @property
11
- def text(self) -> str:
12
- raise NotImplementedError()
13
-
14
-
15
- class HasParent:
16
- @property
17
- def parent(self) -> Any:
18
- raise NotImplementedError()
19
-
20
- @property
21
- def index_of_self(self) -> int:
22
- raise NotImplementedError()
23
-
24
-
25
- class HasChildren:
26
- @property
27
- def collapsed(self) -> bool:
28
- raise NotImplementedError()
29
-
30
- @collapsed.setter
31
- def collapsed(self, value: bool):
32
- raise NotImplementedError()
33
-
34
- @property
35
- def children(self) -> Sequence[HasColumnData]:
36
- raise NotImplementedError()
@@ -1,85 +0,0 @@
1
- from typing import Any, Sequence, TypeVar, Generic, Optional
2
-
3
- from PySide6.QtCore import QAbstractItemModel, QModelIndex, QPersistentModelIndex
4
- from PySide6.QtWidgets import QWidget
5
-
6
- from .model_helpers import HasColumnData, HasParent, HasChildren
7
-
8
- T = TypeVar("T")
9
-
10
-
11
- # todo comment this
12
- class TreeModel(QAbstractItemModel, Generic[T]):
13
- def __init__(self, parent: Optional[QWidget] = None):
14
- super().__init__(parent)
15
- self.root: Sequence[T] = []
16
-
17
- def get_item(self, index: QModelIndex | QPersistentModelIndex) -> object:
18
- if index.isValid():
19
- return index.internalPointer()
20
-
21
- return self.root
22
-
23
- def data(self, index: QModelIndex | QPersistentModelIndex, role: int = ...) -> Any:
24
- if not index.isValid():
25
- return None
26
-
27
- item = self.get_item(index)
28
-
29
- if isinstance(item, HasColumnData):
30
- return item.get_column_data(index.column(), role)
31
- else:
32
- return None
33
-
34
- def index(self, row: int, column: int, parent: QModelIndex | QPersistentModelIndex = ...) -> QModelIndex:
35
- if parent.isValid() and parent.column() != 0:
36
- return QModelIndex()
37
-
38
- parent_item = self.get_item(parent)
39
-
40
- if isinstance(parent_item, HasChildren):
41
- parent_list = parent_item.children
42
- elif isinstance(parent_item, Sequence):
43
- parent_list = parent_item
44
- else:
45
- raise TypeError(
46
- f"Expected parent of item to be Sequence or HasChildren, actually {parent_item.__class__}")
47
-
48
- if row > len(parent_list) or row < 0:
49
- return QModelIndex()
50
-
51
- return self.createIndex(row, column, parent_list[row])
52
-
53
- def parent(self, child: QModelIndex | QPersistentModelIndex = QModelIndex()) -> QModelIndex:
54
- if not child.isValid():
55
- return QModelIndex()
56
-
57
- child_item = self.get_item(child)
58
-
59
- # We don't need to handle the root items here because... they are the root and have no parents
60
-
61
- if isinstance(child_item, HasParent) and child_item.parent is not None:
62
- return self.createIndex(child_item.index_of_self, 0, child_item.parent)
63
-
64
- return QModelIndex()
65
-
66
- def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
67
- if parent.isValid() and parent.column() > 0:
68
- return 0
69
-
70
- parent_item = self.get_item(parent)
71
-
72
- if isinstance(parent_item, HasChildren):
73
- return len(parent_item.children)
74
- elif isinstance(parent_item, Sequence):
75
- return len(parent_item)
76
- else:
77
- return 0
78
-
79
- def get_root(self) -> Sequence[T]:
80
- """
81
- Returns the list of root level items (i.e. items which have no parents) for the tree
82
- :return: List of root level items (i.e. items which have no parents) for the tree
83
- :return: List of root level items (i.e. items which have no parents) for the tree
84
- """
85
- return self.root
@@ -1,21 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: qcanvas
3
- Version: 0.0.5.7a0
4
- Summary: A canvas client
5
- Author: QCanvas
6
- Classifier: Operating System :: OS Independent
7
- Classifier: Programming Language :: Python :: 3
8
- Requires-Python: <=3.12,>=3.11
9
- Requires-Dist: aiosqlite-custom~=0.19.0
10
- Requires-Dist: beautifulsoup4~=4.12.3
11
- Requires-Dist: gql~=3.6.0b0
12
- Requires-Dist: httpx~=0.26.0
13
- Requires-Dist: packaging~=23.2
14
- Requires-Dist: pydantic~=2.6.0
15
- Requires-Dist: pyqtdarktheme~=2.1.0
16
- Requires-Dist: pyside6~=6.6.1
17
- Requires-Dist: qasync~=0.27.1
18
- Requires-Dist: qenerate-custom~=0.6.3
19
- Requires-Dist: requests~=2.31.0
20
- Requires-Dist: sqlalchemy~=2.0.25
21
- Requires-Dist: tenacity~=8.2.3
@@ -1,62 +0,0 @@
1
- qcanvas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- qcanvas/__main__.py,sha256=FOj1TahcG6GSDppkpe09X2Grddc0sXu5Ql5YUABbG5w,5192
3
- qcanvas/db/__init__.py,sha256=j5ZiTiLuY0CYRUV2bktc8qE5x-jBNaSaSVNKAF8DKF0,420
4
- qcanvas/db/database.py,sha256=jKSExpMuq1x7GppeDW2mRHJznLGHeexf76wgvnBnNWQ,10557
5
- qcanvas/db/db_converter_helper.py,sha256=-Rpe4CvMJBmvYXLw68caJWwEMPvxsYJB800Z8vHyqX4,2169
6
- qcanvas/icons/__init__.py,sha256=nknAcvd97UBIYO1dR4atTCY3PHudbPprPT7FPyhqugc,35
7
- qcanvas/icons/icons.qrc,sha256=_wGNdTTOuygPLeFMJxA3Q2HleqJKKkzW38VEhWJ76Rg,110
8
- qcanvas/icons/main_icon.svg,sha256=26moo6Ki_YSkazCgOZgs4pi2p4qWNLDIWugwLMfAWfs,16104
9
- qcanvas/icons/rc_icons.py,sha256=F8TS5WzBbKdNOm3uARzOU0O2ODpOlh4OPIddp1B-1P4,10896
10
- qcanvas/icons/sync.svg,sha256=ArqfW_jH-eEjDnxyVPBBo2OOoDNja1OfN3KiptPs258,2835
11
- qcanvas/net/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- qcanvas/net/custom_httpx_async_transport.py,sha256=1OyszJO8TrwDrq-no-angb-AIjgq_GiV67tQHuPxmm0,1227
13
- qcanvas/net/self_authenticating.py,sha256=VkWpp7kxaWQHvqMjW9Dw-oY0aNFOT-6SYkJMnSvCdqU,4136
14
- qcanvas/net/canvas/__init__.py,sha256=EPLFN05Vx9yNHDLK-eUp97C6ymtMTnscwdn28OBlNJw,96
15
- qcanvas/net/canvas/canvas_client.py,sha256=yxhk3uxjkZFXkp-99Jmr7_GDqpa5OBtNBpQU6zQSGuc,8778
16
- qcanvas/net/canvas/legacy_canvas_types.py,sha256=pKuTBh1m5oSq9e7jmbHzYfKeoPSw0uHAqLwddKZpsr4,3942
17
- qcanvas/queries/__init__.py,sha256=tRUwMS1OvYIWRd4WpmAHrDrcelj_dLEaesxh9NngC6s,227
18
- qcanvas/queries/all_courses.gql,sha256=8U0_3VOcD66SRCfvhZ9IOTmxVgDPJdMD_LuGPbM9vVU,124
19
- qcanvas/queries/all_courses.py,sha256=-DpMy7eDFUeYp3ffZrwXp15qo_t6Lfx8K-rFi3OK1DM,2751
20
- qcanvas/queries/canvas_course_data.gql,sha256=KJHjdu5sMKld4L6mPGfUrhqRLx_wdgGH8fwTnmRFGrI,1115
21
- qcanvas/queries/canvas_course_data.py,sha256=_ZNv9stXAyap2eBbCqmajXVpWr0920ZUcCS6Gtyyo0g,4314
22
- qcanvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- qcanvas/ui/container_item.py,sha256=MNGL3Z6dNlM59XXWqmXB1Ql3FTOEdA6TY2K9Do1LmTE,285
24
- qcanvas/ui/main_ui.py,sha256=4OKDbY3l4f6lxOAiN6v9_yy1Ak1F8pyZ-lCySsqJABI,10219
25
- qcanvas/ui/setup_dialog.py,sha256=Rh9Zj595-OdPUN31cN8QcY5V2giQ3zPRUg65t6wQ-LY,7270
26
- qcanvas/ui/status_bar_reporter.py,sha256=uLX_C8r0augOSOwS-HCeDFxuSQKUJ8qYM5RE8kTKklU,1524
27
- qcanvas/ui/menu_bar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- qcanvas/ui/menu_bar/grouping_preferences_menu.py,sha256=nSe9qt3pI9WAiKVTOEtHN9JPW3gaDxpU8YiSGGRjR5M,2407
29
- qcanvas/ui/menu_bar/theme_selection_menu.py,sha256=WdcFMEO-FRYJm3qKjYbUpRhJPVe0-RhwrVxcyckSr0Q,1248
30
- qcanvas/ui/viewer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- qcanvas/ui/viewer/course_list.py,sha256=umJr_zUUPuL4sGNfcaiDl-Vsodned5egTxEydAKdiCw,3421
32
- qcanvas/ui/viewer/file_list.py,sha256=hqKXrLs4bLHA9himFEuE8d8Pq2TJnvuUGGJgZftiyrA,7869
33
- qcanvas/ui/viewer/file_view_tab.py,sha256=DrXwHwIPBoHVjsKU3zUbOvA4tTarp29d4cMYvZnE-O8,2205
34
- qcanvas/ui/viewer/page_list_viewer.py,sha256=FKFuqgy_yQPS_i3c0AbQk5E3mxtOfgYI7Q81mD28bLI,5678
35
- qcanvas/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- qcanvas/util/app_settings.py,sha256=G_aq-7FPXmV4pN0cM2VldFS1HwTIzofgezgXt0y-AQ0,3017
37
- qcanvas/util/constants.py,sha256=GNRlqG9rnWlW5ijKoJEsyuOWTdrXNSiWL_a5XeEZzI0,259
38
- qcanvas/util/download_pool.py,sha256=8MRzdxZMmCibMx1rwh0G8U2SPjITtvDieLyr8drhJ7c,2003
39
- qcanvas/util/progress_reporter.py,sha256=wgD8A_mrZ3EZQtrJeCYoQ0TvMIVtdFKDVNTQrbZMoDA,2934
40
- qcanvas/util/self_updater.py,sha256=oItk-J-CSN74RGu4P_xtJ4S8vGnaAZL23Bm4jpLbLzg,1655
41
- qcanvas/util/task_pool.py,sha256=NHrsgY8Srd4I9Iv3qqGLblF2RRqLqbLcYFgvnEGpvaY,8192
42
- qcanvas/util/course_indexer/__init__.py,sha256=IqwTfB8KVk7Go_yuBL8NOANgFiBCzjt0z38D4CEgq7w,38
43
- qcanvas/util/course_indexer/conversion_helpers.py,sha256=DZwx3Ct-MYzxQwuvqem5c2cARSkr_2SJvz0gaZDJq6c,2644
44
- qcanvas/util/course_indexer/data_manager.py,sha256=-bqw28YZNoq-BcT3LsJl5RZS5tTpgk85wmfR2bgSCPc,18872
45
- qcanvas/util/course_indexer/resource_helpers.py,sha256=htd3XnrnOEkS8iAxTqAIypgMzqJEcjs1Qte9wmF2ccQ,6966
46
- qcanvas/util/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- qcanvas/util/helpers/canvas_sanitiser.py,sha256=c6r6cyJomQ1zn5wFK-LX-ZsZXQZ-A2awBFtCB_JqUtE,1184
48
- qcanvas/util/helpers/file_icon_helper.py,sha256=6Rgy5K5wB6Hofsx4Etu6JlurAJEqma32zOLWgRHTf48,925
49
- qcanvas/util/helpers/qaction_helper.py,sha256=lQ3eouIgR3w3o1jLNAmu2plAdc6HBBT5qaE4cnW6cX8,667
50
- qcanvas/util/helpers/theme_helper.py,sha256=_qnXIMSeuXW3a8mincehHRtJgTv2Rm93Q3y2N7dW3Zo,1290
51
- qcanvas/util/link_scanner/__init__.py,sha256=VVZ4L0AwC-TQtX6JQNUcpWH8UhKaCaSS3_7X4i3mipE,97
52
- qcanvas/util/link_scanner/canvas_link_scanner.py,sha256=M7_zc7AU2pI9RdR8AaC9cyEiNufnu6bNxu4qu7FaLns,1451
53
- qcanvas/util/link_scanner/canvas_media_object_scanner.py,sha256=8xnDtrdqxIzW-8JuoKXGUoB2usR6S8rzma_QB6rGzaI,2219
54
- qcanvas/util/link_scanner/dropbox_scanner.py,sha256=_xIOGpPH5voORGPGLCEXcQCWWf-PVSBg8MEm-hcIs3M,2034
55
- qcanvas/util/link_scanner/resource_scanner.py,sha256=EY5AzDXG6Ix18YpRkJcMjOPaygird6iD1akaG-yUyHc,1988
56
- qcanvas/util/tree_util/__init__.py,sha256=yqjNHrD8SiyW_fGPfw7xdS-Rctjdu-RMWSkUxcGSn8k,154
57
- qcanvas/util/tree_util/expanding_tree.py,sha256=5UM9kfcHtVavzAc9nwd7lucVnjI_dkVH8qC3GvmBvtc,6622
58
- qcanvas/util/tree_util/model_helpers.py,sha256=io1bHOMNgVqkPvGH2Iohug1-wx2MwC_-qlWlpVA0fFE,743
59
- qcanvas/util/tree_util/tree_model.py,sha256=JhlYmGqxlul8jUUQ-KtPXIehzgG-UtszHve7Wh-XbtE,2981
60
- qcanvas-0.0.5.7a0.dist-info/METADATA,sha256=y1oIx74ntPhF74IYe1uVsBwXWefTp9wNjpcV1QPrlgo,650
61
- qcanvas-0.0.5.7a0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
62
- qcanvas-0.0.5.7a0.dist-info/RECORD,,
File without changes