You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

viewer.py 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # -*- coding: utf-8 -*-
  2. from kivy.uix.screenmanager import Screen
  3. from kivy.uix.scatter import Matrix
  4. from kivy.uix.scatterlayout import ScatterLayout
  5. from kivy.uix.image import Image, AsyncImage
  6. from kivy.uix.boxlayout import BoxLayout
  7. from kivy.uix.floatlayout import FloatLayout
  8. from kivy.uix.stencilview import StencilView
  9. from kivy.uix.popup import Popup
  10. from kivy.uix.button import ButtonBehavior
  11. from kivy.factory import Factory
  12. from kivy.properties import ObjectProperty, BooleanProperty, DictProperty, ListProperty, NumericProperty
  13. from kivy.vector import Vector
  14. from kivy.logger import Logger
  15. from kivy.metrics import dp
  16. from kivy.uix.colorpicker import ColorPicker
  17. class ViewerScreen(Screen):
  18. app = ObjectProperty(None)
  19. dialog = ObjectProperty(None)
  20. def on_pre_enter(self, *args):
  21. if not self.dialog:
  22. self.dialog = Factory.SlidesDialog()
  23. try:
  24. if not len(self.dialog.grid.children) == len(self.app.presentation['slides']):
  25. Logger.debug('Application: Clean \& reload carousel.')
  26. self.dialog.grid.clear_widgets()
  27. self.carousel.clear_widgets()
  28. for slide in reversed(self.app.presentation['slides']):
  29. self.dialog.grid.add_widget(Factory.SlideButton(source=slide['thumb_src'],
  30. size_hint=(None, None),
  31. keep_ratio=True, ))
  32. image = SlideBox(slide=slide)
  33. self.carousel.add_widget(image)
  34. except KeyError as msg:
  35. Logger.debug('Application: Presentations seems empty. {msg}'.format(msg=msg))
  36. def update_carousel(self):
  37. self.dialog.grid.clear_widgets()
  38. self.carousel.clear_widgets()
  39. for slide in reversed(self.app.presentation['slides']):
  40. self.dialog.grid.add_widget(Factory.SlideButton(source=slide['thumb_src'],
  41. size_hint=(None, None),
  42. keep_ratio=True, ))
  43. image = SlideBox(slide=slide)
  44. self.carousel.add_widget(image)
  45. class TouchActionArea(FloatLayout):
  46. def on_touch_down(self, touch):
  47. if self.collide_point(*touch.pos) and touch.is_double_tap and len(self.app.presentation['slides']) > 0:
  48. try:
  49. child = [child for child in self.children if child.collide_point(*touch.pos)][0]
  50. except IndexError:
  51. Logger.debug('Viewer: No TouchActionArea child touched.')
  52. return
  53. if child.name == 'center':
  54. if len(self.app.root.current_screen.box.children) < 2:
  55. Logger.info('Application: Switching to compare mode.')
  56. self.parent.dialog.to_switch = False
  57. self.parent.dialog.title = _('Compare to...')
  58. self.parent.dialog.open()
  59. else:
  60. self.app.compare_slide(action='rm')
  61. touch.ungrab(self)
  62. return True
  63. elif child.name == 'ltop':
  64. self.app.root.current = 'editor'
  65. return True
  66. elif child.name == 'rtop':
  67. pass
  68. elif child.name == 'lbottom':
  69. pass
  70. elif child.name == 'rbottom':
  71. self.parent.dialog.to_switch = True
  72. self.parent.dialog.title = _('Switch to...')
  73. self.parent.dialog.open()
  74. return False
  75. return super(TouchActionArea, self).on_touch_down(touch)
  76. class SlideBox(BoxLayout, StencilView):
  77. slide = DictProperty(None)
  78. def on_touch_down(self, touch):
  79. if not self.collide_point(*touch.pos):
  80. return False
  81. return super(SlideBox, self).on_touch_down(touch)
  82. def on_touch_up(self, touch):
  83. if not touch.grab_current == self:
  84. return False
  85. return super(SlideBox, self).on_touch_down(touch)
  86. def on_size(self, *args):
  87. try:
  88. img_zoom = [child for child in self.float_layout.children if child.id == 'img_zoom'][0]
  89. img_zoom.size = (self.size[0] / 6, (self.size[0] / 6) / img_zoom.image_ratio)
  90. img_zoom.pos = [self.pos[0], 0.05 * dp(self.size[1])]
  91. except IndexError:
  92. pass
  93. def get_caption(self):
  94. artist = self.slide['artist'].decode('utf-8')
  95. title = ''.join(('[i]', self.slide['title'].decode('utf-8'), '[/i]'))
  96. year = self.slide['year']
  97. caption = ' - '.join((artist, title, year))
  98. return caption
  99. class SlideViewer(ScatterLayout):
  100. image = ObjectProperty(None)
  101. app = ObjectProperty(None)
  102. locked = BooleanProperty(False)
  103. painter = ObjectProperty(None)
  104. def on_touch_down(self, touch):
  105. # Scaling scatter with mousewheel based on touch position.
  106. if self.collide_point(*touch.pos) and touch.is_mouse_scrolling:
  107. scale = self.scale
  108. if touch.button == 'scrolldown':
  109. scale = self.scale + 0.08
  110. elif touch.button == 'scrollup':
  111. scale = max(0.1, self.scale - 0.08)
  112. rescale = scale * 1.0 / self.scale
  113. matrix = Matrix().scale(rescale, rescale, rescale)
  114. self.apply_transform(matrix, anchor=touch.pos)
  115. self.check_slide_bbox()
  116. return False
  117. return super(SlideViewer, self).on_touch_down(touch)
  118. def on_transform_with_touch(self, touch):
  119. self.check_slide_bbox()
  120. def check_slide_bbox(self):
  121. if self.app.config.getint('viewer', 'thumb') == 1:
  122. img_point = (self.bbox[0][0] + self.bbox[1][0],
  123. self.bbox[0][1] + self.bbox[1][1])
  124. parent_point = (self.parent.size[0] + self.parent.pos[0],
  125. self.parent.size[1] + self.parent.pos[1])
  126. if not Vector.in_bbox(self.bbox[0],
  127. self.parent.pos,
  128. parent_point) or not Vector.in_bbox(img_point,
  129. self.parent.pos,
  130. parent_point):
  131. if all(child.id != 'img_zoom' for child in self.parent.children):
  132. # TODO: Change thumbnail position and size based on config.
  133. thumb = AsyncImage(source=self.image.source,
  134. id='img_zoom',
  135. size_hint=(None, None),
  136. keep_ratio=True,
  137. size=(self.parent.size[0] / 5,
  138. (self.parent.size[0] / 5) / self.image.image_ratio),
  139. pos=[0, 0.05 * self.parent.size[1]])
  140. self.parent.add_widget(thumb)
  141. elif min(self.bbox[0]) > 0:
  142. img_zoom = [child for child in self.parent.children if child.id == 'img_zoom']
  143. try:
  144. self.parent.remove_widget(img_zoom[0])
  145. except IndexError:
  146. pass
  147. def lock(self):
  148. if not self.locked:
  149. Logger.info('Application: Locking Slide.')
  150. self.locked = True
  151. self.do_rotation = False
  152. self.do_scale = False
  153. self.do_translation = False
  154. toolbar = PainterToolBar(painter=self.painter,
  155. paint_color=self.painter.tools[self.painter.current_tool]['color'],
  156. thickness=self.painter.thickness)
  157. self.painter.bind(thickness=toolbar.on_thickness)
  158. self.painter.bind(color=toolbar.on_paint_color)
  159. self.slidebox.toolbar.add_widget(toolbar)
  160. self.app.root.get_screen('viewer').carousel.scroll_timeout = 50
  161. elif self.locked:
  162. Logger.info('Application: Unlocking Slide.')
  163. self.locked = False
  164. self.do_scale = True
  165. self.do_translation = True
  166. self.do_rotation = True
  167. self.slidebox.toolbar.remove_widget(self.slidebox.toolbar.children[0])
  168. self.app.root.get_screen('viewer').carousel.scroll_timeout = 200
  169. def on_locked(self, *args):
  170. self.slidebox.toolbar.lock_btn.text = {u'\uf13e': u'\uf023', u'\uf023': u'\uf13e'}[
  171. self.slidebox.toolbar.lock_btn.text]
  172. if self.painter:
  173. self.painter.locked = {True: False, False: True}[self.locked]
  174. class PainterToolBar(BoxLayout):
  175. painter = ObjectProperty(None)
  176. paint_color = ListProperty((1, 1, 1, 1))
  177. thickness = NumericProperty(0.5)
  178. def show_color_picker(self, current_color):
  179. popup = Popup(title='Color Picker',
  180. size_hint=(0.5, 0.5))
  181. color_picker = ColorPicker(color=current_color)
  182. color_picker.bind(color=self.on_paint_color)
  183. popup.content = color_picker
  184. popup.open()
  185. def on_paint_color(self, instance, value):
  186. self.paint_color = value
  187. self.painter.color = value
  188. def on_thickness(self, instance, value):
  189. self.thickness = value
  190. self.painter.thickness = value
  191. class SlidesDialog(Popup):
  192. to_switch = BooleanProperty(False)
  193. class SlideButton(ButtonBehavior, Image):
  194. app = ObjectProperty(None)
  195. def on_release(self):
  196. if self.parent.dialog.to_switch:
  197. current_index = self.parent.children.index(self)
  198. carousel_index = list(reversed(xrange(len(self.parent.children))))
  199. self.app.switch_slide(carousel_index[current_index])
  200. else:
  201. self.app.compare_slide(self.parent.children.index(self))