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 librarian.picture import ImageStore
 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()
 
-    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
index b0c75fb..a6f395c 100644 (file)
@@ -19,6 +19,7 @@ TAG_CATEGORIES = (
     ('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
@@ -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 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
 
 
-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):
@@ -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)
+    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)
@@ -64,7 +75,7 @@ class Picture(models.Model):
         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
@@ -76,10 +87,7 @@ class Picture(models.Model):
         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))
@@ -88,12 +96,12 @@ class Picture(models.Model):
         if not isinstance(xml_file, File):
             xml_file = File(open(xml_file))
             close_xml_file = True
-
+        
         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:
@@ -102,35 +110,80 @@ class Picture(models.Model):
             picture.title = picture_xml.picture_info.title
 
             motif_tags = set()
+            thing_tags = set()
+            area_data = {'themes':{}, 'things':{}}
+
             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:
-                        tag.name = motif
+                        tag.name = objname
                         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) + \
-                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()
 
-            # 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()
+            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()
+
+        transaction.commit()
+
         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
@@ -187,8 +240,9 @@ class Picture(models.Model):
             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)
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 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">
@@ -19,8 +20,8 @@
     <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>
         <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">
-           <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 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>
-         <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 }}
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 """
 
-    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]:
@@ -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'):
-    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))
 
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;
@@ -122,7 +122,7 @@ img {
     z-index: 99;
 }
 
-#toc ol, #themes ol {
+#toc ol, #themes ol, #objects ol {
     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;
-//    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;
-    right: 0rem;
-    background: #333;
+    left: 0rem;
     color: #FFF;
-    opacity: 0.9;
     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;
-    width:1.5rem;
+
     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;
index dca2d13..bcbdbc1 100644 (file)
 
 (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(){
-  $("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_PYSCSS_BINARY = '/usr/bin/env pyscss'
+PIPELINE_PYSCSS_BINARY = '/usr/bin/env /home/staging/wolnelektury.pl/ve/bin/pyscss'
 PIPELINE_PYSCSS_ARGUMENTS = ''