viewer basically implemented. going to wire information/links on the site
authorMarcin Koziej <marcin@lolownia.org>
Wed, 4 Dec 2013 18:45:09 +0000 (19:45 +0100)
committerMarcin Koziej <marcin@lolownia.org>
Wed, 4 Dec 2013 18:45:09 +0000 (19:45 +0100)
13 files changed:
apps/catalogue/management/commands/importbooks.py
apps/catalogue/models/tag.py
apps/picture/migrations/0001_initial.py [new file with mode: 0644]
apps/picture/migrations/0002_auto__add_field_picture_areas.py [new file with mode: 0644]
apps/picture/migrations/__init__.py [new file with mode: 0644]
apps/picture/models.py
apps/picture/tasks.py [new file with mode: 0644]
apps/picture/templates/picture/picture_viewer.html
apps/picture/views.py
apps/wolnelektury_core/static/css/master.book.css
apps/wolnelektury_core/static/css/master.picture.css
apps/wolnelektury_core/static/js/picture.js
wolnelektury/settings/static.py

index 323755e..2a44d5a 100644 (file)
@@ -11,7 +11,7 @@ from django.core.management.base import BaseCommand
 from django.core.management.color import color_style
 from django.core.files import File
 from catalogue.utils import trim_query_log
 from django.core.management.color import color_style
 from django.core.files import File
 from catalogue.utils import trim_query_log
-
+from librarian.picture import ImageStore
 from wolnelektury_core.management.profile import profile
 
 from catalogue.models import Book
 from wolnelektury_core.management.profile import profile
 
 from catalogue.models import Book
@@ -60,8 +60,16 @@ class Command(BaseCommand):
                     print "Importing %s.%s" % (file_base, ebook_format)
         book.save()
 
                     print "Importing %s.%s" % (file_base, ebook_format)
         book.save()
 
-    def import_picture(self, file_path, options):
-        picture = Picture.from_xml_file(file_path, overwrite=options.get('force'))
+    def import_picture(self, file_path, options, continue_on_error=True):
+        try:
+            image_store = ImageStore(os.path.dirname(file_path))
+            picture = Picture.from_xml_file(file_path, image_store=image_store, overwrite=options.get('force'))
+        except Exception, ex:
+            if continue_on_error:                
+                print "%s: %s" % (file_path, ex)
+                return
+            else:
+                raise ex
         return picture
 
     #    @profile
         return picture
 
     #    @profile
index b0c75fb..a6f395c 100644 (file)
@@ -19,6 +19,7 @@ TAG_CATEGORIES = (
     ('theme', _('theme')),
     ('set', _('set')),
     ('book', _('book')),
     ('theme', _('theme')),
     ('set', _('set')),
     ('book', _('book')),
+    ('thing', _('thing')), # things shown on pictures
 )
 
 
 )
 
 
