Compare commits

...

29 Commits

Author SHA1 Message Date
  octogene f297cb56bb Fix slide update on download 3 years ago
  octogene e0de7e6a61 Fix MetMuseum search url 3 years ago
  octogene 3266c08ba3 Refactor viewer presentation loading. 4 years ago
  octogene 5b6c759a00 [Editor[ Fix presentation title 4 years ago
  octogene 8e3a8cc56a Refactoring viewer & editor 4 years ago
  octogene 08a4d80302 Fix setting panel crash on opening 4 years ago
  octogene e3b54d7d21 Removed grid drawing 5 years ago
  octogene 481c2a4b89 Refactor Search: 5 years ago
  octogene a4e357d60f Merge origin/dev 5 years ago
  octogene 8bc0b0975d Cleaning & Fixing 5 years ago
  octogene 200978de75 Code refactoring + Tool 5 years ago
  octogene 7b758ebe24 Fix Viewer 6 years ago
  octogene 2507af8e8f Force fullscreen 6 years ago
  octogene 53938b7f67 Refactor drag'n drop for Editor. 6 years ago
  octogene d0a230814d [Editor]: Add scroll to slides view. 6 years ago
  octogene c998ee4996 Code cleaning 6 years ago
  octogene f3fde343f8 Fix Viewer dialog. 6 years ago
  octogene 4530569182 Update doc & Readme 6 years ago
  octogene 94cea73fcf Update translation 6 years ago
  octogene d443c377f9 [Search]: Add dynamic font size for TextInput 6 years ago
  octogene 7dfee34aee Fix ActionBar behavior for editor. 6 years ago
  octogene cb90549965 Update translation 6 years ago
  octogene 8c7445819e Add dynamic font size. 6 years ago
  octogene dfaca624bb Make Viewer UI more dynamic & fixes. 6 years ago
  octogene 26abe8368d Search UI size fixes 6 years ago
  octogene c664c6f2cb Fix locales loading for android 6 years ago
  octogene a0ee0d1047 Include magnet.py from kivy-garden. 6 years ago
  octogene 8270a48f04 Add platform detection & various fixes. 6 years ago
  octogene 83198a68fa Refactored Viewer double tap actions. 6 years ago
23 changed files with 708 additions and 460 deletions
Split View
  1. +2
    -2
      README.md
  2. +0
    -9
      docs/installation.rst
  3. +1
    -0
      docs/interface.rst
  4. BIN
      docs/locale/fr/LC_MESSAGES/index.mo
  5. BIN
      docs/locale/fr/LC_MESSAGES/installation.mo
  6. +18
    -21
      docs/locale/fr/LC_MESSAGES/installation.po
  7. BIN
      docs/locale/fr/LC_MESSAGES/interface.mo
  8. +16
    -12
      docs/locale/fr/LC_MESSAGES/interface.po
  9. BIN
      docs/locale/fr/LC_MESSAGES/sphinx.mo
  10. BIN
      docs/locale/fr/LC_MESSAGES/usage.mo
  11. +13
    -10
      docs/locale/fr/LC_MESSAGES/usage.po
  12. +19
    -5
      hadaly/__main__.py
  13. +145
    -155
      hadaly/app.py
  14. +52
    -31
      hadaly/data/locales/po/fr.po
  15. +58
    -0
      hadaly/data/search_engines.json
  16. +16
    -4
      hadaly/data/settings_panel.json
  17. +40
    -28
      hadaly/editor.kv
  18. +48
    -35
      hadaly/editor.py
  19. +117
    -0
      hadaly/magnet.py
  20. +2
    -2
      hadaly/search.kv
  21. +12
    -15
      hadaly/search.py
  22. +47
    -24
      hadaly/viewer.kv
  23. +102
    -107
      hadaly/viewer.py

+ 2
- 2
README.md View File

@ -5,9 +5,9 @@
This sofware is currently in alpha.
Hadaly is a presentation software allowing to manipulate slides, in various ways during presentation. The goal is to offer a practical presentation software for art historians or any other individual who wants to have a bit more interaction with their presentations.
Hadaly is an interactive presentation software. It allows to manipulate slides in various ways during presentation. The goal is to offer a practical presentation software for art historians or any art enthusiasts.
It contains a presentation editor and a viewer. The editor allows you to build presentations by creating slides from pictures and by adding information about them (e.g: artist, title and year). And, well, the viewer allows you to show and manipulate the slides.
It contains an editor, viewer and a search engine. The editor allows you to build presentations by creating slides which are a combination of an image and informations (e.g: artist, title and year). The viewer allows you to show and manipulate the slides. The search engine allows you create slides from different online sources.
Presentations are saved as \*.opah files which contains all the slides image files and their metadata (JSON).


+ 0
- 9
docs/installation.rst View File

@ -19,15 +19,6 @@ Requirements
pip install lxml Pillow kivy
- kivy.garden.magnet
- kivy.garden.filechooserthumbview
::
pip install kivy-garden
garden install magnet
::
git clone https://github.com/octogene/hadaly.git


+ 1
- 0
docs/interface.rst View File

@ -280,3 +280,4 @@ Shortcuts
- ``Ctrl-d`` : Delete drawings.
- ``Left`` : Go to previous slide.
- ``Right`` : Go to next slide.
- ``Tab`` : Fast slide switching.

BIN
docs/locale/fr/LC_MESSAGES/index.mo View File


BIN
docs/locale/fr/LC_MESSAGES/installation.mo View File


+ 18
- 21
docs/locale/fr/LC_MESSAGES/installation.po View File

@ -8,10 +8,10 @@ msgstr ""
"PO-Revision-Date: 2015-06-01 15:56+0200\n"
"Last-Translator: Bogdan 'Octogene' Cordier <ooctogene@gmail.com>\n"
"Language-Team: French <>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 1.5\n"
@ -49,32 +49,21 @@ msgstr "Pillow >= 2.8.1"
msgid "kivy >= 1.9.0"
msgstr "kivy >= 1.9.0"
#: ../../installation.rst:22
msgid "kivy.garden.magnet"
msgstr "kivy.garden.magnet"
#: ../../installation.rst:32
msgid ""
"For now, no setup or binary are available, you have to clone the repository:"
msgstr ""
"Actuellement, aucun setup ni binaire ne sont disponibles, il vous faut "
"cloner le dépôt:"
#: ../../installation.rst:40
#: ../../installation.rst:29
msgid "Windows"
msgstr "Windows"
#: ../../installation.rst:42
#: ../../installation.rst:31
msgid "Current version was only tested on Windows 7."
msgstr "La version actuelle n'a été testé que sous Windows 7."
#: ../../installation.rst:44
#: ../../installation.rst:33
msgid "Download setup at : https://github.com/octogene/hadaly/releases/latest"
msgstr ""
"Téléchargez l'installateur : "
"https://github.com/octogene/hadaly/releases/latest"
#: ../../installation.rst:47
#: ../../installation.rst:36
msgid "Android"
msgstr "Android"
@ -86,14 +75,22 @@ msgstr "python >= 2.7 (compatible avec python 3.4)"
msgid "lxml >= 3.4"
msgstr "lxml >= 3.4"
#: ../../installation.rst:23
msgid "kivy.garden.filechooserthumbview"
msgstr "kivy.garden.filechooserthumbview"
#: ../../installation.rst:49
#: ../../installation.rst:38
msgid "Soon..."
msgstr "Bientôt..."
#~ msgid "kivy.garden.magnet"
#~ msgstr "kivy.garden.magnet"
#~ msgid "kivy.garden.filechooserthumbview"
#~ msgstr "kivy.garden.filechooserthumbview"
#~ msgid ""
#~ "For now, no setup or binary are available, you have to clone the repository:"
#~ msgstr ""
#~ "Actuellement, aucun setup ni binaire ne sont disponibles, il vous faut "
#~ "cloner le dépôt:"
#~ msgid "python >= 2.7.7 (compatible with python 3.4)"
#~ msgstr "python >= 2.7.7 (compatible avec python 3.4)"


BIN
docs/locale/fr/LC_MESSAGES/interface.mo View File


+ 16
- 12
docs/locale/fr/LC_MESSAGES/interface.po View File

@ -5,15 +5,15 @@ msgstr ""
"Project-Id-Version: Hadaly 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-31 05:25+0200\n"
"PO-Revision-Date: 2015-06-01 16:42+0200\n"
"PO-Revision-Date: 2015-07-14 22:47+0100\n"
"Last-Translator: Bogdan 'Octogene' Cordier <ooctogene@gmail.com>\n"
"Language-Team: French <>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 1.5\n"
"X-Generator: Lokalize 2.0\n"
#: ../../interface.rst:18
msgid "Interface"
@ -84,8 +84,8 @@ msgid ""
"Left click on slide, drag it on a corner of the screen and drop it, it "
"should be removed from presentation."
msgstr ""
"En maintenant un clic gauche sur la diapo, déplacez là sur un bord de l'écran "
"et relâchez, elle devrait être supprimée de la présentation. "
"En maintenant un clic gauche sur la diapo, déplacez là sur un bord de "
"l'écran et relâchez, elle devrait être supprimée de la présentation. "
#: ../../interface.rst:91
msgid "Re-order slides"
@ -138,8 +138,8 @@ msgid ""
"slightly better image (depending on the source)."
msgstr ""
"Cliquez sur l'image que vous souhaitez ajouter pour faire apparaître une "
"fenêtre montrant l'image avec une qualité légèrement supérieure (en fonction "
"de la source)."
"fenêtre montrant l'image avec une qualité légèrement supérieure (en fonction"
" de la source)."
#: ../../interface.rst:136
msgid "Click on `Add to presentation` to begin download."
@ -216,7 +216,8 @@ msgstr "Passer à la diapo 'x' :"
#: ../../interface.rst:199
msgid "Double click on bottom right corner of the screen and select slide:"
msgstr ""
"Double clic sur le coin inférieur droit de l'écran et sélectionnez la diapo :"
"Double clic sur le coin inférieur droit de l'écran et sélectionnez la diapo "
":"
#: ../../interface.rst:207
msgid "Compare current slide with another one:"
@ -277,8 +278,8 @@ msgid ""
"Move the slider of the toolbar to the right to increase line size (Mouse "
"wheel also works)."
msgstr ""
"Déplacez le curseur de la barre d'outil à droite pour augmenter la taille du "
"trait (La molette fonctionne aussi)."
"Déplacez le curseur de la barre d'outil à droite pour augmenter la taille du"
" trait (La molette fonctionne aussi)."
#: ../../interface.rst:266
msgid "Options"
@ -313,8 +314,8 @@ msgid ""
"'New title' : Show/Configure presentation title (`see <#edit-presentation-"
"title>`_)"
msgstr ""
"'Nouveau titre' : Montre/Configure le titre de la présentation (`voir <"
"#edit-presentation-title>`_)"
"'Nouveau titre' : Montre/Configure le titre de la présentation (`voir "
"<#edit-presentation-title>`_)"
#: ../../interface.rst:35
msgid "|plus| : Add slide to presentation. ( `see <#add-new-slide>`_)"
@ -368,4 +369,7 @@ msgstr "Pour effacer les dessins, cliquez sur |eraser|."
msgid "Click on |circle| to show the color picker"
msgstr "Cliquez sur |circle| pour afficher le sélectionneur de couleur."
#: ../../interface.rst:283
msgid "``Tab`` : Fast slide switching."
msgstr "``Tab`` : Changement rapide de diapo."

BIN
docs/locale/fr/LC_MESSAGES/sphinx.mo View File


BIN
docs/locale/fr/LC_MESSAGES/usage.mo View File


+ 13
- 10
docs/locale/fr/LC_MESSAGES/usage.po View File

@ -1,4 +1,3 @@
#
# Bogdan Cordier <bogdan.cordier@gmail.com>, 2015.
# Bogdan 'Octogene' Cordier <ooctogene@gmail.com>, 2015.
msgid ""
@ -6,7 +5,7 @@ msgstr ""
"Project-Id-Version: Hadaly 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-31 05:25+0200\n"
"PO-Revision-Date: 2015-05-31 13:12+0200\n"
"PO-Revision-Date: 2015-07-14 22:15+0100\n"
"Last-Translator: Bogdan 'Octogene' Cordier <ooctogene@gmail.com>\n"
"Language-Team: French <>\n"
"MIME-Version: 1.0\n"
@ -14,7 +13,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 1.5\n"
"X-Generator: Lokalize 2.0\n"
#: ../../usage.rst:2
msgid "Usage"
@ -28,12 +27,16 @@ msgstr "GNU/Linux"
msgid "Windows"
msgstr "Windows"
#: ../../usage.rst:16
msgid ""
"For now, to directly open a presentation file (\\*.opah) you'll have to "
"drag'n drop the file on the hadaly.exe executable or the shortcut."
#: ../../usage.rst:21
msgid "Or open *.opah files like any other by double clicking on it."
msgstr ""
"Actuellement, pour ouvrir directement un fichier de présentation (\\*.opah) "
"vous devrez faire un glisser-déposer du fichier sur l'exécutable hadaly.exe "
"ou son raccourci."
"Ou ouvrez les fichiers *.opah comme n'importe quel autre fichier en double "
"cliquant dessus."
#~ msgid ""
#~ "For now, to directly open a presentation file (\\*.opah) you'll have to "
#~ "drag'n drop the file on the hadaly.exe executable or the shortcut."
#~ msgstr ""
#~ "Actuellement, pour ouvrir directement un fichier de présentation (\\*.opah) "
#~ "vous devrez faire un glisser-déposer du fichier sur l'exécutable hadaly.exe "
#~ "ou son raccourci."

+ 19
- 5
hadaly/__main__.py View File

@ -5,14 +5,28 @@ import os, sys
def main(args=None):
from .app import HadalyApp
from kivy.utils import platform
from kivy.logger import Logger
import locale
import gettext
current_locale, encoding = locale.getdefaultlocale()
abspath = os.path.abspath(os.path.dirname(sys.argv[0]))
langpath = abspath + '/data/locales/'
language = gettext.translation('hadaly', langpath,
[current_locale])
if platform == 'android':
try:
from jnius import autoclass
except ImportError:
Logger.debug('Application: Unable to import jnius.')
current_locale = autoclass(str('java.util.Locale')).getDefault().toString()
Logger.debug('hadaly {locale}'.format(locale=current_locale))
language = gettext.translation('hadaly', 'hadaly/data/locales/',[current_locale], fallback=True)
else:
current_locale, encoding = locale.getdefaultlocale()
abspath = os.path.abspath(os.path.dirname(sys.argv[0]))
langpath = abspath + '/data/locales/'
language = gettext.translation('hadaly', langpath,
[current_locale])
language.install()
HadalyApp().run()


+ 145
- 155
hadaly/app.py View File

@ -4,30 +4,33 @@ import shutil
from kivy import require
require('1.9.0')
require('1.9.1')
import os, json
from sys import argv
from .meta import version as app_version
import urllib
from urllib.parse import urlencode
import re
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
from urllib.parse import urlparse
from urllib.parse import quote
from lxml import html
import tempfile
from PIL import Image
import tarfile
from kivy.config import Config
Config.set('graphics', 'fullscreen', 'auto')
Config.set('kivy', 'log_level', 'debug')
from kivy.app import App
from kivy.core.window import Window
from kivy.properties import StringProperty, ListProperty, DictProperty, NumericProperty
from kivy.uix.screenmanager import ScreenManager, SlideTransition, FadeTransition
from kivy.properties import StringProperty, ListProperty, DictProperty
from kivy.uix.screenmanager import ScreenManager, FadeTransition, SwapTransition
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.factory import Factory
@ -37,17 +40,14 @@ from .viewer import SlideBox
from kivy.logger import Logger
from kivy.network.urlrequest import UrlRequest
from .search import ItemButton
from kivy.uix.filechooser import FileChooserIconView, FileChooserListView
from kivy.uix.filechooser import FileChooserListView
class HadalyApp(App):
presentation = DictProperty({'app': ('hadaly', app_version), 'title': 'New Title', 'slides': []})
slides_list = ListProperty()
filename = StringProperty('')
filename = StringProperty(None)
dirname = StringProperty('')
dirname = StringProperty(None)
use_kivy_settings = True
@ -68,12 +68,16 @@ class HadalyApp(App):
return root
def build_config(self, config):
config.add_section('general')
config.add_section('viewer')
config.add_section('editor')
config.add_section('search')
config.set('general', 'switch_on_start', '0')
config.set('editor', 'autosave_time', '15')
config.set('editor', 'autosave', '0')
config.set('editor', 'font_size', '12')
config.set('editor', 'last_dir', os.path.expanduser('~'))
config.set('editor', 'cols', '6')
config.set('viewer', 'thumb', '1')
config.set('viewer', 'thumb_pos', 'bottom left')
config.set('viewer', 'font_size', '15')
@ -83,21 +87,21 @@ class HadalyApp(App):
def build_settings(self, settings):
settings.add_json_panel('Hadaly',
self.config,
'data/settings_panel.json')
'hadaly/data/settings_panel.json')
def on_start(self):
self.tempdir = tempfile.mkdtemp()
self.engines = json.load(open('hadaly/data/search_engines.json'))
try:
if argv[1].endswith('.opah'):
self.load_slides(os.path.dirname(argv[1]), [os.path.basename(argv[1])])
Logger.info('Application: file \'{file}\' loaded'.format(file=self.filename))
self.root.current = 'viewer'
Logger.info('Application: File {file} loaded.'.format(file=self.filename))
if self.config.getint('general', 'switch_on_start') == 1:
self.root.current = 'viewer'
except IndexError:
pass
Logger.debug('Application: {msg}'.format(msg = 'No file input'))
def load_slides(self, path, filename):
try:
with tarfile.open(os.path.join(path, filename[0]), 'r:*') as tar:
tar.extractall(path=self.tempdir)
@ -109,7 +113,7 @@ class HadalyApp(App):
self.show_popup(_('Error'), _('No file selected'))
else:
if len(self.root.current_screen.slides_view.grid_layout.children) > 0:
self.clear()
self.create_presentation()
try:
with open(os.path.join(self.tempdir, 'presentation.json'), 'r') as fd:
@ -120,8 +124,7 @@ class HadalyApp(App):
self.dirname = path
self.filename = filename[0]
self.presentation = data
self.presentation_title = data['title']
self.presentation['slides'] = data['slides']
for slide in reversed(self.presentation['slides']):
# TODO: Remove tmp in filename on save
@ -131,56 +134,33 @@ class HadalyApp(App):
self.presentation['slides'][index]['thumb_src'] = thumb_src
self.presentation['slides'][index]['img_src'] = img_src
img_slide = Factory.Slide(img_src=str(img_src),
thumb_src=str(thumb_src),
img_slide = Factory.Slide(img_src=img_src,
thumb_src=thumb_src,
artist=slide['artist'],
title=slide['title'],
year=slide['year']
)
self.presentation['slides'][index]['texture_size'] = img_slide.texture_size
drag_slide = Factory.DraggableSlide(img=img_slide, app=self)
self.root.current_screen.slides_view.grid_layout.add_widget(drag_slide)
def clear(self):
def create_presentation(self):
""" Remove all slides from Editor's Grid Layout & Viewer's Carousel.
Empty slides list and restore presentation title to default value.
"""
self.root.current_screen.slides_view.grid_layout.clear_widgets()
self.root.get_screen('viewer').carousel.clear_widgets()
self.presentation = {'app': ('hadaly', __main__.__version__), 'title': 'New Title', 'slides': []}
try:
self.root.get_screen('viewer').carousel.clear_widgets()
except AttributeError:
Logger.debug('Viewer screen not yet initialized, no widgets in '
'carousel to remove.')
del self.presentation.slides[:]
self.presentation.title = 'New Title'
self.filename = self.dirname = ''
def update_presentation(self, type, old_index, new_index):
"""
:param type: type of update to do : 'mv' (move) or 'rm' (remove) or 'update'
:param old_index: previous index of item in grid layout as an integer.
:param new_index: new index of item in grid layout as an integer.
"""
if type == 'rm':
Logger.debug('Application: Removing presentation item.')
self.presentation['slides'].pop(old_index)
try:
self.root.get_screen('viewer').update_carousel()
except AttributeError:
Logger.exception('Application: Viewer dialog not yet added !')
elif type == 'mv':
Logger.debug('Application: Moving presentation item.')
try:
Logger.debug('Application: Moved {slide} at position (in grid layout) {index} to {new_index}'.format(
slide=self.presentation['slides'][old_index],
index=old_index, new_index=new_index))
item = self.presentation['slides'].pop(old_index)
self.presentation['slides'].insert(new_index, item)
try:
self.root.get_screen('viewer').update_carousel()
except AttributeError:
Logger.exception('Application: Viewer dialog not yet added !')
except TypeError:
Logger.exception('Application: Only one slide in presentation view. (BUG)')
elif type == 'update':
Logger.debug('Application: Updating presentation.')
self.presentation['slides'][old_index] = self.root.get_screen('editor').slides_view.grid_layout.children[
old_index].img.get_slide_info()
def show_open(self):
popup = Factory.OpenDialog()
popup.open()
@ -188,6 +168,8 @@ class HadalyApp(App):
def show_file_explorer(self):
popup = Popup(size_hint=(0.8, 0.8))
file_explorer = FileChooserListView(filters=['*.jpg', '*.png', '*.jpeg'])
if os.path.exists(self.config.get('editor', 'last_dir')):
file_explorer.path = self.config.get('editor', 'last_dir')
file_explorer.bind(on_submit=self.show_add_slide)
file_explorer.popup = popup
popup.content = file_explorer
@ -200,7 +182,7 @@ class HadalyApp(App):
If action == 'save', dialog is not shown and file is
saved based on self.dirname and self.filename.
"""
if len(self.presentation['slides']) == 0:
if not self.presentation['slides']:
self.show_popup(_('Error'), _('Nothing to save...'))
else:
if self.filename and action == 'save':
@ -210,7 +192,7 @@ class HadalyApp(App):
popup.open()
def save(self, path, filename):
"""Save file.
"""Save presentation as *.opah file.
:param path: path where to save to as string
:param filename: filename of file to save as string
@ -221,7 +203,7 @@ class HadalyApp(App):
self.filename = filename
self.dirname = path
tar = tarfile.open(os.path.join(path, filename), 'w')
tar = tarfile.open(os.path.join(path, filename), 'a')
# Add image file to *.opah file
try:
@ -268,6 +250,18 @@ class HadalyApp(App):
"""
self.root.current_screen.carousel.index = index
def add_slide_to_compare(self, index):
slide = SlideBox(slide=self.presentation['slides'][index])
self.root.current_screen.box.add_widget(slide, 0)
# Change scatter position to compensate screen split.
pos = self.root.current_screen.carousel.current_slide.viewer.pos
self.root.current_screen.carousel.current_slide.viewer.pos = (pos[0] / 2, pos[1])
def rm_slide_to_compare(self):
self.root.current_screen.box.remove_widget(self.root.current_screen.box.children[0])
pos = self.root.current_screen.carousel.current_slide.viewer.pos
self.root.current_screen.carousel.current_slide.viewer.pos = (pos[0] * 2, pos[1])
def compare_slide(self, index=None, action='add'):
"""Add new SlideBox to ViewerScreen based on SlideButton index.
@ -278,6 +272,7 @@ class HadalyApp(App):
slide = SlideBox(slide=self.presentation['slides'][index])
self.root.current_screen.box.add_widget(slide, 0)
# Change scatter position to compensate screen split.
# TODO : Find and fix behavior.
pos = self.root.current_screen.carousel.current_slide.viewer.pos
self.root.current_screen.carousel.current_slide.viewer.pos = (pos[0] / 2, pos[1])
elif action == 'rm':
@ -285,16 +280,15 @@ class HadalyApp(App):
pos = self.root.current_screen.carousel.current_slide.viewer.pos
self.root.current_screen.carousel.current_slide.viewer.pos = (pos[0] * 2, pos[1])
def set_title(self):
def set_presentation_title(self):
popup = Factory.TitleDialog()
popup.open()
def show_add_slide(self, original_src, *args):
"""Show dialog to add a slide.
:param img_source: image path as string.
"""
original_src.popup.dismiss()
self.config.set('editor', 'last_dir', os.path.dirname(original_src.selection[0]))
thumb_src = self.create_thumbnail(original_src.selection[0])
slide_popup = SlideInfoDialog(slide=Slide(img_src=original_src.selection[0],
thumb_src=thumb_src,
@ -308,7 +302,7 @@ class HadalyApp(App):
:param slide: slide as object
"""
# Logger.debug('Application: Adding to presentation {slide}'.format(slide=slide.get_slide_info()))
Logger.debug('Application: Adding to presentation {slide}'.format(slide=slide.get_slide_info()))
img_slide = DraggableSlide(img=slide, app=self)
self.presentation['slides'].insert(0, slide.get_slide_info())
self.root.get_screen('editor').slides_view.grid_layout.add_widget(img_slide)
@ -335,6 +329,12 @@ class HadalyApp(App):
popup.content = Label(text=msg, id='label')
popup.open()
def switch_to_viewer(self):
if not self.presentation['slides']:
self.show_popup(_('Error'), _('Presentation is empty...\n Please add a slide first.'))
else:
self.root.current = 'viewer'
def search_flickr(self, term):
"""make a search request on flickr and return results
@ -344,7 +344,7 @@ class HadalyApp(App):
api_key = '624d3a7086d14e85f1422430f0b889a1'
base_url = 'https://api.flickr.com/services/rest/?'
method = 'flickr.photos.search'
params = urllib.urlencode([('method', method),
params = urlencode([('method', method),
('text', term),
('api_key', api_key),
('format', 'json'),
@ -357,20 +357,24 @@ class HadalyApp(App):
return result
def request(self, url):
url = urllib.quote(url, safe="%/:=&?~#+!$,;'@()*[]")
url = quote(url, safe="%/:=&?~#+!$,;'@()*[]")
req = UrlRequest(url, debug=True)
req.wait()
return req.result
def download_img(self, url, slide, wait=False):
pb = ProgressBar(id='_pb')
self.progress_dialog = Popup(title='Downloading...',
self.progress_dialog = Popup(title=_('Downloading...'),
size_hint=(0.5, 0.2), content=pb, auto_dismiss=False)
url = urllib.quote(url, safe="%/:=&?~#+!$,;'@()*[]")
url = quote(url, safe="%/:=&?~#+!$,;'@()*[]")
path = os.path.join(self.tempdir,
os.path.basename(urlparse(url).path))
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"}
req = UrlRequest(url, self.reload_slide, file_path=path, on_progress=self.show_download_progress,
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 5.1; rv:31.0) "
"Gecko/20100101 Firefox/31.0"}
url = url.replace("https", "http")
req = UrlRequest(url, self.reload_slide,
file_path=path,
on_progress=self.show_download_progress,
req_headers=headers, debug=True)
self.progress_dialog.open()
@ -389,128 +393,108 @@ class HadalyApp(App):
for slide in slides:
img_src = urlparse(slide.img.img_src)
if img_src[0] == 'http' and img_src[2].endswith(filename):
Logger.debug('Application: Found slide ! Updating image source...')
slide.img.img_src = os.path.join(self.tempdir, filename)
Logger.debug('Application: Found slide ! '
'Updating image source...')
# Update presentation info
idx = self.presentation['slides'].index(slide.img.get_slide_info())
slide.img.img_src = self.presentation['slides'][idx]['img_src'] = os.path.join(self.tempdir, filename)
Logger.debug('Application: {src}'.format(src=slide.img.img_src))
slide.img.thumb_src = self.create_thumbnail(slide.img.img_src)
slide.img.thumb_src = self.presentation['slides'][idx]['thumb_src'] =self.create_thumbnail(slide.img.img_src)
slide.img.update_texture_size()
self.update_presentation('update', 0, 0)
self.root.current = 'editor'
def get_flickr_url(self, photo, size):
"""construct a source url to a flickr photo.
:param photo: photo information in json format.
:param size: desired size of the photo :
s small square 75x75
q large square 150x150
t thumbnail, 100 on longest side
m small, 240 on longest side
n small, 320 on longest side
- medium, 500 on longest side
z medium 640, 640 on longest side
c medium 800, 800 on longest side
b large, 1024 on longest side*
o original image, either a jpg, gif or png, depending on source format
:return url:
"""
url = 'https://farm{farm_id}.staticflickr.com/{server_id}/{id}_{secret}_{size}.jpg'
url = url.format(farm_id=photo['farm'], server_id=photo['server'], id=photo['id'],
secret=photo['secret'], size=size)
return url
def search_term(self, term, engine, page):
engines = {
'met': ('http://www.metmuseum.org/'
'collection/the-collection-online/search'
'?ft={term}&ao=on&rpp={rpp}&pg={page}'),
'getty': ('http://search.getty.edu/'
'gateway/search'
'?q={term}&cat=highlight&f="Open+Content+Images"&rows={rpp}&srt=&dir=s&dsp=0&img=0&pg={page}')
}
params = urlencode({self.engines[engine]['params']['term']: term,
self.engines[engine]['params']['rpp']: self.config.get('search', 'search_rpp'),
self.engines[engine]['params']['page']: page})
url = engines[engine].format(term=term,
rpp=self.config.get('search', 'search_rpp'),
page=page)
clean_url = urllib.quote(url, safe="%/:=&?~#+!$,;'@()*[]")
url = ''.join((self.engines[engine]['base_url'], params))
UrlRequest(clean_url, on_success=self.parse_results, debug=True)
UrlRequest(url, on_success=self.parse_results, debug=True)
def parse_results(self, request, data):
tree = html.fromstring(data)
search_screen = self.root.get_screen('search')
results = []
if 'metmuseum' in urlparse(request.url).hostname:
if self.engines[urlparse(request.url).hostname]['results']['format'] == 'html':
tree = html.fromstring(data)
try:
total_pages = tree.xpath(
'//li[starts-with(@id, "phcontent_0_phfullwidthcontent_0_paginationWidget_rptPagination_paginationLineItem_")]//a/text()')
search_screen.box.total_pages = max([int(n) for n in total_pages])
total_results = re.sub("[^0-9]", "", tree.xpath(self.engines[urlparse(request.url).hostname]['results']['total_results'])[0])
if not total_results or int(total_results) == 0:
raise ValueError
except ValueError:
self.show_popup(_('Error'), _('No results found.'))
return
objects = tree.xpath('//div[starts-with(@class, "list-view-object ")]')
for object in objects:
result = {}
result['title'] = object.xpath('./div[@class="list-view-object-info"]/a/div[@class="objtitle"]/text()')[
0]
try:
result['artist'] = \
object.xpath('./div[@class="list-view-object-info"]/div[@class="artist"]/text()')[::2][0]
except IndexError:
result['artist'] = 'Unknown'
result['thumb'] = urllib.quote(object.xpath('./div[@class="list-view-thumbnail"]//img/@src')[0],
safe="%/:=&?~#+!$,;'@()*[]")
object_info = object.xpath('./div[@class="list-view-object-info"]/div[@class="objectinfo"]/text()')
result['year'] = object_info[0].strip('Dates: ')
results.append(result)
elif 'getty' in urlparse(request.url).hostname:
if isinstance(tree.xpath(self.engines[urlparse(request.url).hostname]['results']['total_pages']), list):
search_screen.box.total_pages = tree.xpath(self.engines[urlparse(request.url).hostname]['results']['total_pages'])[0]
else:
search_screen.box.total_pages = tree.xpath(self.engines[urlparse(request.url).hostname]['results']['total_pages'])
for entry in tree.xpath(self.engines[urlparse(request.url).hostname]['results']['entries']):
artist = entry.xpath(self.engines[urlparse(request.url).hostname]['results']['artist'])[0]
title = entry.xpath(self.engines[urlparse(request.url).hostname]['results']['title'])[0]
date = entry.xpath(self.engines[urlparse(request.url).hostname]['results']['date'])[0]
thumb = entry.xpath(self.engines[urlparse(request.url).hostname]['results']['thumb'])[0]
obj_link = entry.xpath(self.engines[urlparse(request.url).hostname]['results']['obj_link'])[0]
results.append({'title': title,
'artist': artist,
'year': date,
'thumb': quote(thumb, safe="%/:=&?~#+!$,;'@()*[]"),
'obj_link': obj_link}
)
elif self.engines[urlparse(request.url).hostname]['results']['format'] == 'json':
from ast import literal_eval
from functools import reduce
try:
search_screen.box.total_pages = tree.xpath('//td[@class="cs-page"]//input/@count')[0]
if int(re.sub("[^0-9]", "", tree.xpath('//strong[@id="cs-results-count"]//text()')[0])) == 0:
total_results = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['total_results']), data)
if int(total_results) == 0:
raise ValueError
except ValueError:
self.show_popup(_('Error'), _('No results found.'))
return
artists = tree.xpath(
'//div[@class="cs-result-data-brief"]//td[* = "Creator:" or * = "Maker Name:"]/following-sibling::td[1]/p/text()')
titles = tree.xpath(
'//div[@class="cs-result-data-brief"]//td[* = "Title:" or * = "Primary Title:"]/following-sibling::td[1]/p[@class="cs-record-link"]/a/strong/text()')
dates = tree.xpath(
'//div[@class="cs-result-data-brief"]//td[* = "Date:"]/following-sibling::td[1]/p/text()')
thumb = tree.xpath('//img[@class="cs-result-thumbnail"]/@src')
obj_link = tree.xpath(
'//div[@class="cs-result-data-brief"]//td[* = "Title:" or * = "Primary Title:"]/following-sibling::td[1]/p[@class="cs-record-link"]/a/@href')
entries = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['entries']), data)
search_screen.box.total_pages = int(total_results / len(entries))
results = [{'title': a, 'artist': b, 'year': c, 'thumb': urllib.quote(d, safe="%/:=&?~#+!$,;'@()*[]"),
'obj_link': e}
for a, b, c, d, e in zip(titles, artists, dates, thumb, obj_link)]
for entry in entries:
artist = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['artist']), entry)
title = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['title']), entry)
date = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['date']), entry)
thumb = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['thumb']), entry)
obj_link = reduce(dict.__getitem__, literal_eval(self.engines[urlparse(request.url).hostname]['results']['obj_link']), entry)
results.append({'title': title,
'artist': artist,
'year': date,
'thumb': quote(thumb, safe="%/:=&?~#+!$,;'@()*[]"),
'obj_link': obj_link}
)
for photo in results:
Logger.debug('Search (MET): Loading {url}'.format(url=photo['thumb']))
photo['thumb'] = photo['thumb'].replace("https", "http")
Logger.debug('Search : Loading {url}'.format(url=photo['thumb']))
image = ItemButton(photo=photo, source=photo['thumb'], keep_ratio=True)
search_screen.box.grid.add_widget(image)
search_screen.box.status = "Page {page} on {total_page}".format(page=search_screen.box.current_page,
search_screen.box.status = _('Page {page} on {total_page}').format(page=search_screen.box.current_page,
total_page=search_screen.box.total_pages)
def on_stop(self):
self.config.set('editor', 'last_dir', None)
try:
shutil.rmtree(self.tempdir, ignore_errors=True)
except:
Logger.exception('Application: Removing temp dir failed.')
# # TODO: Check changes and ask user to save.
# TODO: Check changes and ask user to save.
pass
class Manager(ScreenManager):
def __init__(self, **kwargs):
super(Manager, self).__init__(**kwargs)
@ -525,3 +509,9 @@ class Manager(ScreenManager):
self.get_screen('viewer').carousel.current_slide.viewer.lock()
elif key == 100 and modifier == ['ctrl'] and self.current == 'viewer':
self.get_screen('viewer').carousel.current_slide.viewer.painter.canvas.clear()
elif key == 9 and self.current == 'viewer':
self.get_screen('viewer').dialog.to_switch = True
self.get_screen('viewer').dialog.title = _('Switch to...')
self.get_screen('viewer').dialog.open()

+ 52
- 31
hadaly/data/locales/po/fr.po View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-31 14:30+0200\n"
"PO-Revision-Date: 2015-05-31 14:31+0200\n"
"POT-Creation-Date: 2015-07-14 19:03+0200\n"
"PO-Revision-Date: 2015-07-14 19:04+0100\n"
"Last-Translator: Bogdan 'Octogene' Cordier <ooctogene@gmail.com>\n"
"Language-Team: French <>\n"
"Language: fr\n"
@ -16,61 +16,61 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 1.5\n"
"X-Generator: Lokalize 2.0\n"
#: hadaly/editor.kv:55
#: hadaly/editor.kv:54
msgid "New"
msgstr "Nouveau"
#: hadaly/editor.kv:58 hadaly/editor.kv:271
#: hadaly/editor.kv:57 hadaly/editor.kv:276
msgid "Open"
msgstr "Ouvrir"
#: hadaly/editor.kv:61 hadaly/editor.kv:243
#: hadaly/editor.kv:60 hadaly/editor.kv:248
msgid "Save"
msgstr "Enregistrer"
#: hadaly/editor.kv:64
#: hadaly/editor.kv:63
msgid "Save as..."
msgstr "Enregistrer sous..."
#: hadaly/editor.kv:68
#: hadaly/editor.kv:67
msgid "Quit"
msgstr "Quitter"
#: hadaly/editor.kv:141
#: hadaly/editor.kv:146
msgid "Informations"
msgstr "Informations"
#: hadaly/editor.kv:164
#: hadaly/editor.kv:169
msgid "Artist"
msgstr "Artiste"
#: hadaly/editor.kv:177
#: hadaly/editor.kv:182
msgid "Title"
msgstr "Titre"
#: hadaly/editor.kv:191
#: hadaly/editor.kv:196
msgid "Year"
msgstr "Année"
#: hadaly/editor.kv:204
#: hadaly/editor.kv:209
msgid "Set title"
msgstr "Ajouter un titre"
#: hadaly/editor.kv:220
#: hadaly/editor.kv:225
msgid "Save file"
msgstr "Enregistrer"
#: hadaly/editor.kv:240 hadaly/editor.kv:268
#: hadaly/editor.kv:245 hadaly/editor.kv:273
msgid "Cancel"
msgstr "Annuler"
#: hadaly/editor.kv:248
#: hadaly/editor.kv:253
msgid "Open file"
msgstr "Ouvrir"
#: hadaly/search.kv:26 hadaly/search.py:56
#: hadaly/search.kv:26 hadaly/search.py:57
msgid "Select a search engine"
msgstr "Choisir un moteur de recherche"
@ -82,9 +82,13 @@ msgstr "Ajouter à la présentation"
msgid "Downloading file..."
msgstr "Téléchargement en cours..."
#: hadaly/app.py:109 hadaly/app.py:112 hadaly/app.py:210 hadaly/app.py:459
#: hadaly/app.py:486 hadaly/search.py:43 hadaly/search.py:57
#: hadaly/search.py:117
#: hadaly/app.py:93 hadaly/app.py:151
msgid "New Title"
msgstr "Sans Titre "
#: hadaly/app.py:109 hadaly/app.py:112 hadaly/app.py:209 hadaly/app.py:346
#: hadaly/app.py:465 hadaly/app.py:492 hadaly/search.py:44 hadaly/search.py:58
#: hadaly/search.py:118
msgid "Error"
msgstr "Erreur"
@ -96,40 +100,57 @@ msgstr "Fichier non valide"
msgid "No file selected"
msgstr "Aucun fichier sélectionné"
#: hadaly/app.py:200
#: hadaly/app.py:199
msgid "File explorer"
msgstr "Explorateur de fichiers"
#: hadaly/app.py:210
#: hadaly/app.py:209
msgid "Nothing to save..."
msgstr "Rien à enregistrer"
#: hadaly/app.py:459 hadaly/app.py:486
#: hadaly/app.py:346
msgid ""
"Presentation is empty...\n"
" Please add a slide first."
msgstr ""
"La présentation est vide...\n"
"Veuillez ajouter une diapo."
#: hadaly/app.py:379
msgid "Downloading..."
msgstr "Téléchargement en cours..."
#: hadaly/app.py:465 hadaly/app.py:492
msgid "No results found."
msgstr "Aucun résultats trouvés."
#: hadaly/search.py:44
#: hadaly/app.py:514
#, python-brace-format
msgid "Page {page} on {total_page}"
msgstr "Page {page} sur {total_page}"
#: hadaly/search.py:45
msgid "Please enter a search term."
msgstr "Veuillez entrer un terme de recherche."
#: hadaly/search.py:58
#: hadaly/search.py:59
msgid "Please select a search engine."
msgstr "Veuillez sélectionnez un moteur de recherche."
#: hadaly/search.py:117
#: hadaly/search.py:118
msgid "High-res image not available, downloading inferior quality."
msgstr ""
"Image en haute résolution non disponible, une version de qualité inférieure "
"a été téléchargée."
#: hadaly/viewer.py:36
msgid "Switch to..."
msgstr "Passer à..."
#: hadaly/viewer.py:58
#: hadaly/viewer.py:70
msgid "Compare to..."
msgstr "Comparer à..."
#: hadaly/viewer.py:86
msgid "Switch to..."
msgstr "Passer à..."
#~ msgid "Please select a search engine"
#~ msgstr "Veuillez choisir un moteur de recherche."


+ 58
- 0
hadaly/data/search_engines.json View File

@ -0,0 +1,58 @@
{
"www.metmuseum.org": {
"base_url": "https://www.metmuseum.org/api/collection/collectionlisting?showOnly=withImage&",
"params": {
"rpp": "perPage",
"artist": "artist",
"page": "page",
"term": "q",
"sort": "sortBy",
"sortings": {
"relevance": "Relevance"
},
"order": "sortOrder",
"orderings": {
"asc": "asc"
}
},
"results": {
"format": "json",
"entries": "('results',)",
"total_results": "('totalResults',)",
"title": "('title',)",
"artist": "('description',)",
"date": "('date',)",
"thumb": "('image',)",
"obj_link": "('largeImage',)"
}
},
"search.getty.edu": {
"base_url": "http://search.getty.edu/gateway/search?cat=highlight&f=\"Open+Content+Images\"&dir=s&img=1&dsp=0&",
"params": {
"rpp": "rows",
"artist": "artist",
"page": "pg",
"term": "q",
"sort": "sortBy",
"sortings": {
"relevance": "Relevance"
},
"order": "sortOrder",
"orderings": {
"asc": "asc"
}
},
"results": {
"format": "html",
"entries": "//div[@class=\"cs-result-item\"]",
"title": ".//div[@class=\"cs-result-data-brief\"]//td[* = \"Title:\" or * = \"Primary Title:\"]/following-sibling::td[1]/p[@class=\"cs-record-link\"]/a/strong/text()",
"artist": ".//div[@class=\"cs-result-data-brief\"]//td[* = \"Creator:\" or * = \"Maker Name:\"]/following-sibling::td[1]/p/text()",
"date": ".//div[@class=\"cs-result-data-brief\"]//td[* = \"Date:\"]/following-sibling::td[1]/p/text()",
"thumb": ".//img[@class=\"cs-result-thumbnail\"]/@src",
"obj_link": ".//div[@class=\"cs-result-data-brief\"]//td[* = \"Title:\" or * = \"Primary Title:\"]/following-sibling::td[1]/p[@class=\"cs-record-link\"]/a/@href",
"total_pages": "//td[@class=\"cs-page\"]//input/@count",
"total_results": "//strong[@id=\"cs-results-count\"]//text()"
}
}
}

+ 16
- 4
hadaly/data/settings_panel.json View File

@ -1,4 +1,12 @@
[
{"type":"title", "title":"General"},
{ "type":"bool",
"title":"Switch to Viewer",
"desc":"Automatically switch to Viewer when opening an *.opah file",
"section":"general",
"key":"switch_on_start" },
{"type":"title", "title":"Editor"},
{ "type":"numeric",
@ -6,13 +14,18 @@
"desc":"Size of font for slide label",
"section":"editor",
"key":"font_size" },
{ "type":"numeric",
"title":"Columns",
"desc":"Number of columns",
"section":"editor",
"key":"cols" },
{ "type":"bool",
"title":"Auto-save",
"desc":"Save every x minutes (Not yet functional)",
"section":"editor",
"key":"autosave",
"true":"auto" },
"key":"autosave" },
{ "type":"numeric",
"title":"Saving interval",
@ -32,8 +45,7 @@
"title":"Thumbnail on zoom",
"desc":"Show thumbnail on zoom",
"section":"viewer",
"key":"thumb",
"true":"auto" },
"key":"thumb" },
{ "type":"options",
"title":"Thumbnail position",


+ 40
- 28
hadaly/editor.kv View File

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
#:kivy 1.9.0
#:import win kivy.core.window
#:import expanduser os.path.expanduser
<EditorScreen@Screen>:
slides_view: _slides_view
action_bar: _action_bar
EditorView:
BoxLayout:
orientation: 'vertical'
id: build_view
ActionBar:
@ -14,8 +15,6 @@
SlidesView:
id: _slides_view
<EditorView@BoxLayout>:
<EditorBar@ContextualActionView>:
use_separator: True
ActionPrevious:
@ -25,21 +24,19 @@
ActionButton:
text: app.presentation['title']
horizontal: root.center_y
on_press: app.set_title()
on_press: app.set_presentation_title()
ActionButton:
on_press: app.show_file_explorer()
font_name: 'data/fonts/fontawesome-webfont.ttf'
font_size: '20sp'
text: u'\uf0fe'
ActionOverflow:
id: action_overflow
ActionButton:
on_press: app.root.current = 'search'
font_name: 'data/fonts/fontawesome-webfont.ttf'
font_size: '20sp'
text: u'\uf002'
ActionButton:
on_press: app.root.current = 'viewer'
on_press: app.switch_to_viewer()
font_name: 'data/fonts/fontawesome-webfont.ttf'
font_size: '20sp'
text: u'\uf06e'
@ -52,40 +49,48 @@
size: (_save_as.texture_size[0] + 10, self.size[1])
ActionButton:
text: _('New')
on_release: action_group._dropdown.select(app.clear())
on_press: app.create_presentation()
ActionButton:
text: _('Open')
on_release: action_group._dropdown.select(app.show_open())
on_press: app.show_open()
ActionButton:
text: _('Save')
on_release: action_group._dropdown.select(app.show_save('save'))
on_press: app.show_save('save')
ActionButton:
text: _('Save as...')
id: _save_as
on_release: action_group._dropdown.select(app.show_save('save_as'))
on_press: app.show_save('save_as')
ActionButton:
text: _('Quit')
on_release: action_group._dropdown.select(app.stop())
on_press: app.stop()
<SlidesView@FloatLayout>:
app: app
grid_layout: _grid_layout
modified: None
BoxLayout:
padding: [dp(20), dp(20), dp(20), dp(20)]
GridLayout:
spacing: dp(20)
cols: 8
id: _grid_layout
ScrollView:
do_scroll_x: False
GridLayout:
app: app
spacing: dp(20)
cols: app.config.getint('editor', 'cols')
height: self.minimum_height
id: _grid_layout
size_hint_y: None
<DraggableSlide>:
ncols: app.config.getint('editor', 'cols')
size_hint: None, None
size: [dp(win.Window.width / 8 - 20 - 20/8)] * 2
size: [dp(int(win.Window.width / self.ncols) - 20 - int(20/self.ncols))] * 2
<Slide>:
app: app
orientation:'vertical'
size_hint: None, None
padding: [dp(10), dp(10), dp(10), 0]
spacing: dp(2)
spacing: dp(3)
image: _image
canvas.before:
Color:
@ -99,37 +104,43 @@
keep_ratio: True
mipmap: True
Button:
size_hint: 0.2, 0.2
size_hint: 0.15, 0.25
text: u'\uf05a'
text_size: self.size
font_name: 'data/fonts/fontawesome-webfont.ttf'
font_size: '20sp'
halign: 'left'
font_size: ''.join((str(int(root.width / 8)), 'sp'))
halign: 'center'
valign: 'middle'
background_color: [1, 1, 1, 0]
border_color: [1,1,1,1]
on_press: root.show_info_panel()
on_press: root.show_info_panel(self.pos)
<SlideInfo@BoxLayout>:
<SlideInfo@Bubble>:
artist: _artist
title: _title
year: _year
font: '10sp'
arrow_pos:'left_mid'
orientation:'vertical'
size_hint: 1, 0.4
size_hint: (None, None)
size: (dp(150), dp(60))
SlideLabel:
id: _artist
font_size: root.font
SlideLabel:
id: _title
italic: True
font_size: root.font
SlideLabel:
id: _year
font_size: root.font
<SlideLabel@Label>:
font_size: app.config.getint('editor','font_size')
text_size: self.parent.width, self.height
size: self.parent.size
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
shorten: True
shorten_from: 'right'
halign: 'left'
@ -254,6 +265,7 @@
id: filechooser
on_selection: text_input.text = self.selection and self.selection[0] or ''
filters: ['*.opah']
path: expanduser('~')
TextInput:
id: text_input
text: '*.opah'


+ 48
- 35
hadaly/editor.py View File

@ -4,16 +4,19 @@ from __future__ import division, unicode_literals, absolute_import
import os
from functools import partial
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty, ObjectProperty, NumericProperty, BooleanProperty
from kivy.garden.magnet import Magnet
from kivy.properties import (StringProperty, ObjectProperty,
NumericProperty, BooleanProperty, DictProperty)
from .magnet import Magnet
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.config import Config
from PIL import Image
from kivy.graphics.opengl import GL_MAX_TEXTURE_SIZE, glGetIntegerv
from kivy.factory import Factory
from kivy.metrics import dp
class Slide(BoxLayout):
@ -27,6 +30,7 @@ class Slide(BoxLayout):
texture_size = ObjectProperty(None)
info_panel = BooleanProperty(False)
def get_slide_info(self):
return {'img_src': self.img_src,
'thumb_src': self.thumb_src,
@ -47,25 +51,31 @@ class Slide(BoxLayout):
try:
im = Image.open(self.img_src)
if max(im.size) > max_texture_size:
Logger.debug('Application : image too big for max texture size ! Resizing...')
Logger.debug('Editor : image too big for max texture size ! Resizing...')
size = self.app.resize(im.size, (max_texture_size, max_texture_size))
im.thumbnail(size, Image.ANTIALIAS)
im.save(self.img_src)
except IOError:
Logger.debug('Editor: {img_src} is not a valid filename.'.format(img_src=self.img_src))
def show_info_panel(self):
if self.info_panel:
self.info_panel = False
self.remove_widget(self.children[0])
elif not self.info_panel:
def rm_info_panel(self, *args):
self.app.root.current_screen.remove_widget(args[0])
self.info_panel = False
def show_info_panel(self, pos):
if not self.info_panel:
# self.info_panel = False
# elif not self.info_panel:
self.info_panel = True
info_panel = Factory.SlideInfo(id='info_panel')
Clock.schedule_once(partial(self.rm_info_panel, info_panel), 3)
# info_panel.font = ''.join((str(int(self.parent.height / 10)), 'sp'))
info_panel.artist.text = self.artist
info_panel.title.text = self.title
info_panel.year.text = self.year
self.add_widget(info_panel)
info_panel.pos = self.to_window(*(pos[0], pos[1] - dp(20)))
self.app.root.current_screen.add_widget(info_panel)
class SlideInfoDialog(Popup):
slide = ObjectProperty(None)
@ -87,7 +97,10 @@ class DraggableSlide(Magnet):
app = ObjectProperty(None)
old_index = NumericProperty(None)
new_index = NumericProperty(None)
no_slides = NumericProperty(None)
transitions = DictProperty({'x': 'out_expo',
'y': 'in_sine',
'size': 'out_elastic'})
duration = NumericProperty(0.5)
def on_img(self, *args):
self.clear_widgets()
@ -106,34 +119,32 @@ class DraggableSlide(Magnet):
Clock.unschedule(touch.ud['event'])
except KeyError:
Logger.exception(
'Application: Touch up passed through and unscheduled clock event could not be unscheduled. A bug...')
'Editor: Touch up passed through and unscheduled clock event could not be unscheduled. A bug...')
def on_touch_down(self, touch, *args):
if self.collide_point(*touch.pos):
if touch.is_touch:
self.create_clock(touch)
if touch.is_double_tap:
self.delete_clock(touch)
popup = SlideInfoDialog(slide=self.img)
popup.open()
else:
self.create_clock(touch)
return super(DraggableSlide, self).on_touch_down(touch)
def single_tap(self, touch, *args):
if self.collide_point(*touch.pos):
grid_layout = self.app.root.current_screen.slides_view.grid_layout
# Get position of current slide and number of slides
for index, value in enumerate(grid_layout.children):
if self == value:
self.old_index = index
self.no_slides = len(grid_layout.children)
touch.grab(self)
self.remove_widget(self.img)
self.app.root.current_screen.add_widget(self.img)
self.center = touch.pos
self.img.center = touch.pos
return True
grid_layout = self.app.root.current_screen.slides_view.grid_layout
# Get position of current slide and number of slides
for index, value in enumerate(grid_layout.children):
if self == value:
self.old_index = index
self.no_slides = len(grid_layout.children)
touch.grab(self)
self.remove_widget(self.img)
self.app.root.current_screen.add_widget(self.img)
self.center = self.to_window(*touch.pos)
self.img.center = touch.pos
return super(DraggableSlide, self).on_touch_down(touch)
@ -141,8 +152,7 @@ class DraggableSlide(Magnet):
grid_layout = self.app.root.current_screen.slides_view.grid_layout
if touch.grab_current == self:
self.img.center = touch.pos
self.img.center = self.to_window(*touch.pos)