Warning, /education/kwordquiz/src/qml/DeckEditorPage.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu> 0002 // SPDX-License-Identifier: LGPL-2.0-or-later 0003 0004 import QtQuick 2.15 0005 import QtQuick.Layouts 1.15 0006 import QtQuick.Controls 2.15 as QQC2 0007 import Qt.labs.platform 1.1 0008 import org.kde.kirigami 2.20 as Kirigami 0009 import org.kde.kwordquiz 1.0 0010 import org.kde.kitemmodels 1.0 0011 0012 Kirigami.ScrollablePage { 0013 id: root 0014 0015 property DocumentModel documentModel 0016 property int mode: DeckEditorPage.CreateMode 0017 property CardModel editorModel: CardModel {} 0018 property string filterText: '' 0019 0020 readonly property bool editPage: true 0021 0022 enum Mode { 0023 EditMode, 0024 CreateMode 0025 } 0026 0027 Component.onCompleted: if (mode === DeckEditorPage.CreateMode) { 0028 root.editorModel.createNew(); 0029 } 0030 0031 title: if (mode === DeckEditorPage.CreateMode) { 0032 i18nc("@title:window", "Create Deck") 0033 } else { 0034 i18nc("@title:window", "Edit Deck") 0035 } 0036 0037 actions: [ 0038 Kirigami.Action { 0039 text: i18n("Filter") 0040 displayComponent: Kirigami.SearchField { 0041 focus: false 0042 placeholderText: i18n("Filter...") 0043 onTextChanged: { 0044 root.filterText = text; 0045 filterProxy.invalidate(); 0046 } 0047 } 0048 }, 0049 Kirigami.Action { 0050 text: i18nc("@action:button", "Print Preview") 0051 icon.name: "document-print-preview" 0052 0053 Kirigami.Action { 0054 text: i18n("Flashcard") 0055 icon.name: "org.kde.kwordquiz" 0056 onTriggered: exporter.printPreview(Exporter.Flashcard) 0057 } 0058 0059 Kirigami.Action { 0060 text: i18n("List") 0061 icon.name: "view-list-text" 0062 onTriggered: exporter.printPreview(Exporter.List) 0063 } 0064 0065 Kirigami.Action { 0066 text: i18n("Exam") 0067 icon.name: "table" 0068 onTriggered: exporter.printPreview(Exporter.Exam) 0069 } 0070 }, 0071 Kirigami.Action { 0072 text: i18nc("@action:button", "Print") 0073 icon.name: "document-print" 0074 0075 Kirigami.Action { 0076 text: i18n("Flashcard") 0077 onTriggered: exporter.print(Exporter.Flashcard) 0078 icon.name: "org.kde.kwordquiz" 0079 } 0080 0081 Kirigami.Action { 0082 text: i18n("List") 0083 icon.name: "view-list-text" 0084 onTriggered: exporter.print(Exporter.List) 0085 } 0086 0087 Kirigami.Action { 0088 text: i18n("Exam") 0089 icon.name: "table" 0090 onTriggered: exporter.print(Exporter.Exam) 0091 } 0092 }, 0093 Kirigami.Action { 0094 text: mode === DeckEditorPage.CreateMode ? i18nc("@action:button", "Create") : i18nc("@action:button", "Save") 0095 icon.name: "document-save" 0096 onTriggered: { 0097 root.editorModel.save(); 0098 if (mode === DeckEditorPage.CreateMode) { 0099 root.documentModel.add(root.editorModel.document); 0100 } else { 0101 root.editorModel.reloaded(); 0102 } 0103 applicationWindow().pageStack.layers.pop(); 0104 } 0105 } 0106 ] 0107 0108 component FileSelectorButton: QQC2.ToolButton { 0109 id: fileButton 0110 0111 required property string file 0112 required property bool isImage 0113 readonly property bool isSound: !isImage 0114 0115 signal fileRemoved() 0116 signal fileAdded(url file) 0117 0118 icon.name: file ? "delete" : (isImage ? "insert-image" : "folder-music") 0119 text: if (isImage) { 0120 file ? i18nc("@action:button", "Remove Image") : i18nc("@action:button", "Link Image") 0121 } else { 0122 file ? i18nc("@action:button", "Remove Sound") : i18nc("@action:button", "Link Sound") 0123 } 0124 display: QQC2.ToolButton.IconOnly 0125 0126 QQC2.ToolTip.text: text 0127 QQC2.ToolTip.visible: hovered 0128 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay 0129 0130 onClicked: { 0131 if (file) { 0132 fileButton.fileRemoved(); 0133 return; 0134 } 0135 0136 var dialog = isImage 0137 ? imageFileDialog.createObject(QQC2.ApplicationWindow.Overlay) 0138 : soundFileDialog.createObject(QQC2.ApplicationWindow.Overlay) 0139 0140 dialog.accepted.connect(() => { 0141 if (!dialog.file) { 0142 return; 0143 } 0144 fileButton.fileAdded(dialog.file); 0145 }); 0146 dialog.open(); 0147 } 0148 } 0149 0150 component LanguageSelectorButton: QQC2.ToolButton { 0151 id: button 0152 0153 property string language 0154 0155 QQC2.ToolTip.text: i18nc("@action:button", "Set language") 0156 QQC2.ToolTip.visible: hovered 0157 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay 0158 0159 font.family: language.length > 0 ? "emoji" : Kirigami.Theme.defaultFont.family 0160 0161 text: language.length > 0 ? LanguageListModel.flagFromName(language) : i18nc("@action:button", "Set Language") 0162 display: language.length > 0 ? QQC2.ToolButton.TextOnly : QQC2.ToolButton.IconOnly 0163 icon.name: language.length > 0 ? "" : "language-chooser" 0164 0165 onClicked: { 0166 var page = applicationWindow().pageStack.pushDialogLayer(languageSelectorPage, { 0167 width: Kirigami.Units.gridUnits * 10, 0168 height: Kirigami.Units.gridUnits * 12, 0169 language: button.language, 0170 }); 0171 0172 page.languageChanged.connect(() => { 0173 button.language = page.language; 0174 }); 0175 } 0176 } 0177 0178 Component { 0179 id: imageFileDialog 0180 0181 FileDialog { 0182 title: i18n("Please Choose an Image") 0183 folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) 0184 } 0185 } 0186 0187 Component { 0188 id: soundFileDialog 0189 0190 FileDialog { 0191 title: i18n("Please Choose a Sound") 0192 folder: StandardPaths.writableLocation(StandardPaths.MusicLocation) 0193 } 0194 } 0195 0196 Component { 0197 id: languageSelectorPage 0198 0199 LanguageSelectorPage {} 0200 } 0201 0202 ListView { 0203 id: listView 0204 0205 header: QQC2.Pane { 0206 width: parent.width 0207 0208 Kirigami.Theme.colorSet: Kirigami.Theme.Window 0209 Kirigami.Theme.inherit: false 0210 0211 function focusQuestionField() { 0212 identifierLeftField.forceActiveFocus(); 0213 } 0214 0215 function focusAnswerField() { 0216 identifierRightField.forceActiveFocus(); 0217 } 0218 0219 contentItem: ColumnLayout { 0220 GridLayout { 0221 Layout.fillWidth: true 0222 0223 columns: 2 0224 0225 QQC2.Label { 0226 text: i18nc("@label", "Name:") 0227 } 0228 0229 QQC2.TextField { 0230 Layout.fillWidth: true 0231 text: root.editorModel.title 0232 onTextChanged: root.editorModel.title = text; 0233 enabled: root.editorModel.enabled 0234 } 0235 0236 QQC2.Label { 0237 text: i18nc("@label", "Author:") 0238 } 0239 0240 QQC2.TextField { 0241 Layout.fillWidth: true 0242 text: root.editorModel.author 0243 onTextChanged: root.editorModel.author = text; 0244 enabled: root.editorModel.enabled 0245 } 0246 0247 QQC2.Label { 0248 text: i18nc("@label", "License:") 0249 } 0250 0251 QQC2.ComboBox { 0252 Layout.fillWidth: true 0253 editText: root.editorModel.license 0254 onCurrentIndexChanged: if (root.editorModel.document) { 0255 root.editorModel.license = currentIndex; 0256 } 0257 currentIndex: 0 0258 Component.onCompleted: if (root.editorModel.license.length === 0) { 0259 editText = currentValue; 0260 } 0261 enabled: root.editorModel.enabled 0262 editable: true 0263 model: [ 0264 'CC-BY-SA-4.0', 0265 'CC-BY-4.0', 0266 'CC0-1.0', 0267 ] 0268 } 0269 } 0270 0271 Kirigami.Separator { 0272 Layout.fillWidth: true 0273 } 0274 0275 RowLayout { 0276 Layout.fillWidth: true 0277 0278 QQC2.TextField { 0279 id: identifierLeftField 0280 text: root.editorModel.identifierLeft 0281 background: null 0282 onEditingFinished: root.editorModel.identifierLeft = text 0283 enabled: root.editorModel.enabled 0284 0285 Layout.fillWidth: true 0286 font.bold: true 0287 Keys.onDownPressed: { 0288 const item = listView.itemAtIndex(0); 0289 if (item) { 0290 item.focusQuestionField(); 0291 } 0292 } 0293 onAccepted: identifierRightField.forceActiveFocus() 0294 } 0295 0296 LanguageSelectorButton { 0297 id: identifierLeftLanguage 0298 onLanguageChanged: { 0299 editorModel.langQuestion = language; 0300 root.editorModel.identifierLeft = LanguageListModel.languageName(language); 0301 } 0302 } 0303 0304 Kirigami.Separator { 0305 Layout.fillHeight: true 0306 Layout.preferredWidth: 1 0307 } 0308 0309 QQC2.TextField { 0310 id: identifierRightField 0311 text: root.editorModel.identifierRight 0312 background: null 0313 onEditingFinished: root.editorModel.identifierRight = text 0314 enabled: root.editorModel.enabled 0315 0316 Layout.fillWidth: true 0317 font.bold: true 0318 Keys.onDownPressed: { 0319 const item = listView.itemAtIndex(0); 0320 if (item) { 0321 item.focusAnswerField(); 0322 } 0323 } 0324 0325 onAccepted: { 0326 const item = listView.itemAtIndex(0); 0327 if (item) { 0328 item.focusQuestionField(); 0329 } 0330 } 0331 } 0332 0333 LanguageSelectorButton { 0334 id: identifierRightLanguage 0335 onLanguageChanged: { 0336 editorModel.langAnswer = language; 0337 root.editorModel.identifierRight = LanguageListModel.languageName(language); 0338 } 0339 } 0340 } 0341 } 0342 } 0343 0344 footer: QQC2.ItemDelegate { 0345 id: footer 0346 0347 function insertRow() { 0348 if (newQuestionField.text.length === 0 || newAnswerField.text.length === 0) { 0349 return; 0350 } 0351 0352 root.editorModel.add(newQuestionField.text, newAnswerField.text); 0353 newQuestionField.text = ''; 0354 newAnswerField.text = ''; 0355 } 0356 0357 function focusQuestionField() { 0358 newQuestionField.forceActiveFocus(); 0359 } 0360 0361 function focusAnswerField() { 0362 newAnswerField.forceActiveFocus(); 0363 } 0364 0365 width: parent.width 0366 0367 contentItem: RowLayout { 0368 QQC2.TextField { 0369 id: newQuestionField 0370 background: null 0371 placeholderText: root.editorModel.identifierLeft 0372 enabled: root.editorModel.enabled 0373 onEditingFinished: footer.insertRow(); 0374 onAccepted: newAnswerField.forceActiveFocus(); 0375 0376 Layout.fillWidth: true 0377 Keys.onUpPressed: { 0378 const item = listView.itemAtIndex(listView.count - 1); 0379 if (item) { 0380 item.focusQuestionField(); 0381 } 0382 } 0383 } 0384 0385 Kirigami.Separator { 0386 Layout.fillHeight: true 0387 Layout.preferredWidth: 1 0388 } 0389 0390 QQC2.TextField { 0391 id: newAnswerField 0392 background: null 0393 placeholderText: root.editorModel.identifierRight 0394 onEditingFinished: footer.insertRow() 0395 onAccepted: newQuestionField.forceActiveFocus(); 0396 enabled: root.editorModel.enabled 0397 0398 Layout.fillWidth: true 0399 Keys.onUpPressed: { 0400 const item = listView.itemAtIndex(listView.count - 1); 0401 if (item) { 0402 item.focusAnswerField(); 0403 } 0404 } 0405 } 0406 } 0407 } 0408 0409 model: KSortFilterProxyModel { 0410 id: filterProxy 0411 sourceModel: root.editorModel 0412 filterRowCallback: function(sourceRow, sourceParent) { 0413 let question = sourceModel.data(sourceModel.index(sourceRow, 0, sourceParent), CardModel.QuestionRole); 0414 let answer = sourceModel.data(sourceModel.index(sourceRow, 0, sourceParent), CardModel.AnswerRole); 0415 0416 return question.includes(filterText) || answer.includes(filterText); 0417 } 0418 } 0419 0420 delegate: ColumnLayout { 0421 id: editorDelegate 0422 0423 required property int index 0424 0425 required property string question 0426 required property string questionImage 0427 required property string questionSound 0428 required property string answer 0429 required property string answerImage 0430 required property string answerSound 0431 0432 function focusQuestionField() { 0433 questionField.forceActiveFocus(); 0434 } 0435 0436 function focusAnswerField() { 0437 answerField.forceActiveFocus(); 0438 } 0439 0440 width: ListView.view.width 0441 spacing: 0 0442 0443 QQC2.ItemDelegate { 0444 Layout.fillWidth: true 0445 0446 contentItem: RowLayout { 0447 QQC2.TextField { 0448 id: questionField 0449 0450 text: editorDelegate.question 0451 background: null 0452 onEditingFinished: root.editorModel.edit(editorDelegate.index, questionField.text, answerField.text) 0453 0454 Layout.fillWidth: true 0455 Keys.onUpPressed: { 0456 const item = listView.itemAtIndex(editorDelegate.index - 1); 0457 if (item) { 0458 item.focusQuestionField(); 0459 } else { 0460 listView.headerItem.focusQuestionField(); 0461 } 0462 } 0463 Keys.onDownPressed: { 0464 const item = listView.itemAtIndex(editorDelegate.index + 1); 0465 if (item) { 0466 item.focusQuestionField(); 0467 } else { 0468 listView.footerItem.focusQuestionField(); 0469 } 0470 } 0471 onAccepted: answerField.forceActiveFocus() 0472 } 0473 0474 FileSelectorButton { 0475 isImage: true 0476 file: editorDelegate.questionImage 0477 onFileRemoved: root.editorModel.removeQuestionImage(editorDelegate.index); 0478 onFileAdded: file => root.editorModel.addQuestionImage(editorDelegate.index, file); 0479 } 0480 0481 FileSelectorButton { 0482 isImage: false 0483 file: editorDelegate.questionSound 0484 onFileRemoved: root.editorModel.removeQuestionSound(editorDelegate.index); 0485 onFileAdded: root.editorModel.addQuestionSound(editorDelegate.index, file); 0486 } 0487 0488 Kirigami.Separator { 0489 Layout.fillHeight: true 0490 Layout.preferredWidth: 1 0491 } 0492 0493 QQC2.TextField { 0494 id: answerField 0495 0496 text: editorDelegate.answer 0497 background: null 0498 onEditingFinished: root.editorModel.edit(editorDelegate.index, questionField.text, answerField.text) 0499 0500 Layout.fillWidth: true 0501 Keys.onUpPressed: { 0502 const item = listView.itemAtIndex(editorDelegate.index - 1); 0503 if (item) { 0504 item.focusAnswerField(); 0505 } else { 0506 root.listView.headerItem.focusAnswerField(); 0507 } 0508 } 0509 Keys.onDownPressed: { 0510 const item = listView.itemAtIndex(editorDelegate.index + 1); 0511 if (item) { 0512 item.focusAnswerField(); 0513 } else { 0514 listView.footerItem.focusAnswerField(); 0515 } 0516 } 0517 onAccepted: { 0518 const item = listView.itemAtIndex(editorDelegate.index + 1); 0519 if (item) { 0520 item.focusQuestionField(); 0521 } else { 0522 root.listView.headerItem.focusQuestionField(); 0523 } 0524 } 0525 0526 } 0527 0528 FileSelectorButton { 0529 isImage: true 0530 file: editorDelegate.answerImage 0531 onFileRemoved: root.editorModel.removeAnswerImage(editorDelegate.index); 0532 onFileAdded: root.editorModel.addAnswerImage(editorDelegate.index, file); 0533 } 0534 0535 FileSelectorButton { 0536 isImage: false 0537 file: editorDelegate.answerSound 0538 onFileRemoved: root.editorModel.removeAnswerSound(editorDelegate.index); 0539 onFileAdded: root.editorModel.addAnswerSound(editorDelegate.index, file); 0540 } 0541 } 0542 } 0543 0544 Kirigami.Separator { 0545 Layout.fillWidth: true 0546 } 0547 } 0548 } 0549 0550 Exporter { 0551 id: exporter 0552 cardModel: root.editorModel 0553 } 0554 }