1/* 2 * Copyright (c) 2013-2021 Meltytech, LLC 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18import QtQuick 2.12 19import QtQuick.Controls 2.12 20import QtQuick.Dialogs 1.3 21import QtQuick.Layouts 1.12 22import Shotcut.Controls 1.0 as Shotcut 23 24Item { 25 id: stabilizeRoot 26 width: 350 27 height: 150 28 property url settingsSavePath: 'file:///' + settings.savePath 29 property string _analysisRequiredMessage: qsTr('Click Analyze to use this filter.') 30 31 Component.onCompleted: { 32 filter.set('analyze', 0) 33 shakinessSlider.value = filter.getDouble('shakiness') 34 accuracySlider.value = filter.getDouble('accuracy') 35 button.enabled = !hasAnalysisCompleted() 36 setStatus(false) 37 } 38 39 function hasAnalysisCompleted() { 40 return (filter.get("results").length > 0 && 41 filter.get("filename").indexOf(filter.get("results")) !== -1) 42 } 43 44 function setStatus( inProgress ) { 45 if (inProgress) { 46 status.text = qsTr('Analyzing...') 47 } 48 else if (hasAnalysisCompleted()) { 49 status.text = qsTr('Analysis complete.') 50 } 51 else 52 { 53 status.text = _analysisRequiredMessage 54 } 55 } 56 57 function analyzeValueChanged() { 58 button.enabled = true 59 status.text = _analysisRequiredMessage 60 } 61 62 function startAnalyzeJob(filename) { 63 stabilizeRoot.fileSaved(filename) 64 filter.set('filename', filename) 65 filter.getHash() 66 setStatus(true) 67 filter.analyze(); 68 } 69 70 // This signal is used to workaround context properties not available in 71 // the FileDialog onAccepted signal handler on Qt 5.5. 72 signal fileSaved(string filename) 73 onFileSaved: { 74 var lastPathSeparator = filename.lastIndexOf('/') 75 if (lastPathSeparator !== -1) { 76 settings.savePath = filename.substring(0, lastPathSeparator) 77 } 78 } 79 80 Connections { 81 target: filter 82 onAnalyzeFinished: { 83 filter.set("reload", 1); 84 setStatus(false) 85 } 86 } 87 88 FileDialog { 89 id: fileDialog 90 title: qsTr( 'Select a file to store analysis results.' ) 91 modality: application.dialogModality 92 selectExisting: false 93 selectMultiple: false 94 selectFolder: false 95 folder: settingsSavePath 96 nameFilters: [ "Stabilize Results (*.stab)" ] 97 selectedNameFilter: "Stabilize Results (*.stab)" 98 onAccepted: { 99 var filename = fileDialog.fileUrl.toString() 100 // Remove resource prefix ("file://") 101 filename = filename.substring(7) 102 if (filename.substring(2, 4) == ':/') { 103 // In Windows, the prefix is a little different 104 filename = filename.substring(1) 105 } 106 107 var extension = ".stab" 108 // Force file extension to ".stab" 109 var extIndex = filename.indexOf(extension, filename.length - extension.length) 110 if (extIndex == -1) { 111 filename += ".stab" 112 } 113 startAnalyzeJob(filename) 114 } 115 onRejected: { 116 button.enabled = true 117 } 118 } 119 120 GridLayout { 121 columns: 3 122 anchors.fill: parent 123 anchors.margins: 8 124 125 Label { 126 text: qsTr('<b>Analyze Options</b>') 127 Layout.columnSpan: 3 128 } 129 130 Label { 131 text: qsTr('Shakiness') 132 Layout.alignment: Qt.AlignRight 133 } 134 Shotcut.SliderSpinner { 135 id: shakinessSlider 136 minimumValue: 1 137 maximumValue: 10 138 stepSize: 1 139 onValueChanged: { 140 filter.set('shakiness', value) 141 analyzeValueChanged() 142 } 143 } 144 Shotcut.UndoButton { 145 onClicked: shakinessSlider.value = 4 146 } 147 148 Label { 149 text: qsTr('Accuracy') 150 Layout.alignment: Qt.AlignRight 151 } 152 Shotcut.SliderSpinner { 153 id: accuracySlider 154 minimumValue: 1 155 maximumValue: 15 156 stepSize: 1 157 onValueChanged: { 158 filter.set('accuracy', value) 159 analyzeValueChanged() 160 } 161 } 162 Shotcut.UndoButton { 163 onClicked: accuracySlider.value = 4 164 } 165 166 Shotcut.Button { 167 id: button 168 text: qsTr('Analyze') 169 Layout.alignment: Qt.AlignRight 170 onClicked: { 171 button.enabled = false 172 var filename = application.getNextProjectFile('stab') 173 if (filename) { 174 stabilizeRoot.fileSaved(filename) 175 startAnalyzeJob(filename) 176 } else { 177 fileDialog.open() 178 } 179 } 180 } 181 Label { 182 id: status 183 Layout.columnSpan: 2 184 } 185 186 Label { 187 text: qsTr('<b>Filter Options</b>') 188 Layout.columnSpan: 3 189 } 190 191 Label { 192 text: qsTr('Zoom') 193 Layout.alignment: Qt.AlignRight 194 } 195 Shotcut.SliderSpinner { 196 id: zoomSlider 197 minimumValue: -50 198 maximumValue: 50 199 decimals: 1 200 suffix: ' %' 201 value: filter.getDouble('zoom') 202 onValueChanged: { 203 filter.set('zoom', value) 204 filter.set("refresh", 1); 205 } 206 } 207 Shotcut.UndoButton { 208 onClicked: zoomSlider.value = 0 209 } 210 211 Label { 212 text: qsTr('Smoothing') 213 Layout.alignment: Qt.AlignRight 214 } 215 Shotcut.SliderSpinner { 216 id: smoothingSlider 217 minimumValue: 0 218 maximumValue: 100 219 stepSize: 1 220 value: filter.get('smoothing') 221 onValueChanged: { 222 filter.set('smoothing', value) 223 } 224 } 225 Shotcut.UndoButton { 226 onClicked: smoothingSlider.value = 15 227 } 228 229 Item { 230 Layout.fillHeight: true; 231 } 232 } 233} 234