1/************************************************************************** 2 ** ** 3 ** Copyright (C) 2018 Lukas Spies ** 4 ** Contact: http://photoqt.org ** 5 ** ** 6 ** This file is part of PhotoQt. ** 7 ** ** 8 ** PhotoQt is free software: you can redistribute it and/or modify ** 9 ** it under the terms of the GNU General Public License as published by ** 10 ** the Free Software Foundation, either version 2 of the License, or ** 11 ** (at your option) any later version. ** 12 ** ** 13 ** PhotoQt is distributed in the hope that it will be useful, ** 14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** 15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** 16 ** GNU General Public License for more details. ** 17 ** ** 18 ** You should have received a copy of the GNU General Public License ** 19 ** along with PhotoQt. If not, see <http://www.gnu.org/licenses/>. ** 20 ** ** 21 **************************************************************************/ 22 23import QtQuick 2.5 24import QtQuick.Controls 1.4 25 26import "../elements" 27 28Item { 29 30 id: meta 31 32 // Set up model on first load, afetrwards just change data 33 property bool imageLoaded: false 34 35 // make sure settings values are valid 36 property int settingsMetadataWindowWidth: Math.max(Math.min(settings.metadataWindowWidth, background.width/2), 300) 37 property real settingsMetadataOpacity: Math.min(Math.max(settings.metadataOpacity/255, 0), 1) 38 property int settingsMetadataFontSize: Math.max(5, Math.min(20, settings.metadataFontSize)) 39 40 // Adjust size 41 width: settingsMetadataWindowWidth 42 anchors { 43 left: mainwindow.left 44 top: mainwindow.top 45 bottom: mainwindow.bottom 46 margins: -1 47 } 48 49 // This is for the background color, allows adjusting opacity without affecting the text 50 Rectangle { 51 52 id: bgcolor 53 54 anchors.fill: parent 55 56 // Background/Border color 57 color: colour.fadein_slidein_bg 58 border.width: 1 59 border.color: colour.fadein_slidein_border 60 61 // Opacity is between 0 and 1 and depends on settings 62 opacity: settingsMetadataOpacity 63 64 } 65 66 67 property int nonFloatWidth: getButtonState() ? width : 0 68 69 opacity: 0 70 visible: opacity!=0 71 Behavior on opacity { NumberAnimation { duration: variables.animationSpeed } } 72 73 // This mouseare catches all mouse movements and prevents them from being passed on to the background 74 MouseArea { anchors.fill: parent; hoverEnabled: true } 75 76 // HEADING OF RECTANGLE 77 Text { 78 79 id: heading 80 81 anchors { 82 top: parent.top 83 left: parent.left 84 right: parent.right 85 topMargin: 10 86 } 87 88 horizontalAlignment: Qt.AlignHCenter 89 90 color: colour.text 91 font.pointSize: 15 92 font.bold: true 93 94 text: em.pty+qsTr("Metadata") 95 96 } 97 98 Rectangle { 99 100 id: separatorTop 101 102 anchors { 103 top: heading.bottom 104 left: parent.left 105 right: parent.right 106 topMargin: 10 107 } 108 109 color: colour.linecolour 110 height: 1 111 112 } 113 114 // Label at first start-up 115 Text { 116 117 anchors { 118 top: separatorTop.bottom 119 left: parent.left 120 right: parent.right 121 bottom: separatorBottom.bottom 122 margins: 10 123 } 124 125 visible: !imageLoaded && !unsupportedLabel.visible && !invalidLabel.visible 126 127 horizontalAlignment: Qt.AlignHCenter 128 verticalAlignment: Qt.AlignVCenter 129 130 color: colour.bg_label 131 font.bold: true 132 font.pointSize: 18 133 wrapMode: Text.WordWrap 134 text: em.pty+qsTr("No File Loaded") 135 136 } 137 138 Text { 139 140 id: unsupportedLabel 141 142 anchors { 143 top: separatorTop.bottom 144 left: parent.left 145 right: parent.right 146 bottom: separatorBottom.bottom 147 margins: 10 148 } 149 150 visible: false 151 152 horizontalAlignment: Qt.AlignHCenter 153 verticalAlignment: Qt.AlignVCenter 154 155 color: colour.bg_label 156 font.bold: true 157 font.pointSize: 18 158 wrapMode: Text.WordWrap 159 160 text: em.pty+qsTr("File Format Not Supported") 161 162 } 163 164 Text { 165 166 id: invalidLabel 167 168 anchors { 169 top: separatorTop.bottom 170 left: parent.left 171 right: parent.right 172 bottom: separatorBottom.bottom 173 margins: 10 174 } 175 176 visible: false 177 178 horizontalAlignment: Qt.AlignHCenter 179 verticalAlignment: Qt.AlignVCenter 180 181 color: colour.bg_label 182 font.bold: true 183 font.pointSize: 18 184 wrapMode: Text.WordWrap 185 186 text: em.pty+qsTr("Invalid File") 187 188 } 189 190 ListView { 191 192 id: view 193 194 anchors { 195 top: separatorTop.bottom 196 left: parent.left 197 right: parent.right 198 bottom: separatorBottom.top 199 margins: 10 200 } 201 202 visible: imageLoaded 203 204 model: ListModel { id: mod; } 205 delegate: deleg 206 207 } 208 209 Rectangle { 210 id: separatorBottom 211 anchors { 212 bottom: keepopen.top 213 left: parent.left 214 right: parent.right 215 bottomMargin: 10 216 } 217 218 height: 1 219 color: colour.linecolour 220 } 221 222 Rectangle { 223 224 id: keepopen 225 226 anchors { 227 bottom: parent.bottom 228 left: parent.left 229 right: parent.right 230 bottomMargin: 10 231 } 232 233 height: check.height 234 color: "#00000000" 235 236 CustomCheckBox { 237 238 id: check 239 240 textOnRight: false 241 242 anchors.right: parent.right 243 anchors.rightMargin: 5 244 245 fsize: 8 246 textColour: "#64" + colour.text.substring(1,colour.text.length) 247 //: Used as in 'Keep the metadata element open even if the cursor leaves it' 248 249 text: em.pty+qsTr("Keep Open") 250 251 onButtonCheckedChanged: 252 updateNonFloatWidth() 253 254 } 255 } 256 function updateNonFloatWidth() { 257 verboseMessage("MainView/MetaData", "updateNonFloatWidth(): " + check.checkedButton + " - " + nonFloatWidth + " - " + meta.width) 258 if(check.checkedButton) 259 nonFloatWidth = meta.width 260 else 261 nonFloatWidth = 0 262 } 263 264 function uncheckCheckbox() { check.checkedButton = false; } 265 function checkCheckbox() { check.checkedButton = true; } 266 function getButtonState() { return check.checkedButton; } 267 268 Component { 269 270 id: deleg 271 272 Rectangle { 273 274 id: rect 275 276 color: "#00000000"; 277 height: val.height; 278 width: meta.width-view.x*2 279 280 Text { 281 282 id: val; 283 284 visible: imageLoaded 285 color: colour.text 286 font.pointSize: settingsMetadataFontSize 287 lineHeight: (name == "" ? 0.8 : 1.3); 288 textFormat: Text.RichText 289 width: parent.width 290 wrapMode: Text.WordWrap 291 text: name !== "" ? "<b>" + name + "</b>: " + value : "" 292 293 ToolTip { 294 text: prop=="Exif.GPSInfo.GPSLongitudeRef" ? em.pty+qsTr("Click to open GPS position with online map") 295 : (name !== "" ? "<b>" + name + "</b><br>" + value : "") 296 anchors.fill: parent 297 cursorShape: prop == "Exif.GPSInfo.GPSLongitudeRef" ? Qt.PointingHandCursor : Qt.ArrowCursor 298 onClicked: { 299 if(prop == "Exif.GPSInfo.GPSLongitudeRef") 300 gpsClick(value) 301 } 302 } 303 304 } 305 306 } 307 308 } 309 310 MouseArea { 311 x: parent.width-8 312 width: 8 313 y: 0 314 height: parent.height 315 cursorShape: Qt.SplitHCursor 316 property int oldMouseX 317 318 onPressed: 319 oldMouseX = mouseX 320 321 onReleased: { 322 updateNonFloatWidth() 323 settings.metadataWindowWidth = parent.width 324 } 325 326 onPositionChanged: { 327 if (pressed) { 328 var w = parent.width + (mouseX - oldMouseX) 329 if(w >= 250 && w <= background.width/2) 330 parent.width = w 331 } 332 } 333 } 334 335 Connections { 336 target: variables 337 onFilterNoMatchChanged: 338 if(variables.filterNoMatch) 339 clear() 340 onDeleteNothingLeftChanged: 341 if(variables.deleteNothingLeft) 342 clear() 343 onGuiBlockedChanged: { 344 if(variables.guiBlocked && meta.opacity == 1) 345 meta.opacity = 0.2 346 else if(!variables.guiBlocked && meta.opacity == 0.2) 347 meta.opacity = 1 348 } 349 } 350 351 Connections { 352 target: watcher 353 onImageUpdated: 354 setData(getmetadata.getExiv2(variables.currentDir + "/" + variables.currentFile)) 355 } 356 357 function setData(d) { 358 359 verboseMessage("MainView/MetaData", "setData()") 360 361 if(variables.currentFile == "") 362 return 363 364 invalidLabel.visible = false 365 unsupportedLabel.visible = false 366 view.visible = false 367 368 if(d["validfile"] === "0") { 369 verboseMessage("MainView/MetaData", "setData(): Invalid file") 370 invalidLabel.visible = true 371 } else { 372 373 view.visible = true 374 375 mod.clear() 376 377 if(settings.metaFilename) { 378 var fname = getanddostuff.removePathFromFilename(variables.currentFile, false) 379 //: Keep string short! 380 mod.append({"name" : qsTranslate("metadata", "Filename"), "prop" : "", "value" : fname, "tooltip" : fname }) 381 } 382 383 if(settings.metaFileSize) 384 //: Keep string short! 385 mod.append({"name" : qsTranslate("metadata", "Filesize"), "prop" : "", "value" : d["filesize"], "tooltip" : d["filesize"]}) 386 387 if(settings.metaImageNumber) { 388 var pos = (variables.currentFilePos+1) + "/" + variables.totalNumberImagesCurrentFolder 389 //: Used as in "Image 3/16". The numbers (position of image in folder) are added on automatically. Keep string short! 390 mod.append({"name" : qsTranslate("metadata", "Image") + " #/#", "prop" : "", "value" : pos, "tooltip" : pos }) 391 } 392 393 if(d["supported"] !== "0") { 394 395 if(settings.metaDimensions) { 396 if("dimensions" in d) 397 //: The dimensions of the loaded image. Keep string short! 398 mod.append({"name" : qsTranslate("metadata", "Dimensions"), "prop" : "", "value" : d["dimensions"], "tooltip" : d["dimensions"]}) 399 else if("Exif.Photo.PixelXDimension" in d && "Exif.Photo.PixelYDimension" in d) { 400 var dim = d["Exif.Photo.PixelXDimension"] + "x" + d["Exif.Photo.PixelYDimension"] 401 //: The dimensions of the loaded image. Keep string short! 402 mod.append({"name" : qsTranslate("metadata", "Dimensions"), "prop" : "", "value" : dim, "tooltip" : dim}) 403 } 404 } 405 406 mod.append({"name" : "", "prop" : "", "value" : ""}) 407 408 //: Exif image metadata: the make of the camera used to take the photo. Keep string short! 409 var labels = ["Exif.Image.Make", qsTranslate("metadata", "Make"), "", 410 //: Exif image metadata: the model of the camera used to take the photo. Keep string short! 411 "Exif.Image.Model", qsTranslate("metadata", "Model"), "", 412 //: Exif image metadata: the software used to create the photo. Keep string short! 413 "Exif.Image.Software", qsTranslate("metadata", "Software"), "", 414 "","", "", 415 //: Exif image metadata: when the photo was taken. Keep string short! 416 "Exif.Photo.DateTimeOriginal", qsTranslate("metadata", "Time Photo was Taken"), "", 417 //: Exif image metadata: how long the sensor was exposed to the light. Keep string short! 418 "Exif.Photo.ExposureTime", qsTranslate("metadata", "Exposure Time"), "", 419 //: Exif image metadata: the flash setting when the photo was taken. Keep string short! 420 "Exif.Photo.Flash", qsTranslate("metadata", "Flash"), "", 421 "Exif.Photo.ISOSpeedRatings", "ISO", "", 422 //: Exif image metadata: the specific scene type the camera used for the photo. Keep string short! 423 "Exif.Photo.SceneCaptureType", qsTranslate("metadata", "Scene Type"), "", 424 //: Exif image metadata: https://en.wikipedia.org/wiki/Focal_length . Keep string short! 425 "Exif.Photo.FocalLength", qsTranslate("metadata", "Focal Length"), "", 426 //: Exif image metadata: https://en.wikipedia.org/wiki/F-number . Keep string short! 427 "Exif.Photo.FNumber", qsTranslate("metadata", "F Number"), "", 428 //: Exif image metadata: What type of light the camera detected. Keep string short! 429 "Exif.Photo.LightSource", qsTranslate("metadata", "Light Source"), "", 430 "","", "", 431 //: IPTC image metadata: A description of the image by the user/software. Keep string short! 432 "Iptc.Application2.Keywords", qsTranslate("metadata", "Keywords"), "", 433 //: IPTC image metadata: The CITY the imge was taken in. Keep string short! 434 "Iptc.Application2.City", qsTranslate("metadata", "Location"), "", 435 //: IPTC image metadata. Keep string short! 436 "Iptc.Application2.Copyright", qsTranslate("metadata", "Copyright"), "", 437 "","", "", 438 //: Exif image metadata. Keep string short! 439 "Exif.GPSInfo.GPSLongitudeRef", qsTranslate("metadata", "GPS Position"), "Exif.GPSInfo.GPSLatitudeRef", 440 "","",""] 441 442 var oneEmpty = false; 443 444 for(var i = 0; i < labels.length; i+=3) { 445 if(d[labels[i]] === undefined && d[labels[i+1]] === undefined) 446 continue 447 if(labels[i] === "" && labels[i+1] === "") { 448 if(!oneEmpty) { 449 oneEmpty = true 450 mod.append({"name" : "", "prop" : "", "value" : "", "tooltip" : ""}) 451 } 452 } else if(d[labels[i]] !== "" && d[labels[i+1]] !== "") { 453 oneEmpty = false; 454 mod.append({"name" : labels[i+1], 455 "prop" : labels[i], 456 "value" : d[labels[i]], 457 "tooltip" : d[labels[i+2] === "" ? d[labels[i]] : d[labels[i+2]]]}) 458 } 459 } 460 461 } 462 463 view.model = mod 464 imageLoaded = true 465 466 } 467 468 } 469 470 function gpsClick(value) { 471 472 verboseMessage("MainView/MetaData", "gpsClick(): " + value) 473 474 if(settings.metaGpsMapService == "bing.com/maps") 475 Qt.openUrlExternally("http://www.bing.com/maps/?sty=r&q=" + value + "&obox=1") 476 else if(settings.metaGpsMapService == "maps.google.com") 477 Qt.openUrlExternally("http://maps.google.com/maps?t=h&q=" + value) 478 else { 479 480 // For openstreetmap.org, we need to convert the GPS location into decimal format 481 482 var one = value.split(", ")[0] 483 var one_dec = 1*one.split("°")[0] + (1*(one.split("°")[1].split("'")[0]))/60 + (1*(one.split("'")[1].split("''")[0]))/3600 484 if(one.indexOf("S") !== -1) 485 one_dec *= -1; 486 487 var two = value.split(", ")[1] 488 var two_dec = 1*two.split("°")[0] + (1*(two.split("°")[1].split("'")[0]))/60 + (1*(two.split("'")[1].split("''")[0]))/3600 489 if(two.indexOf("W") !== -1) 490 two_dec *= -1; 491 492 Qt.openUrlExternally("http://www.openstreetmap.org/#map=15/" + "" + one_dec + "/" + two_dec) 493 } 494 495 } 496 497 function clear() { 498 imageLoaded = false 499 } 500 501 function hide() { 502 if(opacity == 1) verboseMessage("MainView/MetaData", "hide()") 503 if(!check.checkedButton) 504 opacity = 0 505 } 506 function show() { 507 if(opacity != 0) verboseMessage("MainView/MetaData", "show()") 508 opacity = 1 509 } 510 511 function clickInMetaData(pos) { 512 verboseMessage("MainView/MetaData", "clickInMetaData(): " + pos) 513 var ret = meta.contains(meta.mapFromItem(mainwindow,pos.x,pos.y)) 514 return ret 515 } 516 517} 518