Warning, /utilities/krecorder/src/contents/ui/RecordingListPage.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im> 0003 * SPDX-FileCopyrightText: 2020-2022 Devin Lin <espidev@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-3.0-or-later 0006 */ 0007 0008 import QtCore 0009 import QtQuick 0010 import QtQuick.Controls as Controls 0011 import QtQuick.Layouts 0012 import QtQuick.Dialogs 0013 0014 import org.kde.kirigami as Kirigami 0015 0016 import KRecorder 0017 0018 import "components" 0019 0020 Kirigami.ScrollablePage { 0021 id: root 0022 title: i18n("Recordings") 0023 0024 property Recording currentRecordingToEdit 0025 property bool editMode 0026 0027 onEditModeChanged: { 0028 editAction.checked = editMode; 0029 } 0030 0031 implicitWidth: applicationWindow().isWidescreen ? Kirigami.Units.gridUnit * 8 : applicationWindow().width 0032 0033 actions: [ 0034 Kirigami.Action { 0035 id: editAction 0036 icon.name: "edit-entry" 0037 text: i18n("Edit") 0038 onTriggered: root.editMode = !root.editMode 0039 checkable: true 0040 visible: listView.count > 0 0041 }, 0042 Kirigami.Action { 0043 visible: !applicationWindow().isWidescreen 0044 icon.name: "settings-configure" 0045 text: i18n("Settings") 0046 onTriggered: applicationWindow().openSettings(); 0047 }, 0048 Kirigami.Action { 0049 visible: applicationWindow().isWidescreen 0050 icon.name: "microphone-sensitivity-high" 0051 text: i18n("Record") 0052 onTriggered: applicationWindow().openRecordScreen() 0053 } 0054 ] 0055 0056 function editRecordingDialog(recording) { 0057 editDialogName.text = recording.fileName; 0058 editDialogLocation.text = recording.filePath; 0059 currentRecordingToEdit = recording; 0060 editNameDialog.open(); 0061 } 0062 0063 function removeRecordingDialog(recording, index) { 0064 deleteDialog.toDelete = recording; 0065 deleteDialog.toDeleteIndex = index; 0066 deleteDialog.open(); 0067 } 0068 0069 ListView { 0070 id: listView 0071 model: RecordingModel 0072 0073 // show animation 0074 property real yTranslate: 0 0075 transform: Translate { y: listView.yTranslate } 0076 NumberAnimation on opacity { 0077 from: 0 0078 to: 1 0079 duration: Kirigami.Units.longDuration * 2 0080 easing.type: Easing.InOutQuad 0081 running: true 0082 } 0083 NumberAnimation { 0084 from: Kirigami.Units.gridUnit * 3 0085 to: 0 0086 duration: Kirigami.Units.longDuration * 3 0087 easing.type: Easing.OutQuint 0088 property: "yTranslate" 0089 target: listView 0090 running: true 0091 } 0092 0093 // prevent default highlight 0094 currentIndex: -1 0095 0096 add: Transition { 0097 NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration } 0098 } 0099 remove: Transition { 0100 NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration } 0101 } 0102 displaced: Transition { 0103 NumberAnimation { properties: "x,y"; duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad} 0104 } 0105 0106 Kirigami.PlaceholderMessage { 0107 anchors.centerIn: parent 0108 anchors.left: parent.left 0109 anchors.right: parent.right 0110 anchors.margins: Kirigami.Units.largeSpacing 0111 0112 icon.name: applicationWindow().isWidescreen ? "format-list-unordered" : "microphone-sensitivity-high" 0113 text: i18n("No recordings") 0114 visible: parent.count === 0 0115 } 0116 0117 // record button 0118 FloatingActionButton { 0119 visible: !applicationWindow().isWidescreen 0120 icon.name: 'microphone-sensitivity-high' 0121 onClicked: applicationWindow().openRecordScreen() 0122 } 0123 0124 delegate: RecordingListDelegate { 0125 recording: model.recording 0126 width: listView.width 0127 editMode: root.editMode 0128 showSeparator: index != listView.count - 1 0129 0130 onLongPressed: root.editMode = !root.editMode 0131 onEditRequested: root.editRecordingDialog(model.recording) 0132 onDeleteRequested: root.removeRecordingDialog(model.recording, index) 0133 onContextMenuRequested: { 0134 contextMenu.recording = model.recording; 0135 contextMenu.index = index; 0136 contextMenu.popup(this) 0137 } 0138 onExportRequested: saveFileDialog.openForRecording(model.recording) 0139 } 0140 0141 FileDialog { 0142 id: saveFileDialog 0143 fileMode: FileDialog.SaveFile 0144 currentFolder: StandardPaths.writableLocation(StandardPaths.MusicLocation) 0145 0146 property Recording recording 0147 0148 function openForRecording(recording) { 0149 title = i18n("Select a location to save recording %1", recording.fileName); 0150 defaultSuffix = recording.fileExtension; 0151 nameFilters = [`${recording.fileExtension} files (*.${recording.fileExtension})`]; 0152 saveFileDialog.recording = recording; 0153 open(); 0154 } 0155 0156 onAccepted: { 0157 let prefixLessUrl = decodeURIComponent(fileUrl.toString().substring("file://".length)); 0158 recording.createCopyOfFile(prefixLessUrl); 0159 applicationWindow().showPassiveNotification(i18n("Saved recording to %1", prefixLessUrl), "short"); 0160 } 0161 } 0162 0163 Controls.Menu { 0164 id: contextMenu 0165 modal: true 0166 Controls.Overlay.modal: MouseArea {} 0167 0168 property Recording recording 0169 property int index 0170 0171 Controls.MenuItem { 0172 text: i18n("Export to location") 0173 icon.name: "document-save" 0174 onTriggered: saveFileDialog.openForRecording(contextMenu.recording) 0175 } 0176 0177 Controls.MenuItem { 0178 text: i18n("Edit") 0179 icon.name: "edit-entry" 0180 onTriggered: { 0181 openDialogTimer.run = () => root.editRecordingDialog(contextMenu.recording); 0182 openDialogTimer.restart(); 0183 } 0184 } 0185 0186 Controls.MenuItem { 0187 text: i18n("Delete") 0188 icon.name: "delete" 0189 onTriggered: { 0190 openDialogTimer.run = () => root.removeRecordingDialog(contextMenu.recording, contextMenu.index); 0191 openDialogTimer.restart(); 0192 } 0193 } 0194 } 0195 0196 // HACK: for some reason the dialog might close immediately if triggered from the context menu 0197 // open the dialog a little later to workaround this 0198 Timer { 0199 id: openDialogTimer 0200 interval: 50 0201 property var run: () => {} 0202 onTriggered: run() 0203 } 0204 0205 Kirigami.PromptDialog { 0206 id: deleteDialog 0207 standardButtons: Kirigami.Dialog.NoButton 0208 0209 property Recording toDelete: null 0210 property int toDeleteIndex: 0 0211 0212 title: i18n("Delete %1", deleteDialog.toDelete ? deleteDialog.toDelete.fileName : "") 0213 subtitle: i18n("Are you sure you want to delete the recording %1?<br/>It will be <b>permanently lost</b> forever!", deleteDialog.toDelete ? deleteDialog.toDelete.fileName : "") 0214 0215 customFooterActions: [ 0216 Kirigami.Action { 0217 text: i18nc("@action:button", "Delete") 0218 icon.name: "delete" 0219 onTriggered: { 0220 if (applicationWindow().currentRecording && deleteDialog.toDelete.filePath == applicationWindow().currentRecording.filePath) { 0221 applicationWindow().switchToRecording(null); 0222 } 0223 RecordingModel.deleteRecording(deleteDialog.toDeleteIndex); 0224 deleteDialog.close(); 0225 } 0226 }, 0227 Kirigami.Action { 0228 text: i18nc("@action:button", "Cancel") 0229 icon.name: "dialog-cancel" 0230 onTriggered: { 0231 deleteDialog.close(); 0232 } 0233 } 0234 ] 0235 } 0236 0237 Kirigami.Dialog { 0238 id: editNameDialog 0239 0240 title: i18n("Rename %1", editDialogName.text) 0241 standardButtons: Kirigami.Dialog.Cancel | Kirigami.Dialog.Apply 0242 0243 padding: Kirigami.Units.largeSpacing 0244 bottomPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0245 preferredWidth: Kirigami.Units.gridUnit * 20 0246 0247 onApplied: { 0248 currentRecordingToEdit.fileName = editDialogName.text; 0249 editNameDialog.close(); 0250 } 0251 0252 Kirigami.FormLayout { 0253 Controls.TextField { 0254 id: editDialogName 0255 Kirigami.FormData.label: i18n("Name:") 0256 Layout.fillWidth: true 0257 } 0258 0259 Controls.Label { 0260 id: editDialogLocation 0261 Kirigami.FormData.label: i18n("Location:") 0262 Layout.fillWidth: true 0263 wrapMode: Text.Wrap 0264 } 0265 } 0266 } 0267 } 0268 }