qcanvas 1.2.2__py3-none-any.whl → 2.0.0__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 (130) hide show
  1. qcanvas/__init__.py +54 -0
  2. qcanvas/app.py +93 -0
  3. qcanvas/backend_connectors/frontend_resource_manager.py +13 -5
  4. qcanvas/backend_connectors/qcanvas_task_master.py +2 -2
  5. qcanvas/icons/__init__.py +5 -5
  6. qcanvas/icons/_icon_type.py +1 -1
  7. qcanvas/icons/icons.qrc +47 -43
  8. qcanvas/icons/rc_icons.py +1298 -1197
  9. qcanvas/settings/__init__.py +6 -0
  10. qcanvas/{util/settings → settings}/_client_settings.py +4 -4
  11. qcanvas/settings/_course_settings.py +54 -0
  12. qcanvas/{util/settings → settings}/_mapped_setting.py +2 -5
  13. qcanvas/{util/settings → settings}/_ui_settings.py +4 -4
  14. qcanvas/theme.py +101 -0
  15. qcanvas/ui/course_viewer/content_tree.py +9 -12
  16. qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +3 -3
  17. qcanvas/ui/course_viewer/course_tree/course_tree.py +9 -8
  18. qcanvas/ui/course_viewer/course_viewer.py +35 -43
  19. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
  20. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -4
  21. qcanvas/ui/course_viewer/tabs/constants.py +1 -0
  22. qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
  23. qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +4 -4
  24. qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +7 -10
  25. qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +6 -7
  26. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
  27. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +7 -8
  28. qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
  29. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +5 -5
  30. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +15 -21
  31. qcanvas/ui/course_viewer/tree_widget_data_item.py +1 -1
  32. qcanvas/ui/memory_tree/_tree_memory.py +45 -42
  33. qcanvas/ui/memory_tree/memory_tree_widget.py +22 -18
  34. qcanvas/ui/memory_tree/memory_tree_widget_item.py +3 -3
  35. qcanvas/ui/qcanvas_window/__init__.py +1 -0
  36. qcanvas/ui/{main_ui → qcanvas_window}/course_viewer_container.py +10 -10
  37. qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +5 -5
  38. qcanvas/ui/{main_ui → qcanvas_window}/options/quick_sync_option.py +7 -6
  39. qcanvas/ui/{main_ui → qcanvas_window}/options/sync_on_start_option.py +7 -6
  40. qcanvas/ui/{main_ui → qcanvas_window}/options/theme_selection_menu.py +10 -10
  41. qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +38 -33
  42. qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +5 -6
  43. qcanvas/ui/qml_components/AttachmentsList.ui.qml +15 -0
  44. qcanvas/ui/qml_components/AttachmentsListDelegate.qml +77 -0
  45. qcanvas/ui/qml_components/AttachmentsListModel.qml +19 -0
  46. qcanvas/ui/qml_components/AttachmentsPane.qml +12 -0
  47. qcanvas/ui/qml_components/CommentsList.ui.qml +15 -0
  48. qcanvas/ui/qml_components/CommentsListDelegate.ui.qml +118 -0
  49. qcanvas/ui/qml_components/CommentsListModel.qml +56 -0
  50. qcanvas/ui/qml_components/CommentsPane.qml +12 -0
  51. qcanvas/ui/qml_components/DarkTheme.qml +12 -0
  52. qcanvas/ui/qml_components/DecoratedText.ui.qml +44 -0
  53. qcanvas/ui/qml_components/LightTheme.qml +11 -0
  54. qcanvas/ui/qml_components/Spacer.ui.qml +7 -0
  55. qcanvas/ui/qml_components/ThemedRectangle.qml +37 -0
  56. qcanvas/ui/qml_components/__init__.py +3 -0
  57. qcanvas/ui/qml_components/attachments_pane.py +72 -0
  58. qcanvas/ui/qml_components/comments_pane.py +85 -0
  59. qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
  60. qcanvas/ui/qml_components/qml_pane.py +22 -0
  61. qcanvas/ui/setup/setup_checker.py +1 -1
  62. qcanvas/ui/setup/setup_dialog.py +27 -10
  63. qcanvas/util/auto_downloader.py +9 -7
  64. qcanvas/util/basic_fonts.py +2 -2
  65. qcanvas/util/context_dict.py +12 -0
  66. qcanvas/util/file_icons.py +11 -19
  67. qcanvas/util/layouts.py +5 -7
  68. qcanvas/util/paths.py +17 -6
  69. qcanvas/util/qurl_util.py +1 -1
  70. qcanvas/util/ui_tools.py +118 -8
  71. qcanvas/util/url_checker.py +1 -1
  72. {qcanvas-1.2.2.dist-info → qcanvas-2.0.0.dist-info}/METADATA +12 -10
  73. qcanvas-2.0.0.dist-info/RECORD +91 -0
  74. {qcanvas-1.2.2.dist-info → qcanvas-2.0.0.dist-info}/WHEEL +1 -1
  75. qcanvas-2.0.0.dist-info/entry_points.txt +3 -0
  76. qcanvas/app_start/__init__.py +0 -59
  77. qcanvas/icons/_update_icons.py +0 -89
  78. qcanvas/icons/dark/actions/exit.svg +0 -3
  79. qcanvas/icons/dark/actions/mark_all_read.svg +0 -3
  80. qcanvas/icons/dark/actions/open_downloads.svg +0 -3
  81. qcanvas/icons/dark/actions/quick_login.svg +0 -3
  82. qcanvas/icons/dark/actions/sync.svg +0 -3
  83. qcanvas/icons/dark/branding/logo_transparent.svg +0 -303
  84. qcanvas/icons/dark/options/auto_download.svg +0 -3
  85. qcanvas/icons/dark/options/theme.svg +0 -3
  86. qcanvas/icons/dark/tabs/assignments.svg +0 -3
  87. qcanvas/icons/dark/tabs/mail.svg +0 -3
  88. qcanvas/icons/dark/tabs/pages.svg +0 -3
  89. qcanvas/icons/dark/tree_items/assignment.svg +0 -3
  90. qcanvas/icons/dark/tree_items/mail.svg +0 -3
  91. qcanvas/icons/dark/tree_items/module.svg +0 -3
  92. qcanvas/icons/dark/tree_items/page.svg +0 -3
  93. qcanvas/icons/light/actions/exit.svg +0 -3
  94. qcanvas/icons/light/actions/mark_all_read.svg +0 -3
  95. qcanvas/icons/light/actions/open_downloads.svg +0 -3
  96. qcanvas/icons/light/actions/quick_login.svg +0 -3
  97. qcanvas/icons/light/actions/sync.svg +0 -3
  98. qcanvas/icons/light/branding/logo_transparent.svg +0 -304
  99. qcanvas/icons/light/options/auto_download.svg +0 -3
  100. qcanvas/icons/light/options/ignore_old.svg +0 -3
  101. qcanvas/icons/light/options/include_videos.svg +0 -3
  102. qcanvas/icons/light/options/theme.svg +0 -3
  103. qcanvas/icons/light/tabs/assignments.svg +0 -3
  104. qcanvas/icons/light/tabs/mail.svg +0 -3
  105. qcanvas/icons/light/tabs/pages.svg +0 -3
  106. qcanvas/icons/light/tree_items/assignment.svg +0 -3
  107. qcanvas/icons/light/tree_items/mail.svg +0 -3
  108. qcanvas/icons/light/tree_items/module.svg +0 -3
  109. qcanvas/icons/light/tree_items/page.svg +0 -3
  110. qcanvas/icons/universal/branding/main_icon.svg +0 -325
  111. qcanvas/icons/universal/downloads/download_failed.svg +0 -23
  112. qcanvas/icons/universal/downloads/downloaded.svg +0 -23
  113. qcanvas/icons/universal/downloads/not_downloaded.svg +0 -23
  114. qcanvas/icons/universal/downloads/unknown.svg +0 -6
  115. qcanvas/icons/universal/tabs/assignments_new_content.svg +0 -3
  116. qcanvas/icons/universal/tabs/mail_new_content.svg +0 -3
  117. qcanvas/icons/universal/tabs/pages_new_content.svg +0 -3
  118. qcanvas/icons/universal/tree_items/semester.svg +0 -108
  119. qcanvas/run.py +0 -54
  120. qcanvas/ui/course_viewer/tabs/util.py +0 -11
  121. qcanvas/ui/main_ui/__init__.py +0 -0
  122. qcanvas/util/settings/__init__.py +0 -9
  123. qcanvas/util/themes/__init__.py +0 -2
  124. qcanvas/util/themes/_colour_scheme_helper.py +0 -38
  125. qcanvas/util/themes/_selected_theme.py +0 -10
  126. qcanvas/util/themes/_theme_changed_event.py +0 -17
  127. qcanvas/util/themes/_theme_changer.py +0 -86
  128. qcanvas-1.2.2.dist-info/RECORD +0 -118
  129. qcanvas-1.2.2.dist-info/entry_points.txt +0 -3
  130. /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
