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