diff --git a/apps/picture/migrations/0001_initial.py b/apps/picture/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..f61a3de
--- /dev/null
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'Picture'
+        db.create_table(u'picture_picture', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=120)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=120)),
+            ('sort_key', self.gf('django.db.models.fields.CharField')(max_length=120, db_index=True)),
+            ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)),
+            ('changed_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)),
+            ('xml_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
+            ('image_file', self.gf('sorl.thumbnail.fields.ImageField')(max_length=100)),
+            ('html_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
+        ))
+        db.send_create_signal(u'picture', ['Picture'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'Picture'
+        db.delete_table(u'picture_picture')
+
+
+    models = {
+        u'picture.picture': {
+            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Picture'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_file': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['picture']
\ No newline at end of file
diff --git a/apps/picture/migrations/0002_auto__add_field_picture_areas.py b/apps/picture/migrations/0002_auto__add_field_picture_areas.py
new file mode 100644 (file)
index 0000000..44ad804
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'Picture.areas'
+        db.add_column(u'picture_picture', 'areas',
+                      self.gf('jsonfield.fields.JSONField')(default='{}'),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'Picture.areas'
+        db.delete_column(u'picture_picture', 'areas')
+
+
+    models = {
+        u'picture.picture': {
+            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Picture'},
+            'areas': ('jsonfield.fields.JSONField', [], {'default': "'{}'"}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_file': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['picture']
\ No newline at end of file
diff --git a/apps/picture/migrations/__init__.py b/apps/picture/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
index dc92c43..cd91a3f 100644 (file)
@@ -1,4 +1,4 @@
-from django.db import models
+from django.db import models, transaction
 import catalogue.models
 from django.db.models import permalink
 from sorl.thumbnail import ImageField
 import catalogue.models
 from django.db.models import permalink
 from sorl.thumbnail import ImageField
@@ -10,13 +10,21 @@ from django.core.cache import get_cache
 from catalogue.utils import split_tags
 from django.utils.safestring import mark_safe
 from fnpdjango.utils.text.slughifi import slughifi
 from catalogue.utils import split_tags
 from django.utils.safestring import mark_safe
 from fnpdjango.utils.text.slughifi import slughifi
+from picture import tasks
+from StringIO import StringIO
+import jsonfield
+import itertools
+
+from PIL import Image
 
 from django.utils.translation import ugettext_lazy as _
 from newtagging import managers
 from os import path
 
 
 
 from django.utils.translation import ugettext_lazy as _
 from newtagging import managers
 from os import path
 
 
-picture_storage = FileSystemStorage(location=path.join(settings.MEDIA_ROOT, 'pictures'), base_url=settings.MEDIA_URL + "pictures/")
+picture_storage = FileSystemStorage(location=path.join(
+        settings.MEDIA_ROOT, 'pictures'),
+        base_url=settings.MEDIA_URL + "pictures/")
 
 
 class Picture(models.Model):
 
 
 class Picture(models.Model):
@@ -31,6 +39,9 @@ class Picture(models.Model):
     changed_at  = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
     xml_file    = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
     image_file  = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
     changed_at  = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
     xml_file    = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
     image_file  = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
+    html_file   = models.FileField('html_file', upload_to="html", storage=picture_storage)
+    areas       = jsonfield.JSONField(_('picture areas'), default='{}', editable=False)
+
     objects     = models.Manager()
     tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
     tags        = managers.TagDescriptor(catalogue.models.Tag)
     objects     = models.Manager()
     tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
     tags        = managers.TagDescriptor(catalogue.models.Tag)
@@ -64,7 +75,7 @@ class Picture(models.Model):
         return ('picture.views.picture_detail', [self.slug])
 
     @classmethod
         return ('picture.views.picture_detail', [self.slug])
 
     @classmethod
-    def from_xml_file(cls, xml_file, image_file=None, overwrite=False):
+    def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False):
         """
         Import xml and it's accompanying image file.
         If image file is missing, it will be fetched by librarian.picture.ImageStore
         """
         Import xml and it's accompanying image file.
         If image file is missing, it will be fetched by librarian.picture.ImageStore
@@ -76,10 +87,7 @@ class Picture(models.Model):
         from librarian.picture import WLPicture, ImageStore
         close_xml_file = False
         close_image_file = False
         from librarian.picture import WLPicture, ImageStore
         close_xml_file = False
         close_image_file = False
-        # class SimpleImageStore(object):
-        #     def path(self_, slug, mime_type):
-        #         """Returns the image file. Ignores slug ad mime_type."""
-        #         return image_file
+
 
         if image_file is not None and not isinstance(image_file, File):
             image_file = File(open(image_file))
 
         if image_file is not None and not isinstance(image_file, File):
             image_file = File(open(image_file))
@@ -88,12 +96,12 @@ class Picture(models.Model):
         if not isinstance(xml_file, File):
             xml_file = File(open(xml_file))
             close_xml_file = True
         if not isinstance(xml_file, File):
             xml_file = File(open(xml_file))
             close_xml_file = True
-
+        
         try:
             # use librarian to parse meta-data
         try:
             # use librarian to parse meta-data
-            picture_xml = WLPicture.from_file(xml_file,
-                                              image_store=ImageStore(picture_storage.path('images')))
-                    # image_store=SimpleImageStore
+            if image_store is None:
+                image_store = ImageStore(picture_storage.path('images'))
+            picture_xml = WLPicture.from_file(xml_file, image_store=image_store)
 
             picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
             if not created and not overwrite:
 
             picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
             if not created and not overwrite:
@@ -102,35 +110,80 @@ class Picture(models.Model):
             picture.title = picture_xml.picture_info.title
 
             motif_tags = set()
             picture.title = picture_xml.picture_info.title
 
             motif_tags = set()
+            thing_tags = set()
+            area_data = {'themes':{}, 'things':{}}
+
             for part in picture_xml.partiter():
             for part in picture_xml.partiter():
-                for motif in part['themes']:
-                    tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
+                if picture_xml.frame:
+                    c = picture_xml.frame[0]
+                    part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
+                if part.get('object', None) is not None:
+                    objname = part['object']
+                    tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
                     if created:
                     if created:
-                        tag.name = motif
+                        tag.name = objname
                         tag.sort_key = sortify(tag.name)
                         tag.save()
                         tag.sort_key = sortify(tag.name)
                         tag.save()
-                    motif_tags.add(tag)
+                    thing_tags.add(tag)
+                    area_data['things'][tag.slug] = {
+                        'object': part['object'],
+                        'coords': part['coords'],
+                        }
+                else:
+                    for motif in part['themes']:
+                        tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
+                        if created:
+                            tag.name = motif
+                            tag.sort_key = sortify(tag.name)
+                            tag.save()
+                        motif_tags.add(tag)
+                        area_data['themes'][tag.slug] = {
+                            'theme': motif,
+                            'coords': part['coords']
+                            }
 
             picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
 
             picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
-                list(motif_tags)
+                list(motif_tags) + list(thing_tags)
+            picture.areas = area_data
 
             if image_file is not None:
                 img = image_file
             else:
                 img = picture_xml.image_file()
 
 
             if image_file is not None:
                 img = image_file
             else:
                 img = picture_xml.image_file()
 
-            # FIXME: hardcoded extension
-            picture.image_file.save(path.basename(picture_xml.image_path), File(img))
+            modified = cls.crop_to_frame(picture_xml, img)
+            # FIXME: hardcoded extension - detect from DC format or orginal filename
+            picture.image_file.save(path.basename(picture_xml.image_path), File(modified))
 
             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
             picture.save()
 
             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
             picture.save()
+            tasks.generate_picture_html(picture.id)
+
+        except Exception, ex:
+            print "Rolling back a transaction"
+            transaction.rollback()
+            raise ex
+
         finally:
             if close_xml_file:
                 xml_file.close()
             if close_image_file:
                 image_file.close()
         finally:
             if close_xml_file:
                 xml_file.close()
             if close_image_file:
                 image_file.close()
+
+        transaction.commit()
+
         return picture
 
         return picture
 
+    @classmethod
+    def crop_to_frame(cls, wlpic, image_file):
+        if wlpic.frame is None:
+            return image_file
+        img = Image.open(image_file)
+        img = img.crop(itertools.chain(*wlpic.frame))
+        contents = StringIO()
+        img.save(contents, format='png', quality=95)
+        return contents
+
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
@@ -187,8 +240,9 @@ class Picture(models.Model):
             tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
             tags = split_tags(tags)
 
             tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
             tags = split_tags(tags)
 
-            short_html = unicode(render_to_string('picture/picture_short.html',
-                {'picture': self, 'tags': tags}))
+            short_html = unicode(render_to_string(
+                    'picture/picture_short.html',
+                    {'picture': self, 'tags': tags}))
 
             if self.id:
                 get_cache('permanent').set(cache_key, short_html)
 
             if self.id:
                 get_cache('permanent').set(cache_key, short_html)
diff --git a/apps/picture/tasks.py b/apps/picture/tasks.py
new file mode 100644 (file)
index 0000000..b823f26
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright Â© Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from datetime import datetime
+from traceback import print_exc
+from celery.task import task
+from django.conf import settings
+import picture.models
+from django.core.files.base import ContentFile
+from django.template.loader import render_to_string
+import librarian.picture
+
+
+@task
+def generate_picture_html(picture_id):
+    pic = picture.models.Picture.objects.get(pk=picture_id)
+
+    
+    html_text = unicode(render_to_string('picture/picture_info.html', {
+                'things': pic.areas['things'], 
+                'themes': pic.areas['themes'],
+                }))
+    pic.html_file.save("%s.html" % pic.slug, ContentFile(html_text))
+
index 46772db..df0160c 100644 (file)
@@ -1,6 +1,7 @@
 {% load i18n %}
 {% load static from staticfiles %}
 {% load chunks compressed catalogue_tags %}
 {% load i18n %}
 {% load static from staticfiles %}
 {% load chunks compressed catalogue_tags %}
+{% load thumbnail %}
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
@@ -19,8 +20,8 @@
     <body>
         <div id="menu">
             <ul>
     <body>
         <div id="menu">
             <ul>
-                <li><a class="menu" href="#themes">{% trans "Themes" %}</a></li>
-                <li><a class="menu" href="#objects">{% trans "Objects" %}</a></li>
+<!--                <li><a class="menu" href="#themes">{% trans "Themes" %}</a></li>
+                <li><a class="menu" href="#objects">{% trans "Objects" %}</a></li>-->
                <!-- XXX do we have this? -->
                 <li><a class="menu" href="#nota_red">{% trans "Edit. note" %}</a></li>
                 <li><a class="menu" href="#info">{% trans "Infobox" %}</a></li>
                <!-- XXX do we have this? -->
                 <li><a class="menu" href="#nota_red">{% trans "Edit. note" %}</a></li>
                 <li><a class="menu" href="#info">{% trans "Infobox" %}</a></li>
         <div id="info">
             {# book_info book #}
         </div>
         <div id="info">
             {# book_info book #}
         </div>
-<!-- do something with the logo        <div id="header">
-            <a href="/"><img src="{% static "img/logo-220.png" %}" alt="Wolne Lektury" /></a>
-        </div>-->
+       {{ picture.html_file.read|safe }}
 
        <!-- main picture view -->
        <div id="picture-view">
          <ul class="toolbar">
 
        <!-- main picture view -->
        <div id="picture-view">
          <ul class="toolbar">
-           <li class="button plus"><a href="#">&#x2795;<!-- heavy plus sign --></a></li>
-           <li class="button minus"><a href="#">&#x2796;<!-- heavy minus sign --></a>
+           <li class="square button plus"><a href="#">&#x2795;<!-- heavy plus sign --></a></li>
+           <li class="square button minus"><a href="#">&#x2796;<!-- heavy minus sign --></a>
            </li>
            </li>
+           <li class="button"><a href="#picture-objects" class="dropdown">{% trans "Objects" %}</a></li>
+           <li class="button"><a href="#picture-themes" class="dropdown">{% trans "Themes" %}</a></li>
          </ul>
          </ul>
-         <div class="picture-wrap">
-           <img class="canvas" src="{{ picture.image_file.url }}"/>
+         <div class="picture-wrap {% if picture.image_file|is_portrait %}portrait{% endif %}">
+           {% thumbnail picture.image_file "700x500" as pic %}
+           <img class="canvas initial" src="{{pic.url}}"/>
+           {% endthumbnail %}
+           <img class="canvas loading original" src="{{ picture.image_file.url }}"/>
          </div>
        </div>
         {{ piwik_tag|safe }}
          </div>
        </div>
         {{ piwik_tag|safe }}
index dcc3807..27ddaf1 100644 (file)
@@ -10,7 +10,8 @@ from picture.models import Picture
 def picture_list(request, filter=None, template_name='catalogue/picture_list.html'):
     """ generates a listing of all books, optionally filtered with a test function """
 
 def picture_list(request, filter=None, template_name='catalogue/picture_list.html'):
     """ generates a listing of all books, optionally filtered with a test function """
 
-    pictures_by_author, orphans = Picture.picture_list()
+    pictures_by_author, orphans = Picture.picture_list(
+        filter={'image_file__isnull':False})
     books_nav = SortedDict()
     for tag in pictures_by_author:
         if pictures_by_author[tag]:
     books_nav = SortedDict()
     for tag in pictures_by_author:
         if pictures_by_author[tag]:
@@ -21,7 +22,7 @@ def picture_list(request, filter=None, template_name='catalogue/picture_list.htm
 
 
 def picture_list_thumb(request, filter=None, template_name='picture/picture_list_thumb.html'):
 
 
 def picture_list_thumb(request, filter=None, template_name='picture/picture_list_thumb.html'):
-    picture_list = Picture.objects.all()
+    picture_list = Picture.objects.filter(image_file__isnull=False)
     return render_to_response(template_name, locals(),
                               context_instance=RequestContext(request))
 
     return render_to_response(template_name, locals(),
                               context_instance=RequestContext(request))
 
index be54927..80cc978 100644 (file)
@@ -89,7 +89,7 @@ img {
 }
 
 
 }
 
 
-#toc, #themes, #nota_red, #info {
+#toc, #themes, #nota_red, #info, #objects {
     position: fixed;
     left: 0em;
     top: 1.5em;
     position: fixed;
     left: 0em;
     top: 1.5em;
@@ -122,7 +122,7 @@ img {
     z-index: 99;
 }
 
     z-index: 99;
 }
 
-#toc ol, #themes ol {
+#toc ol, #themes ol, #objects ol {
     list-style: none;
     padding: 0;
     margin: 0;
     list-style: none;
     padding: 0;
     margin: 0;
index d541d27..c4e952c 100644 (file)
@@ -1,34 +1,96 @@
+
+#picture-view {
+    width: 100%;
+    position:absolute;
+    top: 0;
+    bottom: 0;
+}
  
  
-#picture-view img.canvas {
-    width: 700px;
+#picture-view .picture-wrap {
     margin: 3rem auto 1rem auto;
     display: block;
     margin: 3rem auto 1rem auto;
     display: block;
-//    position: fixed;
+//    position: absolute;
+}
+
+#picture-view .picture-wrap {
+    width: 700px;
+}
+
+#picture-view .picture-wrap {
+    height: 500px;
 }
 
 }
 
-#picture-view .toolbar {
+
+.picture-wrap img.original {
+    width: 100%;
+}
+
+.picture-wrap img.loading {
+    margin-left: -10000px;
+}
+
+.picture-wrap .mark {
+    border: 2px solid rgba(200, 200, 200, 0.7);
+    position: absolute;
+    display: block;
+}
+
+.picture-wrap .mark .label {
+    position: absolute;
+    display: none; /*block;*/
+    color: rgba(200, 200, 200, 0.8);
+    font-size: 0.9rem;
+    bottom: -1.5em;
+}
+
+
+.toolbar {
     position: fixed;
     top: 1.5rem;
     position: fixed;
     top: 1.5rem;
-    right: 0rem;
-    background: #333;
+    left: 0rem;
     color: #FFF;
     color: #FFF;
-    opacity: 0.9;
     z-index: 99;
     list-style: none; 
     padding: 0;
     margin: 0;
 }
 
     z-index: 99;
     list-style: none; 
     padding: 0;
     margin: 0;
 }
 
-#picture-view .toolbar .button a {
+.toolbar ul {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    font-size: 0.8rem;
+    background-color: #222;
+}
+
+.toolbar .button a {
+    background: #333;
+    opacity: 0.9;
     display: block;
     height:1.5rem;
     display: block;
     height:1.5rem;
-    width:1.5rem;
+
     text-align: center;
     color: #FFF;
     padding: 0.2rem;
     text-decoration: none;
 }
 
     text-align: center;
     color: #FFF;
     padding: 0.2rem;
     text-decoration: none;
 }
 
+.toolbar .button .dropdown-body a {
+    height: 1.2rem;
+}
+
+#picture-view .toolbar .button.square a {
+    width:1.5rem;
+}
+
+li.button {
+    clear: both;
+}
+li.button.square {
+    clear: none;
+    float: left;
+}
+
 #picture-view .toolbar .button a:link,
 #picture-view .toolbar .button a:visited {
     color: #FFF;
 #picture-view .toolbar .button a:link,
 #picture-view .toolbar .button a:visited {
     color: #FFF;
index dca2d13..bcbdbc1 100644 (file)
 
 (function($) {
 
 (function($) {
-  $.widget('wl.pictureviewer', {
-
-    options: {
-      steps: 6, // steps of zoom
-      max: 300, // max zoom in percent
-      plus_button: undefined,
-      minus_button: undefined
-    },
-
-    _create: function() {
-      var self = this;
-      self._zoom = 0;
-      self.initial_size = [ 
-       self.element.width(),
-       self.element.height()
-      ];
-      self.initial_position = self.element.offset();
-
-      self.element.css({
-       'margin': 0,
-       'position': 'absolute',
-      });
-      self.element.offset(self.initial_position);
-
-      if (self.options.plus_button)
-       self.options.plus_button.click(
-         function(ev) { self.zoom(1); });
-      if (self.options.minus_button)
-       self.options.minus_button.click(
-         function(ev) { self.zoom(-1); });
-
-      function contain(event, ui) {
-       var fix = self.allowedPosition(ui.position);
-       console.log("fix: ", fix);
-       if (fix !== undefined) {
-         return false;
-       };
-      };
-      self.element.draggable({drag: contain});
-
-      return self;
-    },
-
-    zoom: function(steps) {
-      var t = this._zoom + steps;
-      return this.zoomTo(t);
-    },
-
-    zoomForStep: function(step) {
-      // 0 => initial
-      // max_step-1 => max %
-      return 100 + (this.options.max - 100) / this.options.steps * step
-    },
-
-    zoomTo: function(level) {
-      if (level < 0 || level > this.options.steps)
-       return;
-      var ratio = this.zoomForStep(level) / 100;
-      var new_width  = ratio * this.initial_size[0];
-      var new_height = ratio * this.initial_size[1];
-      var target = {
-       'width': new_width,
-       'left': this.initial_position.left - (new_width - this.initial_size[0])/2,
-       'top': this.initial_position.top - (new_height - this.initial_size[1])/2,
-      };
-      this._zoom = level;
-      this.element.animate(target, 200); // default duration=400
-    },
-
-    allowedPosition: function(off) {
-      var x = undefined, fix_x = undefined;
-      var y = undefined, fix_y = undefined;
-      var w = this.element.width();
-      var h = this.element.height();
-      var cw = $(window).width();
-      var ch = $(window).height();
-      var off = off || this.element.offset();
-
-      if (w <= cw) {
-       var x = off.left;
-       if (x < 0) 
-         fix_x = 0;
-       if (x + w > cw)
-         fix_x = cw - w;
-      } else {
-       if (x > 0)
-         fix_x = 0;
-       if (x + w < cw)
-         fix_x = cw - w;
-      }
-
-      if (h <= ch) {
-       var y = off.top;
-       if (y < 0)
-         fix_y = 0;
-       if (y + h > ch)
-         fix_y = ch - h;
-      } else {
-       if (y > 0)
-         fix_y = 0;
-       if (y + h < ch)
-         fix_y = ch - h;
-      }
-      if (fix_x !== undefined || fix_y !== undefined)
-       return { top: fix_y, left: fix_x };
-      return undefined;
-
-    },
-  });
+    $.widget('wl.pictureviewer', {
+
+       options: {
+           steps: 6, // steps of zoom
+           max: -1, // max zoom in percent
+           plus_button: undefined,
+           minus_button: undefined,
+           height: 500, // height to scale to initially
+       },
+
+
+       _create: function() {
+           var self = this;
+           /* Calibrate */
+           self._zoom = 0;
+
+           // the initial thumbnailed picture
+
+
+           var img = self.element.find('img.initial').get(0);
+
+           self.initial_size = [ 
+               img.naturalWidth,
+               img.naturalHeight
+           ];
+
+           self.element.width(self.initial_size[0]);
+           self.element.height(self.initial_size[1]);
+           
+           self.initial_position = self.element.offset();
+
+           var original = self.element.find('img.original').get(0);
+           self._original = false;
+           
+           if (self.options.max <= 0) {
+               self.options.max = original.naturalWidth
+                   * 100 / self.initial_size[0];
+           }
+
+           self.element.css({
+               'margin': 0,
+           });
+
+           self.element.offset(self.initial_position);
+           self.element.draggable({containment:"parent"});
+
+           if (self.options.plus_button)
+               self.options.plus_button.click(
+                   function(ev) { self.zoom(1); });
+           if (self.options.minus_button)
+               self.options.minus_button.click(
+                   function(ev) { self.zoom(-1); });
+
+           self.options.areas_links.hover(function() {
+               $this = $(this);
+               var coords = $this.data("coords");
+               this._picture_mark = self.createMark({
+                   label: $this.text(),
+                   coords: coords,
+               });
+           }, function() {
+               $(this._picture_mark).remove();
+               this._picture_mark = undefined;
+           });
+           return self;
+       },
+
+       natural_size: function() { 
+           var img = this.element.find('img').get(0);
+           return [ img.naturalWidth, img.naturalHeight ] 
+       },
+
+       currentZoom: function() { return this._zoom; },
+
+       initOriginal: function() {
+           if (!this._original) {
+               this.element.find("img.initial").remove();
+               this.element.find("img.loading").removeClass("loading");
+               this._original = true;
+           }
+
+       },
+
+       zoom: function(steps) {
+           this.initOriginal();
+           var t = this._zoom + steps;
+           return this.zoomTo(t);
+       },
+
+       zoomForStep: function(step) {
+           // 0 => initial
+           // max_step-1 => max %
+           return 100 + (this.options.max - 100) / this.options.steps * step
+       },
+
+       zoomTo: function(level) {
+           if (level < 0 || level > this.options.steps)
+               return;
+           var ratio = this.zoomForStep(level) / 100;
+           var new_width  = ratio * this.initial_size[0];
+           var new_height = ratio * this.initial_size[1];
+           var target = {
+               'width': new_width,
+               'left': Math.max(0, 
+                                this.initial_position.left 
+                                - (new_width - this.initial_size[0])/2),
+               'top': Math.max(0, 
+                               this.initial_position.top 
+                               - (new_height - this.initial_size[1])/2),
+           };
+
+           this._zoom = level;
+           this.element.animate(target, 200); // default duration=400
+       },
+
+       allowedPosition: function(off) {
+           var x = undefined, fix_x = undefined;
+           var y = undefined, fix_y = undefined;
+           var w = this.element.width();
+           var h = this.element.height();
+           var cw = $(window).width();
+           var ch = $(window).height();
+           var off = off || this.element.offset();
+
+           if (w <= cw) {
+               var x = off.left;
+               if (x < 0) 
+                   fix_x = 0;
+               if (x + w > cw)
+                   fix_x = cw - w;
+           } else {
+               if (x > 0)
+                   fix_x = 0;
+               if (x + w < cw)
+                   fix_x = cw - w;
+           }
+
+           if (h <= ch) {
+               var y = off.top;
+               if (y < 0)
+                   fix_y = 0;
+               if (y + h > ch)
+                   fix_y = ch - h;
+           } else {
+               if (y > 0)
+                   fix_y = 0;
+               if (y + h < ch)
+                   fix_y = ch - h;
+           }
+           if (fix_x !== undefined || fix_y !== undefined)
+               return { top: fix_y, left: fix_x };
+           return undefined;
+
+       },
+
+       // mark
+       // {
+       //  label: "...",
+       //  coords: [x, y, w, h]
+       // }
+       createMark: function(mark) {
+           var $mark = $('<div class="mark"><div class="label">' + 
+                         mark.label + '</div></div>');
+           var ratio = this.zoomForStep(this.currentZoom()) *
+               this.initial_size[0] / (100 * this.natural_size()[0]);
+           var scale = function (v) { 
+               return v * ratio; 
+           }
+           if (mark.coords[1][0] < 0 || mark.coords[1][1] < 0) { // whole
+               var s = self.natural_size();
+               if (mark.coords[1][0] < 0) mark.coords[1][0] = s[0];
+               if (mark.coords[1][1] < 0) mark.coords[1][1] = s[1];
+           }
+
+           var coords = [[scale(mark.coords[0][0]), scale(mark.coords[0][1])],
+                         [scale(mark.coords[1][0]), scale(mark.coords[1][1])]];
+           this.element.append($mark);
+           $mark.width(coords[1][0] - coords[0][0]);
+           $mark.height(coords[1][1] - coords[0][1]);
+           $mark.css({left: coords[0][0], top: coords[0][1]});
+           return $mark.get(0);
+       },
+    });
 }(jQuery));
 
 
 $(document).ready(function(){
 }(jQuery));
 
 
 $(document).ready(function(){
-  $("img.canvas").pictureviewer({
-    plus_button: $(".toolbar .button.plus"),
-    minus_button: $(".toolbar .button.minus")
-  });
+    $(".picture-wrap").pictureviewer({
+       plus_button: $(".toolbar .button.plus"),
+       minus_button: $(".toolbar .button.minus"),
+       areas_links: $("#picture-objects a, #picture-themes a"),
+    });
+
+    $.highlightFade.defaults.speed = 3000;
+
+    $('.toolbar a.dropdown').each(function() {
+       $t = $(this);
+       $($t.attr('href')).hide().insertAfter(this);
+    });
+
+    $('.toolbar a.dropdown').toggle(function() {
+       $(this).addClass('selected');
+       $($(this).attr('href')).slideDown('fast');
+    }, function() {
+       $(this).removeClass('selected');
+       $($(this).attr('href')).slideUp('fast');
+    });
+
+
 });
 
 });
 
index 28d42e9..7dd1a82 100644 (file)
@@ -128,5 +128,5 @@ PIPELINE_JS_COMPRESSOR = None
 PIPELINE_COMPILERS = (
     'pyscss_compiler.PySCSSCompiler',
 )
 PIPELINE_COMPILERS = (
     'pyscss_compiler.PySCSSCompiler',
 )
-PIPELINE_PYSCSS_BINARY = '/usr/bin/env pyscss'
+PIPELINE_PYSCSS_BINARY = '/usr/bin/env /home/staging/wolnelektury.pl/ve/bin/pyscss'
 PIPELINE_PYSCSS_ARGUMENTS = ''
 PIPELINE_PYSCSS_ARGUMENTS = ''