@@ -1,12 +1,13 @@
1
1
  import asyncio
2
2
  import logging
3
3
 
4
- import qcanvas_backend.database.types as db
5
- from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
6
- from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
7
- from qtpy.QtWidgets import QMessageBox, QWidget
4
+ from PySide6.QtCore import Qt
5
+ from libqcanvas import db
6
+ from libqcanvas.net.resources.download.resource_manager import ResourceManager
7
+ from libqcanvas.net.sync.sync_receipt import SyncReceipt
8
+ from PySide6.QtWidgets import QMessageBox, QWidget
8
9
 
9
- from qcanvas.util import settings
10
+ import qcanvas.settings as settings
10
11
 
11
12
  _logger = logging.getLogger(__name__)
12
13
 
@@ -20,7 +21,7 @@ async def download_new_resources(
20
21
  ) -> None:
21
22
  resources_to_download = []
22
23
 
23
- for file_id in receipt.updated_resources:
24
+ for file_id in receipt.updates[db.Resource]:
24
25
  resource = all_resources[file_id]
25
26
 
26
27
  if _should_auto_download_resource(resource, resource_manager=downloader):
@@ -36,7 +37,8 @@ async def download_new_resources(
36
37
  msg.accepted.connect(
37
38
  lambda: asyncio.get_running_loop().create_task(
38
39
  downloader.batch_download(resources_to_download),
39
- )
40
+ ),
41
+ Qt.ConnectionType.SingleShotConnection,
40
42
  )
41
43
  else:
42
44
  await downloader.batch_download(resources_to_download)
@@ -1,5 +1,5 @@
1
- from qtpy.QtGui import QFont
2
- from qtpy.QtWidgets import QLabel
1
+ from PySide6.QtGui import QFont
2
+ from PySide6.QtWidgets import QLabel
3
3
 
4
4
  normal_font = QFont()
5
5
  bold_font = QFont()
@@ -0,0 +1,12 @@
1
+ from PySide6.QtQml import QQmlContext
2
+
3
+
4
+ class ContextDict:
5
+ def __init__(self, context: QQmlContext):
6
+ self._context = context
7
+
8
+ def __getitem__(self, item):
9
+ return self._context.contextProperty(item)
10
+
11
+ def __setitem__(self, key, value):
12
+ self._context.setContextProperty(key, value)
@@ -1,9 +1,10 @@
1
1
  import logging
2
2
  import os.path
3
3
 
