1/**
2 * \file SettingsPage.qml
3 * Settings page.
4 *
5 * \b Project: Kid3
6 * \author Urs Fleisch
7 * \date 16 Feb 2015
8 *
9 * Copyright (C) 2015-2019  Urs Fleisch
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; version 3.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24import QtQuick 2.11
25import QtQuick.Controls 2.4
26
27AbstractSettingsPage {
28  id: settingsPage
29
30  title: qsTr("Settings")
31  model: [
32    SettingsElement { name: qsTr("Tags") },
33    SettingsElement { name: qsTr("Files") },
34    SettingsElement { name: qsTr("Plugins") },
35    SettingsElement { name: qsTr("Appearance") }
36  ]
37  onClicked: pageStack.push(
38               [ tagsPage, filesPage, pluginsPage, appearancePage ][index])
39  Component {
40    id: tagsPage
41    AbstractSettingsPage {
42      property QtObject tagCfg: configs.tagConfig()
43      property QtObject fmtCfg: configs.tagFormatConfig()
44      title: qsTr("Tags")
45      visible: false
46      model: [
47        SettingsElement {
48          name: qsTr("Mark truncated fields")
49          onActivated: function() { value = tagCfg.markTruncations; }
50          onDeactivated: function() { tagCfg.markTruncations = value; }
51        },
52        SettingsElement {
53          name: qsTr("ID3v1 text encoding")
54          dropDownModel: configs.tagConfig().getTextCodecNames()
55          onActivated: function() { value = tagCfg.textEncodingV1Index; }
56          onDeactivated: function() { tagCfg.textEncodingV1Index = value; }
57        },
58        SettingsElement {
59          name: qsTr("ID3v2 text encoding")
60          dropDownModel: configs.tagConfig().getTextEncodingNames()
61          onActivated: function() { value = tagCfg.textEncoding; }
62          onDeactivated: function() { tagCfg.textEncoding = value; }
63        },
64        SettingsElement {
65          name: qsTr("Use track/total number of tracks format")
66          onActivated: function() { value = tagCfg.enableTotalNumberOfTracks; }
67          onDeactivated: function() { tagCfg.enableTotalNumberOfTracks = value; }
68        },
69        SettingsElement {
70          name: qsTr("Genre as text instead of numeric string")
71          onActivated: function() { value = tagCfg.genreNotNumeric; }
72          onDeactivated: function() { tagCfg.genreNotNumeric = value; }
73        },
74        SettingsElement {
75          name: qsTr("WAV files with lowercase id3 chunk")
76          onActivated: function() { value = tagCfg.lowercaseId3RiffChunk; }
77          onDeactivated: function() { tagCfg.lowercaseId3RiffChunk = value; }
78        },
79        SettingsElement {
80          name: qsTr("Version used for new ID3v2 tags")
81          dropDownModel: configs.tagConfig().getId3v2VersionNames()
82          onActivated: function() { value = tagCfg.id3v2Version; }
83          onDeactivated: function() { tagCfg.id3v2Version = value; }
84        },
85        SettingsElement {
86          name: qsTr("Track number digits")
87          onActivated: function() { value = tagCfg.trackNumberDigits; }
88          onDeactivated: function() { tagCfg.trackNumberDigits = value; }
89        },
90        SettingsElement {
91          name: qsTr("Ogg/Vorbis comment field name")
92          dropDownModel: configs.tagConfig().getCommentNames()
93          onActivated: function() {
94            value = dropDownModel.indexOf(tagCfg.commentName)
95          }
96          onDeactivated: function() {
97            tagCfg.commentName = dropDownModel[value]
98          }
99        },
100        SettingsElement {
101          name: qsTr("Ogg/Vorbis picture field name")
102          dropDownModel: configs.tagConfig().getPictureNames()
103          onActivated: function() { value = tagCfg.pictureNameIndex; }
104          onDeactivated: function() { tagCfg.pictureNameIndex = value; }
105        },
106        SettingsElement {
107          name: qsTr("RIFF track number field name")
108          dropDownModel: configs.tagConfig().getRiffTrackNames()
109          onActivated: function() {
110            value = dropDownModel.indexOf(tagCfg.riffTrackName)
111          }
112          onDeactivated: function() {
113            tagCfg.riffTrackName = dropDownModel[value]
114          }
115        },
116        SettingsElement {
117          name: qsTr("Mark if picture larger than maximum size")
118          onActivated: function() { value = tagCfg.markOversizedPictures; }
119          onDeactivated: function() { tagCfg.markOversizedPictures = value; }
120        },
121        SettingsElement {
122          name: qsTr("Picture maximum size (bytes)")
123          onActivated: function() { value = tagCfg.maximumPictureSize; }
124          onDeactivated: function() { tagCfg.maximumPictureSize = value; }
125        },
126        SettingsElement {
127          name: qsTr("Show only custom genres")
128          onActivated: function() { value = tagCfg.onlyCustomGenres; }
129          onDeactivated: function() { tagCfg.onlyCustomGenres = value; }
130          onEdit: function() {
131            stringListEditPage.title = qsTr("Custom Genres")
132            stringListEditPage.onActivated = function() {
133              stringListEditPage.setElements(tagCfg.customGenres)
134            }
135            stringListEditPage.onDeactivated = function() {
136              tagCfg.customGenres = stringListEditPage.getElements()
137            }
138            page.StackView.view.push(stringListEditPage)
139          }
140        },
141        SettingsElement {
142          name: qsTr("Case conversion")
143          dropDownModel: configs.tagFormatConfig().getCaseConversionNames()
144          onActivated: function() { value = fmtCfg.caseConversion; }
145          onDeactivated: function() { fmtCfg.caseConversion = value; }
146        },
147        SettingsElement {
148          name: qsTr("Locale")
149          dropDownModel: configs.tagFormatConfig().getLocaleNames()
150          onActivated: function() {
151            var idx = dropDownModel.indexOf(fmtCfg.localeName)
152            value = idx === -1 ? 0 : idx
153          }
154          onDeactivated: function() {
155            fmtCfg.localeName = value > 0 ? dropDownModel[value] : ""
156          }
157        },
158        SettingsElement {
159          name: qsTr("String replacement")
160          onActivated: function() { value = fmtCfg.strRepEnabled; }
161          onDeactivated: function() { fmtCfg.strRepEnabled = value; }
162          onEdit: function() {
163            mapEditPage.title = qsTr("String Replacement")
164            mapEditPage.onActivated = function() {
165              mapEditPage.setElements(fmtCfg.strRepMap)
166            }
167            mapEditPage.onDeactivated = function() {
168              fmtCfg.strRepMap = mapEditPage.getElements()
169            }
170            page.StackView.view.push(mapEditPage)
171          }
172        }
173      ]
174      StackView.onActivated: activateAll()
175      StackView.onDeactivated: deactivateAll()
176    }
177  }
178  Component {
179    id: filesPage
180    AbstractSettingsPage {
181      property QtObject fileCfg: configs.fileConfig()
182      property QtObject fmtCfg: configs.filenameFormatConfig()
183      title: qsTr("Files")
184      visible: false
185      model: [
186        SettingsElement {
187          name: qsTr("Load last-opened files")
188          onActivated: function() { value = fileCfg.loadLastOpenedFile; }
189          onDeactivated: function() { fileCfg.loadLastOpenedFile = value; }
190        },
191        SettingsElement {
192          name: qsTr("Preserve file timestamp")
193          onActivated: function() { value = fileCfg.preserveTime; }
194          onDeactivated: function() { fileCfg.preserveTime = value; }
195        },
196        SettingsElement {
197          name: qsTr("Mark changes")
198          onActivated: function() { value = fileCfg.markChanges; }
199          onDeactivated: function() { fileCfg.markChanges = value; }
200        },
201        SettingsElement {
202          name: qsTr("Automatically apply format")
203          onActivated: function() { value = fmtCfg.formatWhileEditing; }
204          onDeactivated: function() { fmtCfg.formatWhileEditing = value; }
205        },
206        SettingsElement {
207          name: qsTr("Use maximum length")
208          onActivated: function() { value = fmtCfg.enableMaximumLength; }
209          onDeactivated: function() { fmtCfg.enableMaximumLength = value; }
210        },
211        SettingsElement {
212          name: qsTr("Maximum length")
213          onActivated: function() { value = fmtCfg.maximumLength; }
214          onDeactivated: function() { fmtCfg.maximumLength = value; }
215        },
216        SettingsElement {
217          name: qsTr("Case conversion")
218          dropDownModel: configs.filenameFormatConfig().getCaseConversionNames()
219          onActivated: function() { value = fmtCfg.caseConversion; }
220          onDeactivated: function() { fmtCfg.caseConversion = value; }
221        },
222        SettingsElement {
223          name: qsTr("Locale")
224          dropDownModel: configs.filenameFormatConfig().getLocaleNames()
225          onActivated: function() {
226            var idx = dropDownModel.indexOf(fmtCfg.localeName)
227            value = idx === -1 ? 0 : idx
228          }
229          onDeactivated: function() {
230            fmtCfg.localeName = value > 0 ? dropDownModel[value] : ""
231          }
232        },
233        SettingsElement {
234          name: qsTr("String replacement")
235          onActivated: function() { value = fmtCfg.strRepEnabled; }
236          onDeactivated: function() { fmtCfg.strRepEnabled = value; }
237          onEdit: function() {
238            mapEditPage.title = qsTr("String Replacement")
239            mapEditPage.onActivated = function() {
240              mapEditPage.setElements(fmtCfg.strRepMap)
241            }
242            mapEditPage.onDeactivated = function() {
243              fmtCfg.strRepMap = mapEditPage.getElements()
244            }
245            page.StackView.view.push(mapEditPage)
246          }
247        },
248        SettingsElement {
249          name: qsTr("Filename for cover")
250          onActivated: function() { value = fileCfg.defaultCoverFileName; }
251          onDeactivated: function() { fileCfg.defaultCoverFileName = value; }
252        },
253        SettingsElement {
254          name: qsTr("Playlist text encoding")
255          dropDownModel: configs.fileConfig().getTextCodecNames()
256          onActivated: function() { value = fileCfg.textEncodingIndex; }
257          onDeactivated: function() { fileCfg.textEncodingIndex = value; }
258        },
259        SettingsElement {
260          name: qsTr("To filename format")
261          dropDownModel: configs.fileConfig().toFilenameFormats
262          width: constants.gu(51)
263          onActivated: function() {
264            value = dropDownModel.indexOf(fileCfg.toFilenameFormat)
265          }
266          onDeactivated: function() {
267            fileCfg.toFilenameFormat = dropDownModel[value]
268          }
269          onEdit: function() {
270            stringListEditPage.title = qsTr("Filename from Tag")
271            stringListEditPage.onActivated = function() {
272              stringListEditPage.setElements(dropDownModel)
273              stringListEditPage.currentIndex = value
274            }
275            stringListEditPage.onDeactivated = function() {
276              var lst = stringListEditPage.getElements()
277              configs.fileConfig().toFilenameFormats = lst
278              configs.fileConfig().toFilenameFormat =
279                  lst[stringListEditPage.currentIndex]
280            }
281            page.StackView.view.push(stringListEditPage)
282          }
283        },
284        SettingsElement {
285          name: qsTr("From filename format")
286          dropDownModel: configs.fileConfig().fromFilenameFormats
287          width: constants.gu(51)
288          onActivated: function() {
289            value = dropDownModel.indexOf(fileCfg.fromFilenameFormat)
290          }
291          onDeactivated: function() {
292            fileCfg.fromFilenameFormat = dropDownModel[value]
293          }
294          onEdit: function() {
295            stringListEditPage.title = qsTr("Tag from Filename")
296            stringListEditPage.onActivated = function() {
297              stringListEditPage.setElements(dropDownModel)
298              stringListEditPage.currentIndex = value
299            }
300            stringListEditPage.onDeactivated = function() {
301              var lst = stringListEditPage.getElements()
302              configs.fileConfig().fromFilenameFormats = lst
303              configs.fileConfig().fromFilenameFormat =
304                  lst[stringListEditPage.currentIndex]
305            }
306            page.StackView.view.push(stringListEditPage)
307          }
308        }
309      ]
310      StackView.onActivated: activateAll()
311      StackView.onDeactivated: deactivateAll()
312    }
313  }
314  Component {
315    id: pluginsPage
316    AbstractSettingsPage {
317      title: qsTr("Plugins")
318      visible: false
319      StackView.onActivated: {
320        var tagCfg = configs.tagConfig()
321        var importCfg = configs.importConfig()
322        var disabledTagPlugins, disabledImportPlugins, i, name
323        disabledTagPlugins = tagCfg.disabledPlugins
324        disabledImportPlugins = importCfg.disabledPlugins
325        for (i = 0; i < model.length; ++i) {
326          name = model[i].name
327          model[i].value =
328              disabledTagPlugins.indexOf(name) === -1 &&
329              disabledImportPlugins.indexOf(name) === -1
330        }
331      }
332      StackView.onDeactivated: {
333        var tagCfg = configs.tagConfig()
334        var importCfg = configs.importConfig()
335        var disabledTagPlugins, disabledImportPlugins, i, name
336        var availableTagPlugins = tagCfg.availablePlugins
337        var availableImportPlugins = importCfg.availablePlugins
338        disabledTagPlugins = []
339        disabledImportPlugins = []
340        for (i = 0; i < model.length; ++i) {
341          if (model[i].value === false) {
342            name = model[i].name
343            if (availableTagPlugins.indexOf(name) !== -1) {
344              disabledTagPlugins.push(name)
345            } else if (availableImportPlugins.indexOf(name) !== -1) {
346              disabledImportPlugins.push(name)
347            }
348          }
349        }
350        tagCfg.disabledPlugins = disabledTagPlugins
351        importCfg.disabledPlugins = disabledImportPlugins
352      }
353      Component.onCompleted: {
354        // A deep copy is necessary in QtQuick 2 because of QTBUG-33149
355        // (concat does not work with QStringList) and to avoid modification of
356        // the original list in the config.
357        var availablePlugins = configs.tagConfig().availablePlugins.slice()
358        availablePlugins = availablePlugins.concat(
359               configs.importConfig().availablePlugins)
360        var settingsModel = []
361        var elementComponent = Qt.createComponent("SettingsElement.qml")
362        for (var i = 0; i < availablePlugins.length; ++i) {
363          var elementObj = elementComponent.createObject(null)
364          elementObj.name = availablePlugins[i]
365          settingsModel.push(elementObj)
366        }
367        model = settingsModel
368      }
369    }
370  }
371  Component {
372    id: appearancePage
373    AbstractSettingsPage {
374      property QtObject mainWindowCfg: configs.mainWindowConfig()
375      title: qsTr("Appearance")
376      visible: false
377      model: [
378        SettingsElement {
379          name: qsTr("Language")
380          dropDownModel: [qsTr("System")]
381            .concat(configs.mainWindowConfig().availableLanguages())
382          onActivated: function() {
383            value = mainWindowCfg.language
384                ? dropDownModel.indexOf(mainWindowCfg.language) : 0
385          }
386          onDeactivated: function() {
387            mainWindowCfg.language = value > 0 ? dropDownModel[value] : ""
388          }
389        },
390        SettingsElement {
391          name: qsTr("Theme")
392          dropDownModel: configs.mainWindowConfig().getQtQuickStyleNames()
393          onActivated: function() {
394            value = dropDownModel.indexOf(mainWindowCfg.qtQuickStyle)
395          }
396          onDeactivated: function() {
397            mainWindowCfg.qtQuickStyle = dropDownModel[value]
398          }
399        }
400      ]
401      StackView.onActivated: activateAll()
402      StackView.onDeactivated: deactivateAll()
403    }
404  }
405
406  StringListEditPage {
407    id: stringListEditPage
408    property var onActivated
409    property var onDeactivated
410    visible: false
411    StackView.onActivated: this.onActivated()
412    StackView.onDeactivated: this.onDeactivated()
413  }
414  MapEditPage {
415    id: mapEditPage
416    property var onActivated
417    property var onDeactivated
418    visible: false
419    StackView.onActivated: this.onActivated()
420    StackView.onDeactivated: this.onDeactivated()
421  }
422}
423