#!/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 — next image<br>
Ctrl+Left — previous image<br>
Ctrl+Up — one level up<br>
Ctrl+R — rotate 90 degrees (schedule into batch)<br>
Ctrl+W — rotate 270 degrees (schedule into batch)<br>
Ctrl+P — 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_())