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