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.
253 lines
9.7 KiB
253 lines
9.7 KiB
# -*- coding: utf-8 -*- |
|
|
|
from kivy.uix.screenmanager import Screen |
|
from kivy.uix.scatter import Matrix |
|
from kivy.uix.scatterlayout import ScatterLayout |
|
from kivy.uix.image import Image, AsyncImage |
|
from kivy.uix.boxlayout import BoxLayout |
|
from kivy.uix.floatlayout import FloatLayout |
|
from kivy.uix.stencilview import StencilView |
|
from kivy.uix.popup import Popup |
|
from kivy.uix.button import ButtonBehavior |
|
from kivy.factory import Factory |
|
from kivy.properties import ObjectProperty, BooleanProperty, DictProperty, ListProperty, NumericProperty |
|
from kivy.vector import Vector |
|
from kivy.logger import Logger |
|
from kivy.metrics import dp |
|
from kivy.uix.colorpicker import ColorPicker |
|
|
|
|
|
|
|
class ViewerScreen(Screen): |
|
app = ObjectProperty(None) |
|
dialog = ObjectProperty(None) |
|
|
|
def on_pre_enter(self, *args): |
|
if not self.dialog: |
|
self.dialog = Factory.SlidesDialog() |
|
try: |
|
if not len(self.dialog.grid.children) == len(self.app.presentation['slides']): |
|
Logger.debug('Application: Clean \& reload carousel.') |
|
self.dialog.grid.clear_widgets() |
|
self.carousel.clear_widgets() |
|
|
|
for slide in reversed(self.app.presentation['slides']): |
|
self.dialog.grid.add_widget(Factory.SlideButton(source=slide['thumb_src'], |
|
size_hint=(None, None), |
|
keep_ratio=True, )) |
|
image = SlideBox(slide=slide) |
|
self.carousel.add_widget(image) |
|
|
|
|
|
except KeyError as msg: |
|
Logger.debug('Application: Presentations seems empty. {msg}'.format(msg=msg)) |
|
|
|
def update_carousel(self): |
|
self.dialog.grid.clear_widgets() |
|
self.carousel.clear_widgets() |
|
for slide in reversed(self.app.presentation['slides']): |
|
self.dialog.grid.add_widget(Factory.SlideButton(source=slide['thumb_src'], |
|
size_hint=(None, None), |
|
keep_ratio=True, )) |
|
image = SlideBox(slide=slide) |
|
self.carousel.add_widget(image) |
|
|
|
class TouchActionArea(FloatLayout): |
|
|
|
def on_touch_down(self, touch): |
|
|
|
if self.collide_point(*touch.pos) and touch.is_double_tap and len(self.app.presentation['slides']) > 0: |
|
try: |
|
child = [child for child in self.children if child.collide_point(*touch.pos)][0] |
|
except IndexError: |
|
Logger.debug('Viewer: No TouchActionArea child touched.') |
|
return |
|
if child.name == 'center': |
|
if len(self.app.root.current_screen.box.children) < 2: |
|
Logger.info('Application: Switching to compare mode.') |
|
self.parent.dialog.to_switch = False |
|
self.parent.dialog.title = _('Compare to...') |
|
self.parent.dialog.open() |
|
else: |
|
self.app.compare_slide(action='rm') |
|
touch.ungrab(self) |
|
return True |
|
|
|
elif child.name == 'ltop': |
|
self.app.root.current = 'editor' |
|
return True |
|
elif child.name == 'rtop': |
|
pass |
|
elif child.name == 'lbottom': |
|
pass |
|
elif child.name == 'rbottom': |
|
self.parent.dialog.to_switch = True |
|
self.parent.dialog.title = _('Switch to...') |
|
self.parent.dialog.open() |
|
return False |
|
|
|
return super(TouchActionArea, self).on_touch_down(touch) |
|
|
|
class SlideBox(BoxLayout, StencilView): |
|
slide = DictProperty(None) |
|
|
|
def on_touch_down(self, touch): |
|
|
|
if not self.collide_point(*touch.pos): |
|
return False |
|
|
|
return super(SlideBox, self).on_touch_down(touch) |
|
|
|
def on_touch_up(self, touch): |
|
|
|
if not touch.grab_current == self: |
|
return False |
|
|
|
return super(SlideBox, self).on_touch_down(touch) |
|
|
|
def on_size(self, *args): |
|
|
|
try: |
|
img_zoom = [child for child in self.float_layout.children if child.id == 'img_zoom'][0] |
|
img_zoom.size = (self.size[0] / 6, (self.size[0] / 6) / img_zoom.image_ratio) |
|
img_zoom.pos = [self.pos[0], 0.05 * dp(self.size[1])] |
|
except IndexError: |
|
pass |
|
|
|
def get_caption(self): |
|
|
|
artist = self.slide['artist'].decode('utf-8') |
|
title = ''.join(('[i]', self.slide['title'].decode('utf-8'), '[/i]')) |
|
year = self.slide['year'] |
|
|
|
caption = ' - '.join((artist, title, year)) |
|
|
|
return caption |
|
|
|
|
|
class SlideViewer(ScatterLayout): |
|
image = ObjectProperty(None) |
|
app = ObjectProperty(None) |
|
locked = BooleanProperty(False) |
|
painter = ObjectProperty(None) |
|
|
|
def on_touch_down(self, touch): |
|
# Scaling scatter with mousewheel based on touch position. |
|
if self.collide_point(*touch.pos) and touch.is_mouse_scrolling: |
|
|
|
scale = self.scale |
|
|
|
if touch.button == 'scrolldown': |
|
scale = self.scale + 0.08 |
|
elif touch.button == 'scrollup': |
|
scale = max(0.1, self.scale - 0.08) |
|
|
|
rescale = scale * 1.0 / self.scale |
|
matrix = Matrix().scale(rescale, rescale, rescale) |
|
self.apply_transform(matrix, anchor=touch.pos) |
|
|
|
self.check_slide_bbox() |
|
return False |
|
|
|
return super(SlideViewer, self).on_touch_down(touch) |
|
|
|
def on_transform_with_touch(self, touch): |
|
|
|
self.check_slide_bbox() |
|
|
|
def check_slide_bbox(self): |
|
if self.app.config.getint('viewer', 'thumb') == 1: |
|
img_point = (self.bbox[0][0] + self.bbox[1][0], |
|
self.bbox[0][1] + self.bbox[1][1]) |
|
parent_point = (self.parent.size[0] + self.parent.pos[0], |
|
self.parent.size[1] + self.parent.pos[1]) |
|
if not Vector.in_bbox(self.bbox[0], |
|
self.parent.pos, |
|
parent_point) or not Vector.in_bbox(img_point, |
|
self.parent.pos, |
|
parent_point): |
|
|
|
if all(child.id != 'img_zoom' for child in self.parent.children): |
|
# TODO: Change thumbnail position and size based on config. |
|
thumb = AsyncImage(source=self.image.source, |
|
id='img_zoom', |
|
size_hint=(None, None), |
|
keep_ratio=True, |
|
size=(self.parent.size[0] / 5, |
|
(self.parent.size[0] / 5) / self.image.image_ratio), |
|
pos=[0, 0.05 * self.parent.size[1]]) |
|
self.parent.add_widget(thumb) |
|
|
|
elif min(self.bbox[0]) > 0: |
|
img_zoom = [child for child in self.parent.children if child.id == 'img_zoom'] |
|
try: |
|
self.parent.remove_widget(img_zoom[0]) |
|
except IndexError: |
|
pass |
|
|
|
def lock(self): |
|
if not self.locked: |
|
Logger.info('Application: Locking Slide.') |
|
self.locked = True |
|
self.do_rotation = False |
|
self.do_scale = False |
|
self.do_translation = False |
|
toolbar = PainterToolBar(painter=self.painter, |
|
paint_color=self.painter.tools[self.painter.current_tool]['color'], |
|
thickness=self.painter.thickness) |
|
self.painter.bind(thickness=toolbar.on_thickness) |
|
self.painter.bind(color=toolbar.on_paint_color) |
|
self.slidebox.toolbar.add_widget(toolbar) |
|
self.app.root.get_screen('viewer').carousel.scroll_timeout = 50 |
|
elif self.locked: |
|
Logger.info('Application: Unlocking Slide.') |
|
self.locked = False |
|
self.do_scale = True |
|
self.do_translation = True |
|
self.do_rotation = True |
|
self.slidebox.toolbar.remove_widget(self.slidebox.toolbar.children[0]) |
|
self.app.root.get_screen('viewer').carousel.scroll_timeout = 200 |
|
|
|
def on_locked(self, *args): |
|
self.slidebox.toolbar.lock_btn.text = {u'\uf13e': u'\uf023', u'\uf023': u'\uf13e'}[ |
|
self.slidebox.toolbar.lock_btn.text] |
|
if self.painter: |
|
self.painter.locked = {True: False, False: True}[self.locked] |
|
|
|
|
|
class PainterToolBar(BoxLayout): |
|
painter = ObjectProperty(None) |
|
paint_color = ListProperty((1, 1, 1, 1)) |
|
thickness = NumericProperty(0.5) |
|
|
|
def show_color_picker(self, current_color): |
|
popup = Popup(title='Color Picker', |
|
size_hint=(0.5, 0.5)) |
|
color_picker = ColorPicker(color=current_color) |
|
color_picker.bind(color=self.on_paint_color) |
|
popup.content = color_picker |
|
popup.open() |
|
|
|
def on_paint_color(self, instance, value): |
|
self.paint_color = value |
|
self.painter.color = value |
|
|
|
def on_thickness(self, instance, value): |
|
self.thickness = value |
|
self.painter.thickness = value |
|
|
|
|
|
class SlidesDialog(Popup): |
|
to_switch = BooleanProperty(False) |
|
|
|
|
|
class SlideButton(ButtonBehavior, Image): |
|
app = ObjectProperty(None) |
|
|
|
def on_release(self): |
|
if self.parent.dialog.to_switch: |
|
current_index = self.parent.children.index(self) |
|
carousel_index = list(reversed(xrange(len(self.parent.children)))) |
|
self.app.switch_slide(carousel_index[current_index]) |
|
else: |
|
self.app.compare_slide(self.parent.children.index(self))
|
|
|