pyqflickr.py
author Dmitriy Morozov <morozov@cs.duke.edu>
Sat, 29 Mar 2008 15:52:29 -0400
changeset 5 cf32356b1020
parent 4 3e59316763c8
permissions -rwxr-xr-x
Added help_text

#!/usr/bin/env python

import sys
from PyQt4 import Qt, QtCore, QtGui

from ui_mainwin import Ui_MainWindow
import flickr, os


working_path = os.path.expanduser('~/.pyqflickr')
photo_size = ''       # medium
help_text = '''
Ctrl+Right &mdash; next image<br>
Ctrl+Left  &mdash; previous image<br>
Ctrl+Up    &mdash; one level up<br>
Ctrl+R     &mdash; rotate 90 degrees (schedule into batch)<br>
Ctrl+W     &mdash; rotate 270 degrees (schedule into batch)<br>
Ctrl+P     &mdash; toggle private/public'''

# Keyboard sensitive QLineEdit
class LineEditWithShortcuts(QtGui.QLineEdit):
    def event(self, event):
        if event.type() == QtCore.QEvent.KeyPress:
            if event.modifiers() & QtCore.Qt.ControlModifier:
                self.emit(QtCore.SIGNAL("shortcutPressed"), event.key())
                return True

        return QtGui.QLineEdit.event(self, event)

# Prefetch window
class ProgressDialog(QtGui.QProgressDialog):
    def __init__(self, text, max, cancelButtonText = 'Cancel'):
        QtGui.QProgressDialog.__init__(self, text, cancelButtonText, 0, max)
        self.setWindowTitle('PyQFlickr')
        self.canceled = False
        self.setValue(0)
        self.setMinimumDuration(0)
        self.connect(self, QtCore.SIGNAL("canceled()"), self.cancel)

    def cancel(self):
        self.canceled = True

# Main window
class PyQFlickr(QtGui.QMainWindow):
    def shortcutPressed(self, key):
        if self.photos == None: return
        
        # In set mode?
        if self.photo == None:
            if key == QtCore.Qt.Key_Up: 
                progress = ProgressDialog("Rotating photos", len(self.torotate))
                i = 0
                for index,degree in self.torotate:
                    progress.setValue(i)
                    self.photos[index].rotate(degree)
                    i += 1
                    QtGui.QApplication.processEvents()
                    if progress.canceled: break

                self.torotate = []
                self.photos = None
                self.showSets()
            return

        # In photo mode
        if   key == QtCore.Qt.Key_Right:
            self.photo = (self.photo + 1) % len(self.photos)
            self.showPhoto()
        elif key == QtCore.Qt.Key_Left:
            self.photo = (self.photo - 1) % len(self.photos)
            self.showPhoto()
        elif key == QtCore.Qt.Key_Up:
            self.photo = None
            self.showSet()
        elif key == QtCore.Qt.Key_R:
            self.torotate.append((self.photo, 90))
        elif key == QtCore.Qt.Key_W:
            self.torotate.append((self.photo, 270))
        elif key == QtCore.Qt.Key_P:
            self.photos[self.photo].toggle_public()
            self.showPhoto()

    def showSets(self):
        txt = '<table><tr>'
        i = 1
        for s in self.sets:
            txt += '<td width="120" align="center"><a href="%s"><img src="%s"><br>%s</a></td>' % \
                         (s.id(), s.get_photo(working_path), s.name())
            if i % 6 == 0: txt += '</tr><tr>'
            i += 1
        txt += '</tr></table>'
        self.ui.interfaceBrowser.setHtml(txt)

    def showSet(self):
        progress = ProgressDialog("Loading thumbnails", len(self.photos))
        txt = '<a href="fetchall">Pre-fetch all photos (with info)</a><br>'
        i = 1
        for p in self.photos:
            txt += '<td align="center"><a href="%i"><img src="%s"></a></td>' % \
                         (i-1, p.get(working_path, 's'))
            progress.setValue(i)
            QtGui.QApplication.processEvents()
            if i % 10 == 0: txt += '</tr><tr>'
            i += 1
            if progress.canceled:
                self.photos = None
                self.showSets()
                return
        txt += '</tr></table>'
        self.ui.interfaceBrowser.setHtml(txt)

    def showPhoto(self, force = False):
        photo = self.photos[self.photo]
        tags = photo.getTags()
        info_txt = '<b>Tags</b>:<br>'
        for tag,id in tags:
            info_txt += tag
            if id:
                info_txt += ('<a href="%s">X</a>' % id)
            info_txt += '<br>'
        info_txt += '<br>'
        if photo.is_public():   info_txt += "Public"
        else:                   info_txt += "Private"

        txt = '''<center>
                 <table>
                    <tr>
                        <td margin="5" align="center">
                            <img src="%s">
                        </td>
                        <td bgcolor="#DDDDDD">%s</td>
                    </tr>
                    <tr>
                    <td colspan="2">%i out of %i<br><br>%s</td>
                    </tr>
                 </table>
                 </center>''' % \
               (photo.get(working_path, photo_size, force), info_txt,
                self.photo + 1, len(self.photos), help_text)
        self.ui.interfaceBrowser.setHtml(txt)

    def prefetchAll(self):
        progress = ProgressDialog("Prefetching photos", len(self.photos))
        i = 0
        for p in self.photos:
            progress.setValue(i)
            p.get(working_path, photo_size)
            p.getInfo()
            i += 1
            QtGui.QApplication.processEvents()
            if progress.canceled: break

    def anchorClicked(self, url):
        if self.photos == None:         # In sets mode
            self.photos = flickr.photos(url.toString())     # url must be the setid
            self.photo = None
            self.showSet()
        elif self.photo == None:        # In photos mode
            if url.toString() != 'fetchall':
                self.photo = int(url.toString())
                self.showPhoto()
            else:
                self.prefetchAll()
        else:                           # In photo mode
            self.photos[self.photo].removeTag(url.toString())
            self.showPhoto()

    def message(self, msg):
        QtGui.QMessageBox.warning(None, "Warning", msg)

    def addTag(self):
        if self.photo == None: return       # Need to be in the photo editing mode
        tag = self.ui.tagEdit.text()
        self.photos[self.photo].addTag(tag)
        if tag not in self.tags: self.tags.append(tag)
        self.completions.setStringList(self.tags)
        self.ui.tagEdit.setText('')
        self.showPhoto()

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        # Setup ui
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.tagEdit = LineEditWithShortcuts(self.ui.centralwidget)
        self.ui.tagEdit.setObjectName("tagEdit")
        self.ui.vboxlayout.addWidget(self.ui.tagEdit)
        self.ui.interfaceBrowser.setOpenLinks(False)

        # Connect signals
        self.connect(self.ui.tagEdit, QtCore.SIGNAL("shortcutPressed"), self.shortcutPressed)
        self.connect(self.ui.tagEdit, QtCore.SIGNAL("returnPressed()"), self.addTag)
        self.connect(self.ui.interfaceBrowser, QtCore.SIGNAL("anchorClicked(const QUrl&)"), self.anchorClicked)

        # Init member
        self.sets = None
        self.photos = None
        self.photo = None

        # Deal with Flickr
        flickr.authenticate(self.message)
        self.tags = flickr.tags().values()
        self.sets = flickr.photosets()
        self.showSets()
        self.torotate = []
        
        # Tag completer
        self.completions = QtGui.QStringListModel(QtCore.QStringList(self.tags))
        self.completer = QtGui.QCompleter()
        self.completer.setModel(self.completions)
        self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.ui.tagEdit.setCompleter(self.completer)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    win = PyQFlickr()
    win.show()
    sys.exit(app.exec_())