qcanvas 1.2.1__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 +5 -5
  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 +42 -36
  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.1.dist-info → qcanvas-2.0.0.dist-info}/METADATA +13 -11
  73. qcanvas-2.0.0.dist-info/RECORD +91 -0
  74. {qcanvas-1.2.1.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.1.dist-info/RECORD +0 -118
  129. qcanvas-1.2.1.dist-info/entry_points.txt +0 -3
  130. /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
@@ -0,0 +1,118 @@
1
+
2
+
3
+ /*
4
+ This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
5
+ It is supposed to be strictly declarative and only uses a subset of QML. If you edit
6
+ this file manually, you might introduce QML code that is not supported by Qt Design Studio.
7
+ Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
8
+ */
9
+ import QtQuick
10
+ import QtQuick.Controls
11
+
12
+ Control {
13
+ padding: 15
14
+ topInset: 5
15
+ bottomInset: 5
16
+ background: Rectangle {
17
+ color: palette.midlight
18
+ radius: 4
19
+ border.color: palette.dark
20
+ }
21
+ clip: true
22
+ height: delegate.height + padding * 2
23
+ anchors {
24
+ left: view.contentItem.left
25
+ right: view.contentItem.right
26
+ }
27
+
28
+ contentItem: Item {
29
+ id: delegate
30
+ height: column.height
31
+ anchors {
32
+ left: parent.left
33
+ right: parent.right
34
+ margins: padding
35
+ }
36
+
37
+ Column {
38
+ id: column
39
+ anchors {
40
+ left: parent.left
41
+ right: parent.right
42
+ }
43
+
44
+ Item {
45
+ height: authorText.height
46
+ anchors {
47
+ left: parent.left
48
+ right: parent.right
49
+ }
50
+
51
+ Text {
52
+ id: authorText
53
+ text: modelData.author
54
+ clip: true
55
+ color: palette.text
56
+
57
+ font {
58
+ pointSize: 12
59
+ bold: true
60
+ }
61
+ anchors {
62
+ left: parent.left
63
+ }
64
+ }
65
+
66
+ Text {
67
+ id: commentDate
68
+ text: modelData.date
69
+ verticalAlignment: Text.AlignVCenter
70
+ horizontalAlignment: Text.AlignRight
71
+ color: palette.text
72
+ clip: true
73
+
74
+ anchors {
75
+ left: authorText.right
76
+ right: parent.right
77
+ top: parent.top
78
+ bottom: parent.bottom
79
+ leftMargin: 5
80
+ }
81
+ }
82
+ }
83
+
84
+ Spacer {
85
+ size: 10
86
+ }
87
+
88
+ DecoratedText {
89
+ text: modelData.body
90
+ lineWidth: 2
91
+ anchors {
92
+ left: parent.left
93
+ right: parent.right
94
+ }
95
+ content {
96
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
97
+ }
98
+ }
99
+
100
+ Spacer {
101
+ size: 10
102
+ visible: modelData.attachments.length > 0
103
+ }
104
+
105
+ AttachmentsList {
106
+ id: attachmentsList
107
+ height: contentHeight
108
+ model: modelData.attachments
109
+ interactive: false
110
+ visible: modelData.attachments.length > 0
111
+ anchors {
112
+ left: parent.left
113
+ right: parent.right
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,56 @@
1
+ import QtQuick
2
+
3
+ ListModel {
4
+ ListElement {
5
+ body: "This is a comment. Gaze at it. Be amazed."
6
+ author: "I am Steve"
7
+ date: "2001-01-01 36:00"
8
+ attachments: [
9
+ ListElement {
10
+ file_name: "texas.pdf"
11
+ resource_id: "1"
12
+ download_state: "NOT_DOWNLOADED"
13
+ },
14
+ ListElement {
15
+ file_name: "oh_no_what_a_terribly_long_file_name_its_not_like_someone_would_actually_do_this.pdf"
16
+ resource_id: "2"
17
+ download_state: "FAILED"
18
+ },
19
+ ListElement {
20
+ file_name: "i was transported to another world where javascript doesn't exist.cbz"
21
+ resource_id: "3"
22
+ download_state: "DOWNLOADED"
23
+ }
24
+ ]
25
+ }
26
+ ListElement {
27
+ body: "If Morbius has a million fans, I am one of them.\nIf Morbius has 5 fans, I am one of them.\nIf Morbius has one fan, That one is me.\nIf Morbius has no fans, I am no longer alive.\nIf the world is against Morbius, I am against the world.\nTill my last breath, I'll love Morbius (2022)."
28
+ author: "Goku"
29
+ date: "1942-16-34 06:63"
30
+ attachments: [
31
+ ListElement {
32
+ file_name: "morbius_x264_1080.mkv"
33
+ resource_id: "blahblah"
34
+ download_state: "NOT_DOWNLOADED"
35
+ }
36
+ ]
37
+ }
38
+ ListElement {
39
+ body: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
40
+ author: "Word Wrap Test"
41
+ date: "2000-02-02"
42
+ attachments: []
43
+ }
44
+ ListElement {
45
+ body: "x264 forms the core of many web video services, such as Youtube, Facebook, Vimeo, and Hulu. It is widely used by television broadcasters and ISPs."
46
+ author: "BideoJAN Wordsblahblah"
47
+ date: "2027-07-28"
48
+ attachments: [
49
+ ListElement {
50
+ file_name: "Ass1_SOMEDUDE_48.pdf"
51
+ resource_id: "blah_v345"
52
+ download_state: "DOWNLOADED"
53
+ }
54
+ ]
55
+ }
56
+ }
@@ -0,0 +1,12 @@
1
+ import QtQuick
2
+
3
+ ThemedRectangle {
4
+ anchors.fill: parent
5
+ color: theme.base
6
+
7
+ CommentsList {
8
+ anchors.fill: parent
9
+ model: comments
10
+ palette: theme
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import QtQuick
2
+
3
+ Palette {
4
+ id: darkTheme
5
+ base: "#202124"
6
+ window: "#f8f9fa"
7
+ midlight: "#202124"
8
+ accent: "#e02424"
9
+ link: accent
10
+ dark: "#3f4042"
11
+ text: "#e4e7eb"
12
+ }
@@ -0,0 +1,44 @@
1
+ import QtQuick
2
+ import QtQuick.Controls
3
+ import QtQuick.Layouts
4
+
5
+ Item {
6
+ property alias text: content.text
7
+ property int lineWidth: 3
8
+ property color lineColour: palette.accent
9
+ property alias content: content
10
+
11
+ Layout.fillWidth: true
12
+ height: content.contentHeight
13
+ clip: true
14
+
15
+ Item {
16
+ anchors.fill: parent
17
+ height: content.contentHeight
18
+
19
+ Rectangle {
20
+ id: decoration
21
+ width: lineWidth
22
+ color: lineColour
23
+ radius: 1
24
+ anchors {
25
+ left: parent.left
26
+ top: parent.top
27
+ bottom: parent.bottom
28
+ }
29
+ }
30
+
31
+ Text/*Edit*/ {
32
+ id: content
33
+ Layout.fillWidth: true
34
+ color: palette.text
35
+ //readOnly: true
36
+ //textFormat: TextEdit.AutoText
37
+ anchors {
38
+ left: decoration.right
39
+ leftMargin: 10
40
+ right: parent.right
41
+ }
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,11 @@
1
+ import QtQuick
2
+
3
+ Palette {
4
+ base: "#f8f9fa"
5
+ window: "#f8f9fa"
6
+ midlight: "#f8f9fa"
7
+ accent: "#e02424"
8
+ link: accent
9
+ dark: "#dadce0"
10
+ text: "#4d5157"
11
+ }
@@ -0,0 +1,7 @@
1
+ import QtQuick
2
+
3
+ Item {
4
+ property real size
5
+ height: size
6
+ width: 1
7
+ }
@@ -0,0 +1,37 @@
1
+ import QtQuick
2
+
3
+ Rectangle {
4
+ property Palette theme: getTheme()
5
+
6
+ function getTheme() {
7
+ if (appTheme.theme === "native")
8
+ return palette
9
+ else if (appTheme.dark_mode === true)
10
+ return darkTheme
11
+ else
12
+ return lightTheme
13
+ }
14
+
15
+ LightTheme {
16
+ id: lightTheme
17
+ }
18
+ DarkTheme {
19
+ id: darkTheme
20
+ }
21
+
22
+ Connections {
23
+ target: appTheme
24
+
25
+ function onThemeChanged() {
26
+ updateTheme()
27
+ }
28
+
29
+ function onDarkModeChanged() {
30
+ updateTheme()
31
+ }
32
+
33
+ function updateTheme() {
34
+ theme = getTheme()
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,3 @@
1
+ from .comments_pane import CommentsPane
2
+ from .attachments_pane import AttachmentsPane
3
+ from .qml_bridge_types import Attachment, Comment
@@ -0,0 +1,72 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtCore import Slot
4
+ from PySide6.QtWidgets import QWidget
5
+ from qasync import asyncSlot
6
+
7
+ from qcanvas.backend_connectors import FrontendResourceManager
8
+ from .qml_bridge_types import Attachment
9
+ from .qml_pane import QmlPane
10
+ from libqcanvas import db
11
+ import logging
12
+
13
+ _logger = logging.getLogger(__name__)
14
+
15
+
16
+ class AttachmentsPane(QmlPane):
17
+ def __init__(
18
+ self, downloader: FrontendResourceManager, parent: QWidget | None = None
19
+ ):
20
+ super().__init__(Path(__file__).parent / "AttachmentsPane.qml", parent)
21
+
22
+ self._original_dock_name = None
23
+ self._downloader = downloader
24
+ self._files: dict[str, db.Resource] = {}
25
+ self._qfiles: dict[str, Attachment] = {}
26
+ self.ctx["submission_files"] = []
27
+ self.load_view()
28
+
29
+ self._downloader.download_finished.connect(self._download_updated)
30
+ self._downloader.download_failed.connect(self._download_updated)
31
+
32
+ def clear_files(self):
33
+ self.ctx["submission_files"] = []
34
+ self._files.clear()
35
+ self._qfiles.clear()
36
+
37
+ def load_files(self, files: list[db.Resource]):
38
+ qfiles = []
39
+
40
+ if self._original_dock_name is None:
41
+ self._original_dock_name = self.parent().windowTitle()
42
+
43
+ self.parent().setWindowTitle(f"{self._original_dock_name} ({len(files)})")
44
+
45
+ for file in files:
46
+ qfile = Attachment(
47
+ file_name=file.file_name,
48
+ resource_id=file.id,
49
+ download_state=file.download_state,
50
+ )
51
+ qfile.opened.connect(self._on_attachment_opened)
52
+ qfiles.append(qfile)
53
+
54
+ self._qfiles[file.id] = qfile
55
+ self._files[file.id] = file
56
+
57
+ self.ctx["submission_files"] = qfiles
58
+
59
+ @asyncSlot(str)
60
+ async def _on_attachment_opened(self, resource_id: str) -> None:
61
+ if resource_id in self._files:
62
+ await self._downloader.download_and_open(self._files[resource_id])
63
+ else:
64
+ _logger.warning(
65
+ "User opened an attachment that doesn't belong to any comment! id=%s",
66
+ resource_id,
67
+ )
68
+
69
+ @Slot(db.Resource)
70
+ def _download_updated(self, resource: db.Resource) -> None:
71
+ if resource.id in self._files:
72
+ self._qfiles[resource.id].download_state = resource.download_state
@@ -0,0 +1,85 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtCore import Signal, Slot
4
+ from PySide6.QtWidgets import QWidget
5
+ from libqcanvas.util import remove_unwanted_whitespaces, as_local
6
+ from qasync import asyncSlot
7
+
8
+ from qcanvas.backend_connectors import FrontendResourceManager
9
+ from .qml_bridge_types import Attachment, Comment
10
+ from libqcanvas import db
11
+ import logging
12
+ from .qml_pane import QmlPane
13
+
14
+ _logger = logging.getLogger(__name__)
15
+
16
+
17
+ class CommentsPane(QmlPane):
18
+ attachment_opened = Signal(str)
19
+
20
+ def __init__(
21
+ self, downloader: FrontendResourceManager, parent: QWidget | None = None
22
+ ):
23
+ super().__init__(Path(__file__).parent / "CommentsPane.qml", parent)
24
+ self._downloader = downloader
25
+ self._attachments: dict[str, db.Resource] = {}
26
+ self._qattachments: dict[str, Attachment] = {}
27
+
28
+ # Add context objects before we load the view
29
+ self.ctx["comments"] = []
30
+ self.load_view()
31
+
32
+ self._downloader.download_finished.connect(self._download_updated)
33
+ self._downloader.download_failed.connect(self._download_updated)
34
+
35
+ def clear_comments(self) -> None:
36
+ self.ctx["comments"] = []
37
+ self._attachments.clear()
38
+ self._qattachments.clear()
39
+
40
+ def load_comments(self, comments: list[db.SubmissionComment]) -> None:
41
+ qcomments = []
42
+
43
+ self.parent().setWindowTitle(f"Comments ({len(comments)})")
44
+
45
+ for comment in comments:
46
+ attachments = []
47
+
48
+ for attachment in comment.attachments:
49
+ qattachment = Attachment(
50
+ file_name=attachment.file_name,
51
+ resource_id=attachment.id,
52
+ download_state=attachment.download_state,
53
+ )
54
+ qattachment.opened.connect(self._on_attachment_opened)
55
+ attachments.append(qattachment)
56
+
57
+ self._attachments[attachment.id] = attachment
58
+ self._qattachments[attachment.id] = qattachment
59
+
60
+ qcomments.append(
61
+ Comment(
62
+ body=remove_unwanted_whitespaces(comment.body),
63
+ author=comment.author,
64
+ date=as_local(comment.creation_date).strftime("%Y-%m-%d %H:%M"),
65
+ attachments=attachments,
66
+ parent=self,
67
+ )
68
+ )
69
+
70
+ self.ctx["comments"] = qcomments
71
+
72
+ @asyncSlot(str)
73
+ async def _on_attachment_opened(self, resource_id: str) -> None:
74
+ if resource_id in self._attachments:
75
+ await self._downloader.download_and_open(self._attachments[resource_id])
76
+ else:
77
+ _logger.warning(
78
+ "User opened an attachment that doesn't belong to any comment! id=%s",
79
+ resource_id,
80
+ )
81
+
82
+ @Slot(db.Resource)
83
+ def _download_updated(self, resource: db.Resource) -> None:
84
+ if resource.id in self._qattachments:
85
+ self._qattachments[resource.id].download_state = resource.download_state
@@ -0,0 +1,95 @@
1
+ from PySide6.QtCore import Property, QObject, Signal
2
+ from PySide6.QtQml import ListProperty, QmlElement
3
+ from libqcanvas.database.tables import ResourceDownloadState
4
+
5
+
6
+ QML_IMPORT_NAME = "QCanvas"
7
+ QML_IMPORT_MAJOR_VERSION = 1
8
+
9
+
10
+ @QmlElement
11
+ class Attachment(QObject):
12
+ file_name_changed = Signal()
13
+ resource_id_changed = Signal()
14
+ download_state_changed = Signal()
15
+
16
+ # Emitted by AttachmentsListDelegate when the user clicks on an attachment
17
+ opened = Signal(str)
18
+
19
+ def __init__(
20
+ self,
21
+ file_name: str,
22
+ resource_id: str,
23
+ download_state: ResourceDownloadState,
24
+ parent: QObject | None = None,
25
+ ):
26
+ super().__init__(parent)
27
+ self._file_name = file_name
28
+ self._resource_id = resource_id
29
+ self._download_state = download_state.name
30
+
31
+ @Property(str, notify=file_name_changed)
32
+ def file_name(self) -> str:
33
+ return self._file_name
34
+
35
+ @Property(str, notify=resource_id_changed)
36
+ def resource_id(self) -> str:
37
+ return self._resource_id
38
+
39
+ @Property(str, notify=download_state_changed)
40
+ def download_state(self) -> str:
41
+ return self._download_state
42
+
43
+ @download_state.setter
44
+ def download_state(self, value: ResourceDownloadState):
45
+ if value.name != self._download_state:
46
+ self._download_state = value.name
47
+ self.download_state_changed.emit()
48
+
49
+
50
+ @QmlElement
51
+ class Comment(QObject):
52
+ body_changed = Signal()
53
+ author_changed = Signal()
54
+ date_changed = Signal()
55
+ attachments_changed = Signal()
56
+
57
+ def __init__(
58
+ self,
59
+ body: str,
60
+ author: str,
61
+ date: str,
62
+ attachments: list[Attachment],
63
+ parent: QObject | None = None,
64
+ ):
65
+ super().__init__(parent)
66
+
67
+ self._body = body
68
+ self._author = author
69
+ self._date = date
70
+ self._attachments = attachments
71
+
72
+ @Property(str, notify=body_changed)
73
+ def body(self) -> str:
74
+ return self._body
75
+
76
+ @Property(str, notify=date_changed)
77
+ def date(self) -> str:
78
+ return self._date
79
+
80
+ @Property(str, notify=author_changed)
81
+ def author(self) -> str:
82
+ return self._author
83
+
84
+ def attachment(self, n) -> Attachment:
85
+ return self._attachments[n]
86
+
87
+ def attachment_count(self) -> int:
88
+ return len(self._attachments)
89
+
90
+ # You must set `count`, `at` and `notify` EXPLICTLY (even if you name them according to the examples, which examples are all inconsistent and wrong).
91
+ # Qt?? Are you ok???
92
+ # Oh and you have to use `.length` instead of `.count` on the QML side because javascript.
93
+ attachments = ListProperty(
94
+ Attachment, count=attachment_count, at=attachment, notify=attachments_changed
95
+ )
@@ -0,0 +1,22 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtQuick import QQuickView
4
+ from PySide6.QtWidgets import QGroupBox, QWidget
5
+
6
+ from qcanvas.theme import app_theme
7
+ from qcanvas.util.context_dict import ContextDict
8
+ import qcanvas.util.ui_tools as ui
9
+
10
+
11
+ class QmlPane(QGroupBox):
12
+ def __init__(self, qml_path: Path, parent: QWidget | None = None):
13
+ super().__init__(parent)
14
+ self.qview = QQuickView(parent)
15
+ self._qml_path = qml_path
16
+ self.ctx = ContextDict(self.qview.rootContext())
17
+ self.ctx["appTheme"] = app_theme
18
+
19
+ self.setLayout(ui.hbox(QWidget.createWindowContainer(self.qview, self)))
20
+
21
+ def load_view(self):
22
+ self.qview.setSource(str(self._qml_path))
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
 
3
- import qcanvas.util.settings as settings
3
+ import qcanvas.settings as settings
4
4
  from qcanvas.util.url_checker import is_url
5
5
 
6
6
  _logger = logging.getLogger(__name__)
@@ -2,15 +2,26 @@ import logging
2
2
  from threading import Semaphore
3
3
  from typing import Optional
4
4
 
5
+ from libqcanvas_clients.canvas import CanvasClient, CanvasClientConfig
6
+ from libqcanvas_clients.panopto import PanoptoClient, PanoptoClientConfig
7
+ from libqcanvas_clients.util.request_exceptions import ConfigInvalidError
8
+ from PySide6.QtCore import Qt, QUrl, Signal, Slot
9
+ from PySide6.QtGui import QDesktopServices, QIcon
10
+ from PySide6.QtWidgets import (
11
+ QCheckBox,
12
+ QDialog,
13
+ QDialogButtonBox,
14
+ QErrorMessage,
15
+ QLabel,
16
+ QLineEdit,
17
+ QMessageBox,
18
+ QProgressBar,
19
+ QVBoxLayout,
20
+ QWidget,
21
+ )
5
22
  from qasync import asyncSlot
6
- from qcanvas_api_clients.canvas import CanvasClient, CanvasClientConfig
7
- from qcanvas_api_clients.panopto import PanoptoClient, PanoptoClientConfig
8
- from qcanvas_api_clients.util.request_exceptions import ConfigInvalidError
9
- from qtpy.QtCore import Qt, QUrl, Signal, Slot
10
- from qtpy.QtGui import QDesktopServices, QIcon
11
- from qtpy.QtWidgets import *
12
-
13
- import qcanvas.util.settings as settings
23
+
24
+ import qcanvas.settings as settings
14
25
  from qcanvas import icons
15
26
  from qcanvas.util.layouts import GridItem, grid_layout_widget, layout
16
27
  from qcanvas.util.url_checker import is_url
@@ -113,9 +124,11 @@ class SetupDialog(QDialog):
113
124
  is_password=True,
114
125
  )
115
126
  self._disable_panopto_checkbox = QCheckBox("Continue without Panopto")
127
+ self._disable_panopto_checkbox.setChecked(settings.client.panopto_disabled)
116
128
  self._disable_panopto_checkbox.checkStateChanged.connect(
117
129
  self._disable_panopto_check_changed
118
130
  )
131
+ self._panopto_url_box.enabled = not settings.client.panopto_disabled
119
132
  self._button_box = self._setup_button_box()
120
133
  self._waiting_indicator = self._setup_progress_bar()
121
134
 
@@ -254,7 +267,9 @@ class SetupDialog(QDialog):
254
267
  QMessageBox.StandardButton.Ok,
255
268
  self,
256
269
  )
257
- msg.accepted.connect(self._open_panopto_login)
270
+ msg.accepted.connect(
271
+ self._open_panopto_login, Qt.ConnectionType.SingleShotConnection
272
+ )
258
273
  msg.show()
259
274
 
260
275
  @Slot()
@@ -288,7 +303,9 @@ class SetupDialog(QDialog):
288
303
  "Don't share this key. You can revoke it at any time.",
289
304
  parent=self,
290
305
  )
291
- msg.accepted.connect(self._open_tutorial)
306
+ msg.accepted.connect(
307
+ self._open_tutorial, Qt.ConnectionType.SingleShotConnection
308
+ )
292
309
  msg.show()
293
310
 
294
311
  @Slot()