4
- from qtpy.QtCore import QFileInfo, QMimeDatabase
5
- from qtpy.QtGui import QIcon
6
- from qtpy.QtWidgets import QApplication, QFileIconProvider, QStyle
4
+ import cachetools
5
+ from PySide6.QtCore import QFileInfo, QMimeDatabase
6
+ from PySide6.QtGui import QIcon
7
+ from PySide6.QtWidgets import QApplication, QFileIconProvider, QStyle
7
8
 
8
9
  import qcanvas.util.runtime as runtime
9
10
 
@@ -18,13 +19,9 @@ if runtime.is_running_on_windows:
18
19
 
19
20
  else:
20
21
  _mime_database = QMimeDatabase()
21
- # This must be initialised lazily because the QApplication might not be initialised at this time
22
- _default_icon: QIcon | None = None
23
22
  _icon_for_suffix: dict[str, QIcon] = {}
24
23
 
25
24
  def icon_for_filename(file_name: str) -> QIcon:
26
- global _default_icon
27
-
28
25
  file_suffix = os.path.splitext(file_name)[1]
29
26
 
30
27
  # Check if we already know what icon this file type has
@@ -39,16 +36,11 @@ else:
39
36
  _icon_for_suffix[file_suffix] = icon
40
37
  return icon
41
38
 
42
- _lazy_init_default_icon()
43
-
44
- # No icon for this type of file was found, return default icon
45
- _icon_for_suffix[file_suffix] = _default_icon
46
- return _default_icon
47
-
48
- def _lazy_init_default_icon() -> None:
49
- global _default_icon
39
+ # No icon for this type of file was found, use default icon
40
+ icon = _default_icon()
41
+ _icon_for_suffix[file_suffix] = icon
42
+ return icon
50
43
 
51
- if _default_icon is None:
52
- _default_icon = QApplication.style().standardIcon(
53
- QStyle.StandardPixmap.SP_FileIcon
54
- )
44
+ @cachetools.cached(cachetools.LRUCache(maxsize=1))
45
+ def _default_icon() -> QIcon:
46
+ return QApplication.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon)
qcanvas/util/layouts.py CHANGED
@@ -1,13 +1,11 @@
1
1
  import logging
2
- from typing import *
2
+ from typing import Iterable, NamedTuple
3
3
 
4
- from qtpy.QtCore import Qt
5
- from qtpy.QtWidgets import *
4
+ from PySide6.QtCore import Qt
5
+ from PySide6.QtWidgets import QGridLayout, QLayout, QWidget
6
6
 
7
7
  _logger = logging.getLogger(__name__)
8
8
 
9
- T = TypeVar("T")
10
-
11
9
 
12
10
  class GridItem(NamedTuple):
13
11
  widget: QWidget
@@ -16,13 +14,13 @@ class GridItem(NamedTuple):
16
14
  alignment: Qt.AlignmentFlag = Qt.AlignmentFlag.AlignLeft
17
15
 
18
16
 
19
- def layout_widget(layout_type: Type[T], *items: QWidget, **kwargs) -> QWidget:
17
+ def layout_widget[T](layout_type: type[T], *items: QWidget, **kwargs) -> QWidget:
20
18
  widget = QWidget()
21
19
  widget.setLayout(layout(layout_type, *items, **kwargs))
22
20
  return widget
23
21
 
24
22
 
25
- def layout(layout_type: Type[T], *items: QWidget | QLayout, **kwargs) -> T:
23
+ def layout[T](layout_type: type[T], *items: QWidget | QLayout, **kwargs) -> T:
26
24
  result_layout: QLayout = layout_type(**kwargs)
27
25
 
28
26
  for item in items:
qcanvas/util/paths.py CHANGED
@@ -4,21 +4,30 @@ from pathlib import Path
4
4
 
5
5
  import cachetools
6
6
  import platformdirs
7
- from qtpy.QtCore import QSettings
7
+ from PySide6.QtCore import QSettings
8
8
 
9
- from qcanvas.util.runtime import *
9
+ from qcanvas.util.runtime import (
10
+ is_running_as_compiled,
11
+ is_running_as_flatpak,
12
+ is_running_portable,
13
+ )
10
14
 
11
15
  _logger = logging.getLogger(__name__)
12
16
 
13
17
 
14
- def ui_storage() -> Path:
15
- return root() / ".UI"
16
-
17
-
18
18
  def data_storage() -> Path:
19
19
  return root()
20
20
 
21
21
 
22
+ def config_storage() -> Path:
23
+ if is_running_portable:
24
+ return root()
25
+ else:
26
+ path = platformdirs.user_config_path("QCanvasTeam", "QCanvas")
27
+ path.mkdir(parents=True, exist_ok=True)
28
+ return path
29
+
30
+
22
31
  def client_settings() -> QSettings:
23
32
  if is_running_portable:
24
33
  return QSettings("QCanvas.ini", QSettings.Format.IniFormat)
@@ -35,6 +44,8 @@ def root() -> Path:
35
44
  root_path = Path(os.environ["XDG_DATA_HOME"])
36
45
  elif not is_running_portable and is_running_as_compiled:
37
46
  root_path = platformdirs.user_data_path("QCanvasReborn", "QCanvasTeam")
47
+ elif not is_running_portable:
48
+ _logger.warning("Don't know how we're being run? Are you running from source?")
38
49
 
39
50
  print("Root path", root_path.absolute())
40
51
  _logger.debug("Root path %s", root_path.absolute())
qcanvas/util/qurl_util.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from pathlib import Path
3
3
 
4
- from qtpy.QtCore import QUrl
4
+ from PySide6.QtCore import QUrl
5
5
 
6
6
  _logger = logging.getLogger(__name__)
7
7
 
