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.
- qcanvas/__init__.py +54 -0
- qcanvas/app.py +93 -0
- qcanvas/backend_connectors/frontend_resource_manager.py +13 -5
- qcanvas/backend_connectors/qcanvas_task_master.py +2 -2
- qcanvas/icons/__init__.py +5 -5
- qcanvas/icons/_icon_type.py +1 -1
- qcanvas/icons/icons.qrc +47 -43
- qcanvas/icons/rc_icons.py +1298 -1197
- qcanvas/settings/__init__.py +6 -0
- qcanvas/{util/settings → settings}/_client_settings.py +4 -4
- qcanvas/settings/_course_settings.py +54 -0
- qcanvas/{util/settings → settings}/_mapped_setting.py +2 -5
- qcanvas/{util/settings → settings}/_ui_settings.py +4 -4
- qcanvas/theme.py +101 -0
- qcanvas/ui/course_viewer/content_tree.py +9 -12
- qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +3 -3
- qcanvas/ui/course_viewer/course_tree/course_tree.py +9 -8
- qcanvas/ui/course_viewer/course_viewer.py +35 -43
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -4
- qcanvas/ui/course_viewer/tabs/constants.py +1 -0
- qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
- qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +4 -4
- qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +7 -10
- qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +6 -7
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +7 -8
- qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +5 -5
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +15 -21
- qcanvas/ui/course_viewer/tree_widget_data_item.py +1 -1
- qcanvas/ui/memory_tree/_tree_memory.py +45 -42
- qcanvas/ui/memory_tree/memory_tree_widget.py +22 -18
- qcanvas/ui/memory_tree/memory_tree_widget_item.py +3 -3
- qcanvas/ui/qcanvas_window/__init__.py +1 -0
- qcanvas/ui/{main_ui → qcanvas_window}/course_viewer_container.py +10 -10
- qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +5 -5
- qcanvas/ui/{main_ui → qcanvas_window}/options/quick_sync_option.py +7 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/sync_on_start_option.py +7 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/theme_selection_menu.py +10 -10
- qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +38 -33
- qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +5 -6
- qcanvas/ui/qml_components/AttachmentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/AttachmentsListDelegate.qml +77 -0
- qcanvas/ui/qml_components/AttachmentsListModel.qml +19 -0
- qcanvas/ui/qml_components/AttachmentsPane.qml +12 -0
- qcanvas/ui/qml_components/CommentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/CommentsListDelegate.ui.qml +118 -0
- qcanvas/ui/qml_components/CommentsListModel.qml +56 -0
- qcanvas/ui/qml_components/CommentsPane.qml +12 -0
- qcanvas/ui/qml_components/DarkTheme.qml +12 -0
- qcanvas/ui/qml_components/DecoratedText.ui.qml +44 -0
- qcanvas/ui/qml_components/LightTheme.qml +11 -0
- qcanvas/ui/qml_components/Spacer.ui.qml +7 -0
- qcanvas/ui/qml_components/ThemedRectangle.qml +37 -0
- qcanvas/ui/qml_components/__init__.py +3 -0
- qcanvas/ui/qml_components/attachments_pane.py +72 -0
- qcanvas/ui/qml_components/comments_pane.py +85 -0
- qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
- qcanvas/ui/qml_components/qml_pane.py +22 -0
- qcanvas/ui/setup/setup_checker.py +1 -1
- qcanvas/ui/setup/setup_dialog.py +27 -10
- qcanvas/util/auto_downloader.py +9 -7
- qcanvas/util/basic_fonts.py +2 -2
- qcanvas/util/context_dict.py +12 -0
- qcanvas/util/file_icons.py +11 -19
- qcanvas/util/layouts.py +5 -7
- qcanvas/util/paths.py +17 -6
- qcanvas/util/qurl_util.py +1 -1
- qcanvas/util/ui_tools.py +118 -8
- qcanvas/util/url_checker.py +1 -1
- {qcanvas-1.2.2.dist-info → qcanvas-2.0.0.dist-info}/METADATA +12 -10
- qcanvas-2.0.0.dist-info/RECORD +91 -0
- {qcanvas-1.2.2.dist-info → qcanvas-2.0.0.dist-info}/WHEEL +1 -1
- qcanvas-2.0.0.dist-info/entry_points.txt +3 -0
- qcanvas/app_start/__init__.py +0 -59
- qcanvas/icons/_update_icons.py +0 -89
- qcanvas/icons/dark/actions/exit.svg +0 -3
- qcanvas/icons/dark/actions/mark_all_read.svg +0 -3
- qcanvas/icons/dark/actions/open_downloads.svg +0 -3
- qcanvas/icons/dark/actions/quick_login.svg +0 -3
- qcanvas/icons/dark/actions/sync.svg +0 -3
- qcanvas/icons/dark/branding/logo_transparent.svg +0 -303
- qcanvas/icons/dark/options/auto_download.svg +0 -3
- qcanvas/icons/dark/options/theme.svg +0 -3
- qcanvas/icons/dark/tabs/assignments.svg +0 -3
- qcanvas/icons/dark/tabs/mail.svg +0 -3
- qcanvas/icons/dark/tabs/pages.svg +0 -3
- qcanvas/icons/dark/tree_items/assignment.svg +0 -3
- qcanvas/icons/dark/tree_items/mail.svg +0 -3
- qcanvas/icons/dark/tree_items/module.svg +0 -3
- qcanvas/icons/dark/tree_items/page.svg +0 -3
- qcanvas/icons/light/actions/exit.svg +0 -3
- qcanvas/icons/light/actions/mark_all_read.svg +0 -3
- qcanvas/icons/light/actions/open_downloads.svg +0 -3
- qcanvas/icons/light/actions/quick_login.svg +0 -3
- qcanvas/icons/light/actions/sync.svg +0 -3
- qcanvas/icons/light/branding/logo_transparent.svg +0 -304
- qcanvas/icons/light/options/auto_download.svg +0 -3
- qcanvas/icons/light/options/ignore_old.svg +0 -3
- qcanvas/icons/light/options/include_videos.svg +0 -3
- qcanvas/icons/light/options/theme.svg +0 -3
- qcanvas/icons/light/tabs/assignments.svg +0 -3
- qcanvas/icons/light/tabs/mail.svg +0 -3
- qcanvas/icons/light/tabs/pages.svg +0 -3
- qcanvas/icons/light/tree_items/assignment.svg +0 -3
- qcanvas/icons/light/tree_items/mail.svg +0 -3
- qcanvas/icons/light/tree_items/module.svg +0 -3
- qcanvas/icons/light/tree_items/page.svg +0 -3
- qcanvas/icons/universal/branding/main_icon.svg +0 -325
- qcanvas/icons/universal/downloads/download_failed.svg +0 -23
- qcanvas/icons/universal/downloads/downloaded.svg +0 -23
- qcanvas/icons/universal/downloads/not_downloaded.svg +0 -23
- qcanvas/icons/universal/downloads/unknown.svg +0 -6
- qcanvas/icons/universal/tabs/assignments_new_content.svg +0 -3
- qcanvas/icons/universal/tabs/mail_new_content.svg +0 -3
- qcanvas/icons/universal/tabs/pages_new_content.svg +0 -3
- qcanvas/icons/universal/tree_items/semester.svg +0 -108
- qcanvas/run.py +0 -54
- qcanvas/ui/course_viewer/tabs/util.py +0 -11
- qcanvas/ui/main_ui/__init__.py +0 -0
- qcanvas/util/settings/__init__.py +0 -9
- qcanvas/util/themes/__init__.py +0 -2
- qcanvas/util/themes/_colour_scheme_helper.py +0 -38
- qcanvas/util/themes/_selected_theme.py +0 -10
- qcanvas/util/themes/_theme_changed_event.py +0 -17
- qcanvas/util/themes/_theme_changer.py +0 -86
- qcanvas-1.2.2.dist-info/RECORD +0 -118
- qcanvas-1.2.2.dist-info/entry_points.txt +0 -3
- /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,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,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,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))
|
qcanvas/ui/setup/setup_dialog.py
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
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(
|
|
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(
|
|
306
|
+
msg.accepted.connect(
|
|
307
|
+
self._open_tutorial, Qt.ConnectionType.SingleShotConnection
|
|
308
|
+
)
|
|
292
309
|
msg.show()
|
|
293
310
|
|
|
294
311
|
@Slot()
|