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