qcanvas/util/ui_tools.py CHANGED
@@ -1,14 +1,124 @@
1
- import logging
2
- from typing import Any
1
+ from typing import Any, Sequence
3
2
 
4
- from qtpy.QtGui import QIcon, QKeySequence, QPixmap
5
- from qtpy.QtWidgets import *
3
+ from PySide6.QtCore import QSize
4
+ from PySide6.QtGui import QAction, QIcon, QKeySequence, QPixmap, QFont
5
+ from PySide6.QtWidgets import (
6
+ QMenu,
7
+ QSizePolicy,
8
+ QLabel,
9
+ QWidget,
10
+ QFormLayout,
11
+ QDockWidget,
12
+ QLayout,
13
+ QHBoxLayout,
14
+ QVBoxLayout,
15
+ )
6
16
 
7
- _logger = logging.getLogger(__name__)
8
17
 
18
+ def font(*, point_size: float | int | None = None, bold: bool | None = None) -> QFont:
19
+ _font = QFont()
9
20
 
10
- def make_truncatable(widget: QWidget) -> None:
11
- widget.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Fixed)
21
+ if point_size is not None:
22
+ if isinstance(point_size, int):
23
+ _font.setPointSize(point_size)
24
+ elif isinstance(point_size, float):
25
+ _font.setPointSizeF(point_size)
26
+ else:
27
+ raise TypeError("point_size")
28
+
29
+ if bold is not None:
30
+ _font.setBold(bold)
31
+
32
+ return _font
33
+
34
+
35
+ _bold = font(bold=True)
36
+
37
+
38
+ def label(text: str, *, font: QFont = None, allow_truncation: bool = False) -> QLabel:
39
+ _label = QLabel(text)
40
+
41
+ if font is not None:
42
+ _label.setFont(font)
43
+
44
+ if allow_truncation:
45
+ _label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Fixed)
46
+
47
+ return _label
48
+
49
+
50
+ def bold_label(text: str, *, allow_truncation: bool = False) -> QLabel:
51
+ return label(text, allow_truncation=allow_truncation, font=_bold)
52
+
53
+
54
+ def form_layout(
55
+ rows: dict[str, QWidget], label_font: QFont | None = _bold, label_suffix: str = ":"
56
+ ) -> QFormLayout:
57
+ layout = QFormLayout()
58
+
59
+ for name, widget in rows.items():
60
+ if label_font:
61
+ layout.addRow(label(name + label_suffix, font=label_font), widget)
62
+ else:
63
+ layout.addRow(name + label_suffix, widget)
64
+
65
+ return layout
66
+
67
+
68
+ def size(width: int, height: int):
69
+ return QSize(width, height)
70
+
71
+
72
+ def dock_widget(
73
+ *,
74
+ widget: QWidget,
75
+ title: str | None = None,
76
+ hide: bool = False,
77
+ name: str | None = None,
78
+ min_size: QSize | None = None,
79
+ features: QDockWidget.DockWidgetFeature | None = None,
80
+ parent: QWidget | None = None,
81
+ ) -> QDockWidget:
82
+ dock = QDockWidget(title, parent)
83
+ dock.setWidget(widget)
84
+
85
+ if name is not None:
86
+ dock.setObjectName(name)
87
+
88
+ if min_size is not None:
89
+ dock.setMinimumSize(min_size)
90
+
91
+ if features is not None:
92
+ dock.setFeatures(features)
93
+
94
+ if hide:
95
+ dock.hide()
96
+
97
+ return dock
98
+
99
+
100
+ def widget(layout: QLayout) -> QWidget:
101
+ widget = QWidget()
102
+ widget.setLayout(layout)
103
+ return widget
104
+
105
+
106
+ def hbox(*items: QWidget | QLayout) -> QHBoxLayout:
107
+ _add_layout_items(layout := QHBoxLayout(), items)
108
+ return layout
109
+
110
+
111
+ def vbox(*items: QWidget | QLayout) -> QVBoxLayout:
112
+ _add_layout_items(layout := QVBoxLayout(), items)
113
+ return layout
114
+
115
+
116
+ def _add_layout_items(layout: QLayout, items: Sequence[QWidget | QLayout]):
117
+ for item in items:
118
+ if isinstance(item, QLayout):
119
+ layout.addItem(item)
120
+ else:
121
+ layout.addWidget(item)
12
122
 
13
123
 
