ecasa: remove '\n\nView Photo' from summary
[enigma2-plugins.git] / ecasa / src / EcasaGui.py
1 from __future__ import print_function
2
3 #pragma mark - GUI
4
5 #pragma mark Screens
6 from Screens.ChoiceBox import ChoiceBox
7 from Screens.Screen import Screen
8 from Screens.HelpMenu import HelpableScreen
9 from Screens.InfoBarGenerics import InfoBarNotifications
10 from Screens.MessageBox import MessageBox
11 from NTIVirtualKeyBoard import NTIVirtualKeyBoard
12 from EcasaSetup import EcasaSetup
13
14 #pragma mark Components
15 from Components.ActionMap import HelpableActionMap
16 from Components.AVSwitch import AVSwitch
17 from Components.Label import Label
18 from Components.Pixmap import Pixmap, MovingPixmap
19 from Components.Sources.StaticText import StaticText
20 from Components.Sources.List import List
21
22 #pragma mark Configuration
23 from Components.config import config
24
25 #pragma mark Picasa
26 from .PicasaApi import PicasaApi
27 from TagStrip import strip_readable
28
29 from enigma import ePicLoad, ePythonMessagePump, getDesktop
30 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
31 from Tools.Notifications import AddPopup
32 from collections import deque
33
34 try:
35         xrange = xrange
36 except NameError:
37         xrange = range
38
39 our_print = lambda *args, **kwargs: print("[EcasaGui]", *args, **kwargs)
40
41 AUTHENTICATION_ERROR_ID = "EcasaAuthenticationError"
42
43 class EcasaPictureWall(Screen, HelpableScreen, InfoBarNotifications):
44         """Base class for so-called "picture walls"."""
45         PICS_PER_PAGE = 15
46         PICS_PER_ROW = 5
47         skin = """<screen position="center,center" size="600,380">
48                 <ePixmap position="0,0" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on"/>
49                 <ePixmap position="140,0" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on"/>
50                 <ePixmap position="280,0" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on"/>
51                 <ePixmap position="420,0" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on"/>
52                 <ePixmap position="565,10" size="35,25" pixmap="skin_default/buttons/key_menu.png" alphatest="on"/>
53                 <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
54                 <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
55                 <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
56                 <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
57                 <widget name="waitingtext" position="100,179" size="400,22" valign="center" halign="center" font="Regular;22"/>
58                 <widget name="image0"  position="30,50"   size="90,90"/>
59                 <widget name="image1"  position="140,50"  size="90,90"/>
60                 <widget name="image2"  position="250,50"  size="90,90"/>
61                 <widget name="image3"  position="360,50"  size="90,90"/>
62                 <widget name="image4"  position="470,50"  size="90,90"/>
63                 <widget name="image5"  position="30,160"  size="90,90"/>
64                 <widget name="image6"  position="140,160" size="90,90"/>
65                 <widget name="image7"  position="250,160" size="90,90"/>
66                 <widget name="image8"  position="360,160" size="90,90"/>
67                 <widget name="image9"  position="470,160" size="90,90"/>
68                 <widget name="image10" position="30,270"  size="90,90"/>
69                 <widget name="image11" position="140,270" size="90,90"/>
70                 <widget name="image12" position="250,270" size="90,90"/>
71                 <widget name="image13" position="360,270" size="90,90"/>
72                 <widget name="image14" position="470,270" size="90,90"/>
73                 <!-- TODO: find some better picture -->
74                 <widget name="highlight" position="30,142" size="90,5"/>
75                 </screen>"""
76         def __init__(self, session, api=None):
77                 Screen.__init__(self, session)
78                 HelpableScreen.__init__(self)
79                 InfoBarNotifications.__init__(self)
80
81                 if api is None:
82                         self.api = PicasaApi(cache=config.plugins.ecasa.cache.value)
83                         try:
84                                 self.api.setCredentials(
85                                         config.plugins.ecasa.google_username.value,
86                                         config.plugins.ecasa.google_password.value
87                                 )
88                         except Exception as e:
89                                 AddPopup(
90                                         _("Unable to authenticate with Google: %s.") % (e.message),
91                                         MessageBox.TYPE_ERROR,
92                                         5,
93                                         id=AUTHENTICATION_ERROR_ID,
94                                 )
95                 else:
96                         self.api = api
97
98                 self["key_red"] = StaticText(_("Close"))
99                 self["key_green"] = StaticText(_("Albums"))
100                 self["key_yellow"] = StaticText()
101                 self["key_blue"] = StaticText(_("Search"))
102                 for i in xrange(self.PICS_PER_PAGE):
103                         self['image%d' % i] = Pixmap()
104                         self['title%d' % i] = StaticText()
105                 self["highlight"] = MovingPixmap()
106                 self["waitingtext"] = Label(_("Please wait... Loading list..."))
107
108                 self["overviewActions"] = HelpableActionMap(self, "EcasaOverviewActions", {
109                         "up": self.up,
110                         "down": self.down,
111                         "left": self.left,
112                         "right": self.right,
113                         "nextPage": (self.nextPage, _("show next page")),
114                         "prevPage": (self.prevPage, _("show previous page")),
115                         "select": self.select,
116                         "exit":self.close,
117                         "albums":(self.albums, _("show your albums (if logged in)")),
118                         "search":(self.search, _("start a new search")),
119                         "contextMenu":(self.contextMenu, _("open context menu")),
120                         }, -1)
121
122                 self.offset = 0
123                 self.__highlighted = 0
124                 self.pictures = ()
125
126                 # thumbnail loader
127                 self.picload = ePicLoad()
128                 self.picload.PictureData.get().append(self.gotPicture)
129                 sc = AVSwitch().getFramebufferScale()
130                 self.picload.setPara((90, 90, sc[0], sc[1], False, 1, '#ff000000')) # TODO: hardcoded size is evil!
131                 self.currentphoto = None
132                 self.queue = deque()
133
134                 self.onLayoutFinish.append(self.layoutFinished)
135
136         def layoutFinished(self):
137                 self["highlight"].instance.setPixmapFromFile(resolveFilename(SCOPE_PLUGINS, "Extensions/Ecasa/highlighted.png"))
138                 self["highlight"].hide()
139
140         @property
141         def highlighted(self):
142                 return self.__highlighted
143
144         @highlighted.setter
145         def highlighted(self, highlighted):
146                 our_print("setHighlighted", highlighted)
147                 # only allow to select valid pictures
148                 if highlighted + self.offset >= len(self.pictures): return
149
150                 self.__highlighted = highlighted
151                 pixmap = self['image%d' % highlighted]
152                 origpos = pixmap.getPosition()
153                 origsize = pixmap.instance.size()
154                 # TODO: hardcoded highlight offset is evil :P
155                 self["highlight"].moveTo(origpos[0], origpos[1]+origsize.height()+2, 1)
156                 self["highlight"].startMoving()
157
158         def gotPicture(self, picInfo=None):
159                 ptr = self.picload.getData()
160                 idx = self.pictures.index(self.currentphoto)
161                 realIdx = idx - self.offset
162                 if ptr is not None:
163                         self['image%d' % realIdx].instance.setPixmap(ptr.__deref__())
164                 else:
165                         our_print("gotPicture got invalid results for idx", idx, "("+str(realIdx)+")")
166                         # NOTE: we could use a different picture here that indicates a failure
167                         self['image%d' % realIdx].instance.setPixmap(None)
168                         # NOTE: the thread WILL most likely be hung and NOT recover from it, so we should remove the old picload and create a new one :/
169                 self.currentphoto = None
170                 self.maybeDecode()
171
172         def maybeDecode(self):
173                 if self.currentphoto is not None: return
174                 try:
175                         filename, self.currentphoto = self.queue.pop()
176                 except IndexError:
177                         our_print("no queued photos")
178                         # no more pictures
179                         pass
180                 else:
181                         self.picload.startDecode(filename)
182
183         def pictureDownloaded(self, tup):
184                 filename, photo = tup
185                 self.queue.append((filename, photo))
186                 self.maybeDecode()
187
188         def pictureDownloadFailed(self, tup):
189                 error, photo = tup
190                 our_print("pictureDownloadFailed", error, photo)
191                 # TODO: indicate in gui
192
193         def setup(self):
194                 our_print("setup")
195                 self["waitingtext"].hide()
196                 self["highlight"].show()
197                 self.queue.clear()
198                 pictures = self.pictures
199                 for i in xrange(self.PICS_PER_PAGE):
200                         try:
201                                 our_print("trying to initiate download of idx", i+self.offset)
202                                 picture = pictures[i+self.offset]
203                                 self.api.downloadThumbnail(picture).addCallbacks(self.pictureDownloaded, self.pictureDownloadFailed)
204                         except IndexError:
205                                 # no more pictures
206                                 self['image%d' % i].instance.setPixmap(None)
207                         except Exception as e:
208                                 our_print("unexpected exception in setup:", e)
209
210         def up(self):
211                 # TODO: implement for incomplete pages
212                 highlighted = (self.highlighted - self.PICS_PER_ROW) % self.PICS_PER_PAGE
213                 our_print("up. before:", self.highlighted, ", after:", highlighted)
214                 self.highlighted = highlighted
215
216                 # we requested an invalid idx
217                 if self.highlighted != highlighted:
218                         # so skip another row
219                         highlighted = (highlighted - self.PICS_PER_ROW) % self.PICS_PER_PAGE
220                         our_print("up2. before:", self.highlighted, ", after:", highlighted)
221                         self.highlighted = highlighted
222
223         def down(self):
224                 # TODO: implement for incomplete pages
225                 highlighted = (self.highlighted + self.PICS_PER_ROW) % self.PICS_PER_PAGE
226                 our_print("down. before:", self.highlighted, ", after:", highlighted)
227                 self.highlighted = highlighted
228
229                 # we requested an invalid idx
230                 if self.highlighted != highlighted:
231                         # so try to skip another row
232                         highlighted = (highlighted + self.PICS_PER_ROW) % self.PICS_PER_PAGE
233                         our_print("down2. before:", self.highlighted, ", after:", highlighted)
234                         self.highlighted = highlighted
235
236         def left(self):
237                 # TODO: implement for incomplete pages
238                 highlighted = (self.highlighted - 1) % self.PICS_PER_PAGE
239                 our_print("left. before:", self.highlighted, ", after:", highlighted)
240                 self.highlighted = highlighted
241
242                 # we requested an invalid idx
243                 if self.highlighted != highlighted:
244                         # go to last possible item
245                         highlighted = (len(self.pictures) - 1) % self.PICS_PER_PAGE
246                         our_print("left2. before:", self.highlighted, ", after:", highlighted)
247                         self.highlighted = highlighted
248
249         def right(self):
250                 highlighted = (self.highlighted + 1) % self.PICS_PER_PAGE
251                 if highlighted + self.offset >= len(self.pictures):
252                         highlighted = 0
253                 our_print("right. before:", self.highlighted, ", after:", highlighted)
254                 self.highlighted = highlighted
255         def nextPage(self):
256                 our_print("nextPage")
257                 offset = self.offset + self.PICS_PER_PAGE
258                 Len = len(self.pictures)
259                 if offset >= Len:
260                         self.offset = 0
261                 else:
262                         self.offset = offset
263                         if offset + self.highlighted > Len:
264                                 self.highlighted = Len - offset - 1
265                 self.setup()
266         def prevPage(self):
267                 our_print("prevPage")
268                 offset = self.offset - self.PICS_PER_PAGE
269                 if offset < 0:
270                         Len = len(self.pictures) - 1
271                         offset = Len - (Len % self.PICS_PER_PAGE)
272                         self.offset = offset
273                         if offset + self.highlighted >= Len:
274                                 self.highlighted = Len - offset
275                 else:
276                         self.offset = offset
277                 self.setup()
278
279         def prevFunc(self):
280                 old = self.highlighted
281                 self.left()
282                 highlighted = self.highlighted
283                 if highlighted > old:
284                         self.prevPage()
285
286                 photo = None
287                 try:
288                         # NOTE: using self.highlighted as prevPage might have moved this if the page is not full
289                         photo = self.pictures[self.highlighted+self.offset]
290                 except IndexError:
291                         pass
292                 return photo
293
294         def nextFunc(self):
295                 old = self.highlighted
296                 self.right()
297                 highlighted = self.highlighted
298                 if highlighted < old:
299                         self.nextPage()
300
301                 photo = None
302                 try:
303                         # NOTE: using self.highlighted as nextPage might have moved this if the page is not full
304                         photo = self.pictures[self.highlighted+self.offset]
305                 except IndexError:
306                         pass
307                 return photo
308
309         def select(self):
310                 try:
311                         photo = self.pictures[self.highlighted+self.offset]
312                 except IndexError:
313                         our_print("no such picture")
314                         # TODO: indicate in gui
315                 else:
316                         self.session.open(EcasaPicture, photo, api=self.api, prevFunc=self.prevFunc, nextFunc=self.nextFunc)
317         def albums(self):
318                 self.session.open(EcasaAlbumview, self.api, user=config.plugins.ecasa.user.value)
319         def search(self):
320                 self.session.openWithCallback(
321                         self.searchCallback,
322                         NTIVirtualKeyBoard,
323                         title=_("Enter text to search for")
324                 )
325         def searchCallback(self, text=None):
326                 if text:
327                         # Maintain history
328                         history = config.plugins.ecasa.searchhistory.value
329                         if text not in history:
330                                 history.insert(0, text)
331                                 del history[10:]
332                         else:
333                                 history.remove(text)
334                                 history.insert(0, text)
335                         config.plugins.ecasa.searchhistory.save()
336
337                         # Workaround to allow search for umlauts if we know the encoding (pretty bad, I know...)
338                         thread = EcasaThread(lambda:self.api.getSearch(text, limit=str(config.plugins.ecasa.searchlimit.value)))
339                         self.session.open(EcasaFeedview, thread, api=self.api, title=_("Search for %s") % (text))
340
341         def contextMenu(self):
342                 options = [
343                         (_("Setup"), lambda: self.session.openWithCallback(self.setupClosed, EcasaSetup)),
344                         (_("Search History"), self.openHistory),
345                 ]
346                 self.session.openWithCallback(
347                         self.menuCallback,
348                         ChoiceBox,
349                         list=options
350                 )
351
352         def menuCallback(self, ret=None):
353                 if ret:
354                         ret[1]()
355
356         def openHistory(self):
357                 options = [(x, x) for x in config.plugins.ecasa.searchhistory.value]
358
359                 if options:
360                         self.session.openWithCallback(
361                                 self.historyWrapper,
362                                 ChoiceBox,
363                                 title=_("Select text to search for"),
364                                 list=options
365                         )
366                 else:
367                         self.session.open(
368                                 MessageBox,
369                                 _("No history"),
370                                 type=MessageBox.TYPE_INFO
371                         )
372
373         def historyWrapper(self, ret):
374                 if ret:
375                         self.searchCallback(ret[1])
376
377         def setupClosed(self):
378                 try:
379                         self.api.setCredentials(
380                                 config.plugins.ecasa.google_username.value,
381                                 config.plugins.ecasa.google_password.value
382                         )
383                 except Exception as e:
384                         AddPopup(
385                                 _("Unable to authenticate with Google: %s.") % (e.message),
386                                 MessageBox.TYPE_ERROR,
387                                 5,
388                                 id=AUTHENTICATION_ERROR_ID,
389                         )
390                 self.api.cache = config.plugins.ecasa.cache.value
391
392         def gotPictures(self, pictures):
393                 if not self.instance: return
394                 self.pictures = pictures
395                 self.setup()
396
397         def errorPictures(self, error):
398                 if not self.instance: return
399                 our_print("errorPictures", error)
400                 self.session.open(
401                         MessageBox,
402                         _("Error downloading") + ': ' + error.message,
403                         type=MessageBox.TYPE_ERROR,
404                         timeout=3
405                 )
406
407 class EcasaOverview(EcasaPictureWall):
408         """Overview and supposed entry point of ecasa. Shows featured pictures on the "EcasaPictureWall"."""
409         def __init__(self, session):
410                 EcasaPictureWall.__init__(self, session)
411                 self.skinName = ["EcasaOverview", "EcasaPictureWall"]
412                 thread = EcasaThread(self.api.getFeatured)
413                 thread.deferred.addCallbacks(self.gotPictures, self.errorPictures)
414                 thread.start()
415
416         def layoutFinished(self):
417                 EcasaPictureWall.layoutFinished(self)
418                 self.setTitle(_("eCasa: %s") % (_("Featured Photos")))
419
420 class EcasaFeedview(EcasaPictureWall):
421         """Display a nonspecific feed."""
422         def __init__(self, session, thread, api=None, title=None):
423                 EcasaPictureWall.__init__(self, session, api=api)
424                 self.skinName = ["EcasaFeedview", "EcasaPictureWall"]
425                 self.feedTitle = title
426                 self['key_green'].text = ''
427                 thread.deferred.addCallbacks(self.gotPictures, self.errorPictures)
428                 thread.start()
429
430         def layoutFinished(self):
431                 EcasaPictureWall.layoutFinished(self)
432                 self.setTitle(_("eCasa: %s") % (self.feedTitle or _("Album")))
433
434         def albums(self):
435                 pass
436
437 class EcasaAlbumview(Screen, HelpableScreen, InfoBarNotifications):
438         """Displays albums."""
439         skin = """<screen position="center,center" size="560,420">
440                 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" transparent="1" alphatest="on" />
441                 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" transparent="1" alphatest="on" />
442                 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" transparent="1" alphatest="on" />
443                 <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" transparent="1" alphatest="on" />
444                 <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
445                 <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
446                 <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
447                 <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
448                 <widget source="list" render="Listbox" position="0,50" size="560,360" scrollbarMode="showAlways">
449                         <convert type="TemplatedMultiContent">
450                                 {"template": [
451                                                 MultiContentEntryText(pos=(1,1), size=(540,22), text = 0, font = 0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER),
452                                         ],
453                                   "fonts": [gFont("Regular", 20)],
454                                   "itemHeight": 24
455                                  }
456                         </convert>
457                 </widget>
458         </screen>"""
459         def __init__(self, session, api, user='default'):
460                 Screen.__init__(self, session)
461                 HelpableScreen.__init__(self)
462                 InfoBarNotifications.__init__(self)
463                 self.api = api
464                 self.user = user
465
466                 self['list'] = List()
467                 self['key_red'] = StaticText(_("Close"))
468                 self['key_green'] = StaticText()
469                 self['key_yellow'] = StaticText(_("Change user"))
470                 self['key_blue'] = StaticText(_("User history"))
471
472                 self["albumviewActions"] = HelpableActionMap(self, "EcasaAlbumviewActions", {
473                         "select":(self.select, _("show album")),
474                         "exit":(self.close, _("Close")),
475                         "users":(self.users, _("Change user")),
476                         "history":(self.history, _("User history")),
477                 }, -1)
478
479                 self.acquireAlbumsForUser(user)
480                 self.onLayoutFinish.append(self.layoutFinished)
481
482         def layoutFinished(self):
483                 self.setTitle(_("eCasa: Albums for user %s") % (self.user,))
484
485         def acquireAlbumsForUser(self, user):
486                 thread = EcasaThread(lambda:self.api.getAlbums(user=user))
487                 thread.deferred.addCallbacks(self.gotAlbums, self.errorAlbums)
488                 thread.start()
489
490         def gotAlbums(self, albums):
491                 if not self.instance: return
492                 self['list'].list = albums
493
494         def errorAlbums(self, error):
495                 if not self.instance: return
496                 our_print("errorAlbums", error)
497                 self['list'].setList([(_("Error downloading"), "0", None)])
498                 self.session.open(
499                         MessageBox,
500                         _("Error downloading") + ': ' + error.value.message,
501                         type=MessageBox.TYPE_ERROR,
502                         timeout=30,
503                 )
504
505         def select(self):
506                 cur = self['list'].getCurrent()
507                 if cur:
508                         album = cur[-1]
509                         thread = EcasaThread(lambda:self.api.getAlbum(album))
510                         self.session.open(EcasaFeedview, thread, api=self.api, title=album.title and album.title.text)
511
512         def users(self):
513                 self.session.openWithCallback(
514                         self.searchCallback,
515                         NTIVirtualKeyBoard,
516                         title = _("Enter username")
517                 )
518         def searchCallback(self, text=None):
519                 if text:
520                         # Maintain history
521                         history = config.plugins.ecasa.userhistory.value
522                         if text not in history:
523                                 history.insert(0, text)
524                                 del history[10:]
525                         else:
526                                 history.remove(text)
527                                 history.insert(0, text)
528                         config.plugins.ecasa.userhistory.save()
529
530                         # Workaround to allow search for umlauts if we know the encoding (pretty bad, I know...)
531                         self.session.openWithCallback(self.close, EcasaAlbumview, self.api, text)
532
533         def history(self):
534                 options = [(x, x) for x in config.plugins.ecasa.userhistory.value]
535
536                 if options:
537                         self.session.openWithCallback(
538                                 self.historyWrapper,
539                                 ChoiceBox,
540                                 title=_("Select user"),
541                                 list=options
542                         )
543                 else:
544                         self.session.open(
545                                 MessageBox,
546                                 _("No history"),
547                                 type=MessageBox.TYPE_INFO
548                         )
549
550         def historyWrapper(self, ret):
551                 if ret:
552                         self.searchCallback(ret[1])
553
554 class EcasaPicture(Screen, HelpableScreen, InfoBarNotifications):
555         """Display a single picture and its metadata."""
556         PAGE_PICTURE = 0
557         PAGE_INFO = 1
558         def __init__(self, session, photo, api=None, prevFunc=None, nextFunc=None):
559                 size_w = getDesktop(0).size().width()
560                 size_h = getDesktop(0).size().height()
561                 self.skin = """<screen position="0,0" size="{size_w},{size_h}" flags="wfNoBorder">
562                         <widget name="pixmap" position="0,0" size="{size_w},{size_h}" backgroundColor="black" zPosition="2"/>
563                         <widget source="title" render="Label" position="25,20" zPosition="1" size="{labelwidth},40" valign="center" halign="left" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
564                         <widget source="summary" render="Label" position="25,60" zPosition="1" size="{labelwidth},100" valign="top" halign="left" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
565                         <widget source="keywords" render="Label" position="25,160" zPosition="1" size="{labelwidth},40" valign="center" halign="left" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
566                         <widget source="camera" render="Label" position="25,180" zPosition="1" size="{labelwidth},40" valign="center" halign="left" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1"/>
567                 </screen>""".format(size_w=size_w,size_h=size_h,labelwidth=size_w-50)
568                 Screen.__init__(self, session)
569                 HelpableScreen.__init__(self)
570                 InfoBarNotifications.__init__(self)
571
572                 self.api = api
573                 self.page = self.PAGE_PICTURE
574                 self.prevFunc = prevFunc
575                 self.nextFunc = nextFunc
576
577                 self['pixmap'] = Pixmap()
578                 self['camera'] = StaticText()
579                 self['title'] = StaticText()
580                 self['summary'] = StaticText()
581                 self['keywords'] = StaticText()
582
583                 self["pictureActions"] = HelpableActionMap(self, "EcasaPictureActions", {
584                         "info": (self.info, _("show metadata")),
585                         "exit": (self.close, _("Close")),
586                         }, -1)
587                 if prevFunc and nextFunc:
588                         self["directionActions"] = HelpableActionMap(self, "DirectionActions", {
589                                 "left": self.previous,
590                                 "right": self.next,
591                                 }, -2)
592
593                 self.picload = ePicLoad()
594                 self.picload.PictureData.get().append(self.gotPicture)
595
596                 # populate with data, initiate download
597                 self.reloadData(photo)
598
599         def gotPicture(self, picInfo=None):
600                 our_print("picture decoded")
601                 ptr = self.picload.getData()
602                 if ptr is not None:
603                         self['pixmap'].instance.setPixmap(ptr.__deref__())
604
605         def cbDownload(self, tup):
606                 if not self.instance: return
607                 filename, photo = tup
608                 self.picload.startDecode(filename)
609
610         def ebDownload(self, tup):
611                 if not self.instance: return
612                 error, photo = tup
613                 print("ebDownload", error)
614                 self.session.open(
615                         MessageBox,
616                         _("Error downloading") + ': ' + error.message,
617                         type=MessageBox.TYPE_ERROR,
618                         timeout=3
619                 )
620
621         def info(self):
622                 our_print("info")
623                 if self.page == self.PAGE_PICTURE:
624                         self.page = self.PAGE_INFO
625                         self['pixmap'].hide()
626                 else:
627                         self.page = self.PAGE_PICTURE
628                         self['pixmap'].show()
629
630         def reloadData(self, photo):
631                 if photo is None: return
632                 self.photo = photo
633                 unk = _("unknown")
634
635                 # camera
636                 if photo.exif.make and photo.exif.model:
637                         camera = '%s %s' % (photo.exif.make.text, photo.exif.model.text)
638                 elif photo.exif.make:
639                         camera = photo.exif.make.text
640                 elif photo.exif.model:
641                         camera = photo.exif.model.text
642                 else:
643                         camera = unk
644                 self['camera'].text = _("Camera: %s") % (camera,)
645
646                 title = photo.title.text if photo.title.text else unk
647                 self.setTitle(_("eCasa: %s") % (title))
648                 self['title'].text = _("Title: %s") % (title,)
649                 summary = strip_readable(photo.summary.text).replace('\n\nView Photo', '') if photo.summary.text else ''
650                 self['summary'].text = summary
651                 if photo.media and photo.media.keywords and photo.media.keywords.text:
652                         keywords = photo.media.keywords.text
653                         # TODO: find a better way to handle this
654                         if len(keywords) > 50:
655                                 keywords = keywords[:47] + "..."
656                 else:
657                         keywords = unk
658                 self['keywords'].text = _("Keywords: %s") % (keywords,)
659
660                 try:
661                         real_w = int(photo.media.content[0].width.text)
662                         real_h = int(photo.media.content[0].heigth.text)
663                 except Exception as e:
664                         our_print("EcasaPicture.__init__: illegal w/h values, using max size!")
665                         size = getDesktop(0).size()
666                         real_w = size.width()
667                         real_h = size.height()
668
669                 sc = AVSwitch().getFramebufferScale()
670                 self.picload.setPara((real_w, real_h, sc[0], sc[1], False, 1, '#ff000000'))
671
672                 # NOTE: no need to start an extra thread for this, twisted is "parallel" enough in this case
673                 self.api.downloadPhoto(photo).addCallbacks(self.cbDownload, self.ebDownload)
674
675         def previous(self):
676                 if self.prevFunc: self.reloadData(self.prevFunc())
677         def next(self):
678                 if self.nextFunc: self.reloadData(self.nextFunc())
679
680 #pragma mark - Thread
681
682 import threading
683 from twisted.internet import defer
684
685 class EcasaThread(threading.Thread):
686         def __init__(self, fnc):
687                 threading.Thread.__init__(self)
688                 self.deferred = defer.Deferred()
689                 self.__pump = ePythonMessagePump()
690                 self.__pump.recv_msg.get().append(self.gotThreadMsg)
691                 self.__asyncFunc = fnc
692                 self.__result = None
693                 self.__err = None
694
695         def gotThreadMsg(self, msg):
696                 if self.__err:
697                         self.deferred.errback(self.__err)
698                 else:
699                         try:
700                                 self.deferred.callback(self.__result)
701                         except Exception as e:
702                                 self.deferred.errback(e)
703
704         def run(self):
705                 try:
706                         self.__result = self.__asyncFunc()
707                 except Exception as e:
708                         self.__err = e
709                 finally:
710                         self.__pump.send(0)