14
124
  def create_qaction(
@@ -19,7 +129,7 @@ def create_qaction(
19
129
  triggered: Any = None,
20
130
  checkable: bool | None = None,
21
131
  checked: bool | None = None,
22
- icon: QIcon | QPixmap | None = None
132
+ icon: QIcon | QPixmap | None = None,
23
133
  ) -> QAction:
24
134
  action = QAction(name)
25
135
 
@@ -6,4 +6,4 @@ _logger = logging.getLogger(__name__)
6
6
 
7
7
 
8
8
  def is_url(url: str) -> bool:
9
- return validators.url(url) == True
9
+ return validators.url(url)
@@ -1,21 +1,20 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: qcanvas
3
- Version: 1.2.2
3
+ Version: 2.0.0
4
4
  Summary: QCanvas is a desktop client for Canvas LMS.
5
5
  Author: QCanvas
6
6
  Author-email: QCanvas@noreply.codeberg.org
7
- Requires-Python: >3.11.0,<3.13
7
+ Requires-Python: >=3.12,<3.13
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Dist: aiofile (>=3.9.0,<4.0.0)
10
11
  Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
11
12
  Requires-Dist: asynctaskpool (>=0.2.1,<0.3.0)
12
- Requires-Dist: lightdb (>=2.0,<3.0)
13
+ Requires-Dist: cachetools (>=5.5.0,<6.0.0)
14
+ Requires-Dist: libqcanvas (>=0.5.1,<0.6.0)
13
15
  Requires-Dist: platformdirs (>=4.2.2,<5.0.0)
14
16
  Requires-Dist: pyqtdarktheme-fork (>=2.3.2,<3.0.0)
15
17
  Requires-Dist: qasync (>=0.27.1,<0.28.0)
16
- Requires-Dist: qcanvas-api-clients (>=0.4.0,<0.5.0)
17
- Requires-Dist: qcanvas-backend (>=0.3.0,<0.4.0)
18
- Requires-Dist: qtpy (>=2.4.1,<3.0.0)
19
18
  Requires-Dist: sqlalchemy (>=2.0.31,<3.0.0)
20
19
  Requires-Dist: validators (>=0.34.0,<0.35.0)
21
20
  Description-Content-Type: text/markdown
@@ -43,7 +42,7 @@ based distros.
43
42
 
44
43
  ## Prerequisites
45
44
 
46
- - Python 3.12+ (use [pyenv](https://github.com/pyenv/pyenv) if your distro does not have that version)
45
+ - Python 3.11+ (use [pyenv](https://github.com/pyenv/pyenv) if your distro does not have that version)
47
46
  - [Pipx](https://pipx.pypa.io/stable/) (optional)
48
47
  - Poetry (recommended to install using `pipx install poetry`)
49
48
  - [Appimagetool](https://github.com/AppImage/appimagetool) (Only for building the appimage)
@@ -57,15 +56,18 @@ cd QCanvasApp
57
56
  # Enter shell and run it
58
57
  poetry shell
59
58
  poetry install
60
- python qcanvas/run.py
59
+ python -m qcanvas
61
60
 
62
61
  # Alternatively you can run it like this:
63
62
  poetry install
64
- poetry run python qcanvas/run.py
63
+ poetry run python -m qcanvas
65
64
  ```
66
65
 
67
66
  ## Build custom AppImage
68
67
 
68
+ > [!INFO]
69
+ > This is not recommended as the appimage produced by this process isn't a proper appimage.
70
+
69
71
  ```bash
70
72
  bash build_appimage.sh
71
73
  ```
@@ -0,0 +1,91 @@
1
+ qcanvas/__init__.py,sha256=FSRKOgINpdkHq1lU0lzAKl6v6KmMYOyGcihFpf2HIQs,1719
2
+ qcanvas/app.py,sha256=NNxjdzFtRaeGk0cSOHKepwK7ONvHwmY_RcW0IVVzoIs,2747
3
+ qcanvas/backend_connectors/__init__.py,sha256=Wj8cmxQng3SSlmlXJyzHaPmvxbkauwsxINckPb7WuHc,108
4
+ qcanvas/backend_connectors/frontend_resource_manager.py,sha256=uzi8z2bNBdQoVkvtwxWo4qX5r0s13qmH5QL2t0vD0d8,1613
5
+ qcanvas/backend_connectors/qcanvas_task_master.py,sha256=G46WgM4LA9VGWUDe3cBfRqHEYrOqcM70UJ5ipwc8hm0,740
6
+ qcanvas/icons/__init__.py,sha256=RHBT64IkUdQQtSRRxsMU1uiYRPvUoTHJV01x9WXHepA,1965
7
+ qcanvas/icons/_icon_type.py,sha256=_Xq0qwz_4OsAOhT6_4lEWTFMsPSqKVRkmQjfXbJAeAE,904
8
+ qcanvas/icons/icons.qrc,sha256=ec-_Hiy2GOA0NOQCeyRpIhexTZk0_SSDxZlWSvSUgno,2084
9
+ qcanvas/icons/rc_icons.py,sha256=SZBFaQaLym2jkJ90csM-2W8TKWgBLMyRxnZUO0yAw_0,74285
10
+ qcanvas/settings/__init__.py,sha256=GPOgh9fzK0zGiJhIy7fWfXJZVE86i6DE-ZjQSqFunco,176
11
+ qcanvas/settings/_client_settings.py,sha256=6Oy5eXlttlwC11YSqbqLcgyVa5Nk3jBdB4Ea-gFC-yw,1397
12
+ qcanvas/settings/_course_settings.py,sha256=mNNPNt88f5q1m_YtoqaHDUqoAiRnVZj4wUGd0xDlcjY,1547
13
+ qcanvas/settings/_mapped_setting.py,sha256=a9wq60chtn9X8RLpQrf6JR7XsDxJlJfWrq0hfHHJvyU,1928
14
+ qcanvas/settings/_ui_settings.py,sha256=gyC_ZwPdu4JzZgA4I6TelJmm6pXginBigp58u-fBd3w,753
15
+ qcanvas/theme.py,sha256=7GVSpc1VCHxQiM7eamPNQJJKikVE8rejAsUFYOvsuaQ,3077
16
+ qcanvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ qcanvas/ui/course_viewer/__init__.py,sha256=XkoFnh4ULw3_i-GDsOlueEWido0PkoDcN9_EX6-nkXY,76
18
+ qcanvas/ui/course_viewer/content_tree.py,sha256=ewgi4oNN3uCURjykpLLRb2efzBxfFws14UAQ3H6z-64,4602
19
+ qcanvas/ui/course_viewer/course_tree/__init__.py,sha256=N2XorJ4COFHzRYx53noANb4_qJYf6N8_YCjPdQuDCHs,36
20
+ qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py,sha256=facgU1YPLpPy5C6EJG5KURuKTn7LOTu9ZmPEj-UQLFI,3080
21
+ qcanvas/ui/course_viewer/course_tree/course_tree.py,sha256=KaPVdvS2tPdMZFhggWCvg9DVSQugHS_EpGa12vgGNyw,3224
22
+ qcanvas/ui/course_viewer/course_viewer.py,sha256=AGZ6eQlRLb1i2ex-gbe2ygT3_sHYD7bvXy3tYQ2Jugc,4974
23
+ qcanvas/ui/course_viewer/tabs/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
24
+ qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py,sha256=w936dW7za10Fh6rN0zVA-7Kyiup3kd6C-mPAFHtxmy0,42
25
+ qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py,sha256=HAy6SsPTC-FpaaTWELS7lc5rgkB3Wm0m3DIHb3cvaaI,4850
26
+ qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=Lwj3Sq_W_XFV240VpLK6W3kIUQ00NH9qB88mAKsYHNs,2908
27
+ qcanvas/ui/course_viewer/tabs/constants.py,sha256=rUVEGSREV9vTFs4o3AD2OjaSFA-GPsmelxYWz0J8OP4,48
28
+ qcanvas/ui/course_viewer/tabs/content_tab.py,sha256=l2h1Vl7t0GAFYXzEjihMzpBv596sOu8BJKiMz1lcFAE,3274
29
+ qcanvas/ui/course_viewer/tabs/file_tab/__init__.py,sha256=mMebnrNw_V-lQ-eBXS_kFinL-ZdUAXa459NmuyuCu6s,30
30
+ qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py,sha256=SeKGKNRLmSmL5Ij96YSRx6Skl-1wC14uDfIPJy_xmzw,1398
31
+ qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py,sha256=ely6qZTs6CFsKv9ZFDCAe_8unosJ6nt9AQFLkcuknvE,3357
32
+ qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py,sha256=C1QtoY9GA2Q_lbUearVrdZbHCAefV4ZpsIPHGBAUxPI,1875
33
+ qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py,sha256=68iRUUWEP7mudbaxa4ZBKMra4rvs2oZKaZkBWwmUrsI,30
34
+ qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py,sha256=FGfRaEAcuLfcPo92_D7ZcyeIjePMbU_BSlpAtyT0qLo,2686
35
+ qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=w4dkC4V_fKsMkVB68vbfvMRI1HBxbuKzxExclAn-AaQ,1762
36
+ qcanvas/ui/course_viewer/tabs/page_tab/__init__.py,sha256=lcafxlSEVZ0wqZySxT6hTrvExX-GU2AfcZQbp6W8haU,30
37
+ qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py,sha256=-u6RaH7kO1cCrvcXNF0hcLrEL6nomy6IUXfKEGhANgg,743
38
+ qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=mxQzhjiiO7onQALhKbcyQQtsKEdBGAspQreXljRu9i4,2147
39
+ qcanvas/ui/course_viewer/tabs/resource_rich_browser.py,sha256=BlUHqoNKu5vzmLIYjUJZX6APxeNA2bia4xRZ3B0gaqc,6479
40
+ qcanvas/ui/course_viewer/tree_widget_data_item.py,sha256=z3CnXNFivtDtKemkH41E0a_Dc-l_tKJ6AEqNqM23eao,569
41
+ qcanvas/ui/memory_tree/__init__.py,sha256=-XLitM6teC0zmwPrGf-Q-A53-zgmIPASExdOtaLIvPU,107
42
+ qcanvas/ui/memory_tree/_tree_memory.py,sha256=kPsAw0sL5AFPIhWuhfKSS56043t9pQFi2YpBdYkbfm0,2231
43
+ qcanvas/ui/memory_tree/memory_tree_widget.py,sha256=NT8T9EMql7xxVRCQFkfb1p0h3gL7oQq9As2UGy_481k,4553
44
+ qcanvas/ui/memory_tree/memory_tree_widget_item.py,sha256=76a7IEZGXwYhqx7WMbq-tA_GvinvQW7hdueZNWAvFxg,435
45
+ qcanvas/ui/qcanvas_window/__init__.py,sha256=GeMsShFH9OGYLDDgB0KmKHlJr32NeewxurM-QzgabSQ,42
46
+ qcanvas/ui/qcanvas_window/course_viewer_container.py,sha256=oEPdqnGU752JTY0p4HfSlFyNDw-12AW4JzgxZ1A31J0,3502
47
+ qcanvas/ui/qcanvas_window/options/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
48
+ qcanvas/ui/qcanvas_window/options/auto_download_resources_option.py,sha256=oeVdUsFUSrCRKKjb6eyjsJecaMSMnEKR66L9FTlm_Eo,1379
49
+ qcanvas/ui/qcanvas_window/options/quick_sync_option.py,sha256=16zEFL__JRi-wE7Q99_Bqi-OHEBhRgf5QlnVUOUX3Eg,833
50
+ qcanvas/ui/qcanvas_window/options/sync_on_start_option.py,sha256=gL_FIulULN1uGBJJWgUszyjayQDw5WfppjoErbM3850,773
51
+ qcanvas/ui/qcanvas_window/options/theme_selection_menu.py,sha256=VMx79H45R7lcpMFpaN8eohbCxkQkq43MdH8W0nt2aTQ,1434
52
+ qcanvas/ui/qcanvas_window/qcanvas_window.py,sha256=VDgoBpldNAu_7Rj4BqT7KQOgcBN1Koq6c0s0uduumc8,8945
53
+ qcanvas/ui/qcanvas_window/status_bar_progress_display.py,sha256=v8cL7EzhKIO6YDo-IJGC2TvjnaHvHb9F2VyJtW0W4U8,5047
54
+ qcanvas/ui/qml_components/AttachmentsList.ui.qml,sha256=kEgdn1s490hfvMtDtbba54_-_vF-3GUIEVpB8D12ET0,483
55
+ qcanvas/ui/qml_components/AttachmentsListDelegate.qml,sha256=Frcd-vMd6lXBICF8SpPdpGJgxrk4vasvK29uTxXKAcs,2115
56
+ qcanvas/ui/qml_components/AttachmentsListModel.qml,sha256=y4pQoJLVrSAEdl8BkeKZrMBln0pCCfjotCCxcCS9L6w,516
57
+ qcanvas/ui/qml_components/AttachmentsPane.qml,sha256=50UElZB598m230dgBbDx9WDvK3uS5PvbcAhzSDIuIKQ,196
58
+ qcanvas/ui/qml_components/CommentsList.ui.qml,sha256=l8uqDrqIGVeOIAHnn4rwQQGiedFXAdMFANzvcHflCJo,477
59
+ qcanvas/ui/qml_components/CommentsListDelegate.ui.qml,sha256=iI-8Byw9dEOcbB18kQ6LGBH9IMCdmoK4Ak2WgSxkx78,3172
60
+ qcanvas/ui/qml_components/CommentsListModel.qml,sha256=oEffdqXiavoO4zcyBu8aqIlR-0IYDQKli3aKlGUxltg,2230
61
+ qcanvas/ui/qml_components/CommentsPane.qml,sha256=ZatxphygIA4aPJ5nEkEdU9SG03sOCqItaG3IewLJUBE,185
62
+ qcanvas/ui/qml_components/DarkTheme.qml,sha256=cc9NltNQp9vzZeKItJEXCBUBzvvfDTC6y8nL3gKWMTY,222
63
+ qcanvas/ui/qml_components/DecoratedText.ui.qml,sha256=U4p_P6gXRBj1GKDe0azJlp9-fT8yFw3XVjQH76L8MQ0,1003
64
+ qcanvas/ui/qml_components/LightTheme.qml,sha256=ItN-3U9WiCUThIOlvYwGkTukx-ceHTGxHBqEZve__is,172
65
+ qcanvas/ui/qml_components/Spacer.ui.qml,sha256=RE86ASh90JUV82QLWV9FoD3pVzv9F_gHFNyDpk_Befo,78
66
+ qcanvas/ui/qml_components/ThemedRectangle.qml,sha256=9EIBZInRCTrO8vEqYLlIsWxa88PYHmNTgc1nqKiO7hY,653
67
+ qcanvas/ui/qml_components/__init__.py,sha256=ogGsI7ynuB0EhAeQRH7VJeJIYYwMza_AzFzjPlBmsVc,136
68
+ qcanvas/ui/qml_components/attachments_pane.py,sha256=PwvITNo9xwGKvqGYp6OkMO7MAeMJjOm9H_DajZzfM_E,2366
69
+ qcanvas/ui/qml_components/comments_pane.py,sha256=IJkaR5cn4Ez08nh7V_iZEY56ojHcxRRPRLKgiZirslo,2993
70
+ qcanvas/ui/qml_components/qml_bridge_types.py,sha256=iHyG9ACG7bj8X_fty4Qpd5oSTmcW3zaFZdSi_KNXymE,2725
71
+ qcanvas/ui/qml_components/qml_pane.py,sha256=KNWFJ1--hRpDSCjiZB8yMWFxQdbZFGDemXP09KGFMMo,698
72
+ qcanvas/ui/setup/__init__.py,sha256=QWt2lEyLqWG5QC-BmCBlYyi0LZsBfsQYbP0XkvqA2f8,77
73
+ qcanvas/ui/setup/setup_checker.py,sha256=gU70geojUyOTW_fNgH-GzpTMrtYXsLHWf2wUnFaezmA,444
74
+ qcanvas/ui/setup/setup_dialog.py,sha256=_-52R6B8UIpKWRQ5jqpypgJLdnm_aXrZ5cKtyAXq0JA,10730
75
+ qcanvas/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
+ qcanvas/util/auto_downloader.py,sha256=DO2TCNQ2idUoITqlxxAp1tY8C5Zy52nghjCE4hv49aw,1905
77
+ qcanvas/util/basic_fonts.py,sha256=TqaY2UchDseHDZYB-Bh6r3xRoRuc5S9lf8j-fMGGCYQ,249
78
+ qcanvas/util/context_dict.py,sha256=05TlRto_fzmwdUBOVFEPVNCnch4gDYys1fpuMwLhpto,315
79
+ qcanvas/util/file_icons.py,sha256=TKHITkkd2HKAWR7mBJuXc034l76Ptp1YXM_eeLbEKW8,1509
80
+ qcanvas/util/html_cleaner.py,sha256=U-UbZVX9x85gatOvxLsQXe2hL6x4I86eUKwkm0p159E,710
81
+ qcanvas/util/layouts.py,sha256=in7V6mzJIlnVc1DbvhpxA5fIYBIUIQT-OPa9dHUYIrI,1498
82
+ qcanvas/util/logs.py,sha256=VZKFITiW2WR2POEFVv5GRpEXic23Pzjehry-vH3g3Gk,138
83
+ qcanvas/util/paths.py,sha256=lLqQqOmREefJB8D1xARH3gCNNXXlSELlIuDQaCHpDr4,1373
84
+ qcanvas/util/qurl_util.py,sha256=6nTk8VkQjT87wMOXyZucPBYdT6TTYzfCTElqsypLZPg,200
85
+ qcanvas/util/runtime.py,sha256=EO1HFCN7dZbQtKHQOILuvjZFXGT7SzhlmPvU8ma95Z8,617
86
+ qcanvas/util/ui_tools.py,sha256=cdwDrtljxIpzikvUt4CAMC06kogJ4WfhSTsC3lXAcz4,3575
87
+ qcanvas/util/url_checker.py,sha256=HkNFixp1ARwc_YjYkpHb-IMTyuaC7CylvYCgDyo3sh4,136
88
+ qcanvas-2.0.0.dist-info/METADATA,sha256=pXItdPg8BAwpQzOiLjtBKDtPHeiAQblNVnNGAly2whs,2118
89
+ qcanvas-2.0.0.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
90
+ qcanvas-2.0.0.dist-info/entry_points.txt,sha256=hqG_g3MRYh_eLyQcDN4H3EXqmBia_fnJTJ-3nqZGzvY,40
91
+ qcanvas-2.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.0.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ qcanvas=qcanvas:main
3
+
@@ -1,59 +0,0 @@
1
- import asyncio
2
- import logging
3
- import sys
4
-
5
- import qtpy
6
- from qasync import QEventLoop
7
- from qtpy.QtGui import QGuiApplication
8
- from qtpy.QtWidgets import QApplication
9
-
10
- import qcanvas.backend_connectors.qcanvas_task_master as task_master
11
- from qcanvas.ui.main_ui.qcanvas_window import QCanvasWindow
12
- from qcanvas.ui.setup import SetupDialog, setup_checker
13
- from qcanvas.util import runtime, settings, themes
14
-
15
- main_window = None
16
- setup_window = None
17
-
18
- _logger = logging.getLogger(__name__)
19
-
20
-
21
- def _show_main():
22
- global main_window
23
- main_window = QCanvasWindow()
24
- main_window.show()
25
-
26
-
27
- def _show_qt_api_name():
28
- print(f"Using Qt bindings from {qtpy.API_NAME}")
29
- _logger.info("Using Qt bindings from %s", qtpy.API_NAME)
30
-
31
-
32
- def launch():
33
- _show_qt_api_name()
34
-
35
- app = QApplication(sys.argv)
36
-
37
- if runtime.is_running_as_flatpak:
38
- QGuiApplication.setDesktopFileName("io.github.qcanvas.QCanvasApp")
39
-
40
- app.setApplicationName("QCanvas")
41
-
42
- task_master.register()
43
- themes.apply(settings.ui.theme)
44
-
45
- event_loop = QEventLoop(app)
46
- asyncio.set_event_loop(event_loop)
47
-
48
- app_close_event = asyncio.Event()
49
- app.aboutToQuit.connect(app_close_event.set)
50
-
51
- if setup_checker.needs_setup():
52
- setup_window = SetupDialog()
53
- setup_window.show()
54
- setup_window.closed.connect(_show_main)
55
- else:
56
- _show_main()
57
-
58
- with event_loop:
59
- event_loop.run_until_complete(app_close_event.wait())
@@ -1,89 +0,0 @@
1
- # This file regenerates icons.qrc, rc_icons.py and the icons definition file based on the icons in dark/, light/ and universal/
2
-
3
- import glob
4
- import logging
5
- import os
6
- import subprocess
7
- from pathlib import Path
8
-
9
- import xmltodict
10
-
11
- _logger = logging.getLogger(__name__)
12
-
13
-
14
- def remove_base_directory(path: str | Path) -> Path:
15
- if isinstance(path, str):
16
- path = Path(path)
17
-
18
- return Path(*path.parts[1:])
19
-
20
-
21
- def generate_xml(all_icons: list[Path]) -> str:
22
- xml_dict = {
23
- "RCC": {
24
- "@version": "1.0",
25
- "qresource": {"@prefix": "icons/", "file": all_icons},
26
- }
27
- }
28
-
29
- return "<!DOCTYPE RCC>\n" + xmltodict.unparse(
30
- xml_dict, pretty=True, full_document=False
31
- )
32
-
33
-
34
- def generate_icon_defs(icons: list[Path]) -> str:
35
- icons_base_removed = [remove_base_directory(icon) for icon in icons]
36
- groups = group_icons_by_category(icons_base_removed)
37
- classes = [
38
- generate_group_class(group_name, icons) for group_name, icons in groups.items()
39
- ]
40
-
41
- return f"""from .rc_icons import qt_resource_data as _ # Without this, icon data will not be loaded
42
- from qtpy.QtGui import QIcon
43
- from ._icon_type import UniversalIcon, ThemeIcon
44
-
45
- """ + "\n\n".join(
46
- classes
47
- )
48
-
49
-
50
- def group_icons_by_category(icons: list[Path]) -> dict[str, set[Path]]:
51
- groups: dict[str, set[Path]] = {}
52
-
53
- for icon in icons:
54
- icon_category = icon.parts[0]
55
- groups.setdefault(icon_category, set())
56
- groups[icon_category].add(icon)
57
-
58
- return groups
59
-
60
-
61
- def generate_group_class(group_name: str, icons: set[Path]) -> str:
62
- # pascal_case_class_name = "".join([part.title() for part in group_name.split("_")])
63
- lines = ["# noinspection PyPep8Naming", f"class {group_name}:"]
64
-
65
- for icon in sorted(icons):
66
- path_no_ext = os.path.splitext(icon)[0]
67
- lines.append(f' {icon.stem} = QIcon.fromTheme("{path_no_ext}")')
68
-
69
- return "\n".join(lines)
70
-
71
-
72
- def run_rcc() -> None:
73
- subprocess.run(["pyside6-rcc", "icons.qrc", "-o", "rc_icons.py"])
74
-
75
-
76
- def write(file_name: str, content: str) -> None:
77
- with open(file_name, "w") as f:
78
- f.write(content)
79
-
80
-
81
- if __name__ == "__main__":
82
- all_icons: list[Path] = [
83
- Path(file) for file in glob.glob("**/*.svg", recursive=True)
84
- ]
85
- all_icons.sort()
86
-
87
- write("icons.qrc", generate_xml(all_icons))
88
- write("__init__.py", generate_icon_defs(all_icons))
89
- run_rcc()
@@ -1,3 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
2
- <path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>
3
- </svg>
@@ -1,3 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
2
- <path d="M120-280v-80h560v80H120Zm80-160v-80h560v80H200Zm80-160v-80h560v80H280Z"/>
3
- </svg>