From 1c64c03bd096ac6c92bee4361926611be3d22259 Mon Sep 17 00:00:00 2001
From: =?utf8?q?=C5=81ukasz=20Rekucki?= 
Date: Fri, 4 Sep 2009 13:06:59 +0200
Subject: [PATCH 01/16] Przyciski do zmiany czcionki.
---
 apps/toolbar/templates/toolbar/toolbar.html   |  2 +-
 dump_toolbar.sh                               |  1 +
 fixtures/przyciski.xml                        | 11 +++++++++-
 .../templates/explorer/panels/xmleditor.html  | 22 +------------------
 4 files changed, 13 insertions(+), 23 deletions(-)
 create mode 100755 dump_toolbar.sh
diff --git a/apps/toolbar/templates/toolbar/toolbar.html b/apps/toolbar/templates/toolbar/toolbar.html
index 29528c16..21135305 100644
--- a/apps/toolbar/templates/toolbar/toolbar.html
+++ b/apps/toolbar/templates/toolbar/toolbar.html
@@ -21,7 +21,7 @@
                 ui:action-params="{{ button.params|escape }}"
                 {% if button.key %}ui:hotkey="{{ button.key|keycode }}"{% endif %} 
                 {% if button.tooltip %}ui:tooltip="{{ button.tooltip }}"{% endif %} >
-            {{ button.label }}
+            {{ button.label|safe }}
             
             {% endfor %}
         
diff --git a/dump_toolbar.sh b/dump_toolbar.sh
new file mode 100755
index 00000000..3bac9fe6
--- /dev/null
+++ b/dump_toolbar.sh
@@ -0,0 +1 @@
+./project/manage.py dumpdata --format=xml toolbar > fixtures/przyciski.xml
diff --git a/fixtures/przyciski.xml b/fixtures/przyciski.xml
index a8ad2c19..287a0cc9 100644
--- a/fixtures/przyciski.xml
+++ b/fixtures/przyciski.xml
@@ -1,5 +1,5 @@
 
-
-  
-    Novelpages
-    novelpages
-    ({exprs: [
-
-  ["\\,\\.\\.|\\.\\,\\.|\\.\\.\\,", "..."],
-
-  ["â", ",,"] /* DOUBLE LOW-9 QUOTATION MARK */
-
-]})
-    lineregexp
-    
-    WykonujÄ operacjÄ z novel-pages.
-    
-      
-    
-  
   
     opowiadanie
     opowiadanie
-    ({tag:"opowiadanie"})
+    {"tag": "opowiadanie"}
     insert_tag
     
     
@@ -485,7 +513,7 @@
   
     osoba
     osoba
-    ({tag:"osoba"})
+    {"tag": "osoba"}
     insert_tag
     
     
@@ -496,7 +524,7 @@
   
     osoba
     osoba
-    ({tag:"osoba"})
+    {"tag": "osoba"}
     insert_tag
     
     
@@ -507,7 +535,7 @@
   
     podrozdziaÅ
     podrozdzial
-    ({tag:"naglowek_podrozdzial"})
+    {"tag": "naglowek_podrozdzial"}
     insert_tag
     
     
@@ -518,7 +546,7 @@
   
     podtytuÅ
     podtytul
-    ({tag:"podtytul"})
+    {"tag": "podtytul"}
     insert_tag
     
     
@@ -529,7 +557,7 @@
   
     powieÅÄ
     powiesc
-    ({tag:"powiesc"})
+    {"tag": "powiesc"}
     insert_tag
     
     
@@ -540,7 +568,7 @@
   
     przypis autorski
     przypis-autorski
-    ({tag:"pa"})
+    {"tag": "pa"}
     insert_tag
     
     
@@ -551,7 +579,7 @@
   
     przypis edytorski
     przypis-edytorski
-    ({tag:"pe"})
+    {"tag": "pe"}
     insert_tag
     
     
@@ -562,7 +590,7 @@
   
     przypis redaktorski
     przypis-redaktorski
-    ({tag:"pr"})
+    {"tag": "pr"}
     insert_tag
     
     
@@ -573,7 +601,7 @@
   
     przypis tÅumacza
     przypis-tlumacza
-    ({tag:"pt"})
+    {"tag": "pt"}
     insert_tag
     
     
@@ -584,7 +612,7 @@
   
     rozdziaÅ
     rozdzial
-    ({tag:"naglowek_rozdzial"})
+    {"tag": "naglowek_rozdzial"}
     insert_tag
     
     
@@ -595,7 +623,7 @@
   
     scena
     scena
-    ({tag:"naglowek_scena"})
+    {"tag": "naglowek_scena"}
     insert_tag
     
     
@@ -606,7 +634,7 @@
   
     sep. asteryks
     sep-asteryks
-    ({tag:"sekcja_asteryks"})
+    {"tag": "sekcja_asteryks"}
     insert_tag
     
     
@@ -617,7 +645,7 @@
   
     sep. linia
     sep-linia
-    ({tag:"separator_linia"})
+    {"tag": "separator_linia"}
     insert_tag
     
     
@@ -628,7 +656,7 @@
   
     sep. ÅwiatÅo
     sep-swiatlo
-    ({tag:"sekcja_swiatlo"})
+    {"tag": "sekcja_swiatlo"}
     insert_tag
     
     
@@ -636,32 +664,10 @@
       
     
   
-  
-    ÅródtytuÅ
-    srodtytul
-    ({tag:"srodtytul"})
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    strofa
-    strofa
-    ({tag"strofa"})
-    insert_tag
-    
-    
-    
-      
-    
-  
   
     strofa
     strofa
-    ({tag:"strofa"})
+    {"tag": "strofa"}
     insert_tag
     
     
@@ -672,7 +678,7 @@
   
     sÅowo obce
     slowo-obce
-    ({tag:"slowo_obce"})
+    {"tag": "slowo_obce"}
     insert_tag
     
     
@@ -683,7 +689,7 @@
   
     tagi gÅówne
     tagi-glowne
-    ({tag:"utwor"})
+    {"tag": "utwor"}
     insert_tag
     
     
@@ -694,7 +700,7 @@
   
     tytuÅ
     tytul
-    ({tag:"nazwa_utworu"})
+    {"tag": "nazwa_utworu"}
     insert_tag
     
     
@@ -705,7 +711,7 @@
   
     tytuÅ dzieÅa
     tytul-dziela
-    ({tag:"tytul_dziela"})
+    {"tag": "tytul_dziela", "attrs": {"typ": " "}}
     insert_tag
     
     
@@ -713,21 +719,10 @@
       
     
   
-  
-    UsuÅ spacjÄ
-    strip_whitespace
-    ({exprs: [ ["^\\s+|\\s+$", ""], ["\\s+", " "] ]})
-    lineregexp
-    
-    Usuwa zbÄdne spacjÄ z dokumentu.
-    
-      
-    
-  
   
     uwaga
     uwaga
-    ({tag:"uwaga"})
+    {"tag": "uwaga"}
     insert_tag
     
     
@@ -738,7 +733,7 @@
   
     wers akap.
     wers-akap
-    ({tag:"wers_akap"})
+    {"tag": "wers_akap"}
     insert_tag
     
     
@@ -749,7 +744,7 @@
   
     wers akap.
     wers-akap
-    ({tag:"wers_akap"})
+    {"tag": "wers_akap"}
     insert_tag
     
     
@@ -760,7 +755,7 @@
   
     wers cd.
     wers-cd
-    ({tag:"wers_cd"})
+    {"tag": "wers_cd"}
     insert_tag
     
     
@@ -771,7 +766,7 @@
   
     wers cd.
     wers-cd
-    ({tag:"wers_cd"})
+    {"tag": "wers_cd"}
     insert_tag
     
     
@@ -782,7 +777,7 @@
   
     wers wciÄty
     wers-wciety
-    ({tag:"wers_wciety"})
+    {"tag": "wers_wciety"}
     insert_tag
     
     
@@ -793,7 +788,7 @@
   
     wers wciÄty
     wers-wciety
-    ({tag:"wers_wciety"})
+    {"tag": "wers_wciety"}
     insert_tag
     
     
@@ -804,7 +799,7 @@
   
     www
     www
-    ({tag:"www"})
+    {"tag": "www"}
     insert_tag
     
     
@@ -815,7 +810,7 @@
   
     wyróżnienie
     wyroznienie
-    ({tag:"wyroznienie"})
+    {"tag": "wyroznienie"}
     insert_tag
     
     
@@ -826,7 +821,7 @@
   
     wywiad
     wywiad
-    ({tag:"wywiad"})
+    {"tag": "wywiad"}
     insert_tag
     
     
@@ -837,7 +832,7 @@
   
     wywiad odpowiedź
     wywiad-odpowiedz
-    ({tag:"wywiad_odp"})
+    {"tag": "wywiad_odp"}
     insert_tag
     
     
@@ -848,7 +843,7 @@
   
     wywiad pytanie
     wywiad-pytanie
-    ({tag:"wywiad_pyt"})
+    {"tag": "wywiad_pyt"}
     insert_tag
     
     
@@ -856,21 +851,10 @@
       
     
   
-  
-    ZamieÅ dywiz
-    zamien_dywiz
-    ({exprs:[ ["â","---"] ]})
-    lineregexp
-    
-    Zamienia 'â' na '---'.
-    
-      
-    
-  
   
     zastÄpnik wersu
     zastepnik-wersu
-    ({tag:"zastepnik_wersu"})
+    {"tag": "zastepnik_wersu"}
     insert_tag
     
     
@@ -878,20 +862,43 @@
       
     
   
+  
+    ÅródtytuÅ
+    srodtytul
+    {"tag": "srodtytul"}
+    insert_tag
+    
+    
+    
+      
+    
+  
   
-    $.log(editor, panel, params);
+    var texteditor = panel.texteditor;
 
+var text = texteditor.selection();
 
+var out = '<'+params.tag;
 
-var texteditor = panel.texteditor;
+for (var attr in params.attrs) {
 
-var text = texteditor.selection();
+    out += ' '+attr+'="' + params.attrs[attr] + '"';
 
-texteditor.replaceSelection('<' + params.tag + '>' + text + '</' + params.tag + '>');
+};
 
-if (text.length == 0) 
+out += '>';
+
+out += text;
+
+out += '</' + params.tag + '>';
+
+ 
+
+texteditor.replaceSelection(out);
 
-{
+
+
+if (text.length == 0) {
 
     var pos = texteditor.cursorPosition();
 
@@ -905,56 +912,86 @@ panel.fireEvent('contentChanged');
   
   
     // params: {exprs: list of {expr: "", repl: "" [, opts: "g"]}}
+
 var cm = panel.texteditor;
 
+
+
 var exprs = $.map(params.exprs, function(expr) {
+
     var opts = "g";
+
     if(expr.length > 2)
+
         opts = expr[2];
+
     return {rx: new RegExp(expr[0], opts), repl: expr[1]};
+
 });
 
-var selection = cm.selection();
 
-if(selection) 
-{
-    var changed = false;
-    var lines = selection.split('\n');
-    var lines = $.map(lines, function(line) { 
-        var old_line = line;
-        $(exprs).each(function() { 
-            var expr = this;
-            line = line.replace(expr.rx, expr.repl);
-        });
-        if(old_line != line) changed = true;
-        return line;
+
+var partial = true;
+
+var text = cm.selection();
+
+
+
+if(!text) {
+
+    var cpos = cm.cursorPosition();
+
+    cpos.line = cm.lineNumber(cpos.line)
+
+    cm.selectLines(cm.firstLine(), 0, cm.lastLine(), 0);
+
+    text = cm.selection();
+
+    partial = false;
+
+}
+
+
+
+var changed = false;
+
+var lines = text.split('\n');
+
+var lines = $.map(lines, function(line) { 
+
+    var old_line = line;
+
+    $(exprs).each(function() { 
+
+        var expr = this;
+
+        line = line.replace(expr.rx, expr.repl);
+
     });
 
-    if(changed) {
-        cm.replaceSelection( lines.join('\n') );
-        panel.fireEvent('contentChanged');
-    }
+    if(old_line != line) changed = true;
+
+    return line;
+
+});
+
+
+
+if(changed) 
+
+{
+
+    cm.replaceSelection( lines.join('\n') );
+
+    panel.fireEvent('contentChanged');
+
 }
-else {
-    var line = cm.firstLine();
-    var hasChanges = false;
-    do {
-        var content = cm.lineContent(line);    
-        var old_content = content;
-        $(exprs).each(function() { var expr = this;
-            content = content.replace(expr.rx, expr.repl);
-        });
-        
-        if(old_content != content) {
-            cm.setLineContent(line, content);
-            hasChanges = true;
-        }
-
-        line = cm.nextLine(line);
-    } while( !(line === false) );
-
-    if(hasChanges) panel.fireEvent('contentChanged');
-}
+
+
+
+if(!partial)
+
+    cm.selectLines( cm.nthLine(cpos.line), cpos.character )
   
   
     var texteditor = panel.texteditor;
@@ -977,4 +1014,95 @@ else {
 
 }
   
+  
+    var cm = panel.texteditor;
+
+var exprs = $.map(params.exprs, function(expr) {
+
+    var opts = "mg";
+
+    if(expr.length > 2)
+
+        opts = expr[2];
+
+    return {rx: new RegExp(expr[0], opts), repl: expr[1]};
+
+});
+
+
+
+var partial = true;
+
+var text = cm.selection();
+
+
+
+if(!text) {
+
+    var cpos = cm.cursorPosition();
+
+    cpos.line = cm.lineNumber(cpos.line)
+
+    cm.selectLines(cm.firstLine(), 0, cm.lastLine(), 0);
+
+    text = cm.selection();
+
+    partial = false;
+
+}
+
+
+
+var original = text;
+
+
+
+$(exprs).each(function() { 
+
+    text = text.replace(this.rx, this.repl);
+
+});
+
+
+
+if( original != text) {
+
+    cm.replaceSelection(text);
+
+    panel.fireEvent('contentChanged');
+
+}
+
+
+
+if(!partial) {
+
+    cm.selectLines( cm.nthLine(cpos.line), cpos.character );
+
+}
+  
+  
+    params.each(function() {
+
+    editor.callScriptlet(this[0], this[1]);
+
+});
+  
+  
+    var cm = panel.texteditor;
+
+var text = cm.selection();
+
+if(!text) return;
+
+var repl = text.toLowerCase();
+
+if(repl != text) {
+
+    cm.replaceSelection(repl);
+
+    panel.fireEvent('contentChanged');
+
+};
+  
 
-- 
2.20.1
From 537012c60af1ea1c0e39949c58df4647ddadc72b Mon Sep 17 00:00:00 2001
From: =?utf8?q?=C5=81ukasz=20Rekucki?= 
Date: Tue, 8 Sep 2009 19:34:14 +0200
Subject: [PATCH 14/16] Zlacz takie same przyciski podczas naprawiania.
---
 .../toolbar/management/commands/fixbuttons.py | 41 +++++++++++++++----
 dump_toolbar.sh                               |  2 +-
 2 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/apps/toolbar/management/commands/fixbuttons.py b/apps/toolbar/management/commands/fixbuttons.py
index 2f15525b..627ef25d 100755
--- a/apps/toolbar/management/commands/fixbuttons.py
+++ b/apps/toolbar/management/commands/fixbuttons.py
@@ -10,26 +10,49 @@ class Command(NoArgsCommand):
     
     def handle_noargs(self, **options):
         buttons = Button.objects.all()
+        print "Validating parameters... "
         for b in buttons:
             params = b.params;
             try:
-                v = json.loads(b.params)
-               
+                v = json.loads(b.params)               
             except ValueError, e:
-                print 'On button %s: ' % b.label, b.params
-                print e
-                # try to fix the bad json
-                
-                # cut the parenthis
+                print 'Trying to fix button "%s" ...' % b.slug
                 if params[0] == u'(':
                     params = params[1:]
                 if params[-1] == u')':
                     params = params[:-1]
+                try:
+                    v = son.loads(re.sub(u'([\\w-]+)\\s*:', u'"\\1": ', params).encode('utf-8'))
+                except ValueError, e:
+                    print "Unable to fix '%s' " % b.params
+                    print "Try to fix this button manually and rerun the script."
+                    return False
 
-                v = json.loads(re.sub(u'([\\w-]+)\\s*:', u'"\\1": ', params).encode('utf-8'))
+            # resave
             b.params = json.dumps(v)
             b.save()
 
+        print "Merge duplicate buttons (if any)..."
+        hash = {}
+        for b in buttons:
+            if b.slug not in hash:
+                hash[b.slug] = b
+                continue
+                
+            # duplicate button
+            print "Found duplicate of '%s'" % b.slug
+            a = hash[b.slug]
+
+            remove_duplicate = True
+            if a.params != b.params:
+                print "Conflicting params for duplicate of '%s'." % b.slug
+                print "Groups will be joined, but won't remove duplicates."
+                remove_duplicate = False
 
+            for g in b.group.all():
+                a.group.add(g)
 
-    
+            b.group.clear()
+
+            a.save()
+            b.delete()
diff --git a/dump_toolbar.sh b/dump_toolbar.sh
index 58898eee..6dcbbe08 100755
--- a/dump_toolbar.sh
+++ b/dump_toolbar.sh
@@ -1 +1 @@
-./project/manage.py dumpdata --format=xml toolbar | xmllint --format - > fixtures/przyciski.xml.new
+./project/manage.py dumpdata --format=xml toolbar | xmllint --format - > fixtures/przyciski.new.xml
-- 
2.20.1
From 39e235fcc4bffec8893cf9a2f8924303c7b3a859 Mon Sep 17 00:00:00 2001
From: =?utf8?q?=C5=81ukasz=20Rekucki?= 
Date: Tue, 8 Sep 2009 20:13:29 +0200
Subject: [PATCH 15/16] Sprawdzanie pustych grup.
---
 apps/toolbar/management/commands/fixbuttons.py | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/apps/toolbar/management/commands/fixbuttons.py b/apps/toolbar/management/commands/fixbuttons.py
index 627ef25d..5482ddc5 100755
--- a/apps/toolbar/management/commands/fixbuttons.py
+++ b/apps/toolbar/management/commands/fixbuttons.py
@@ -1,8 +1,9 @@
-# -*- conding: utf-8
+#!/usr/bin/env python
+# -*- conding: utf-8 -*-
 __author__="lreqc"
 __date__ ="$2009-09-08 14:31:26$"
 from django.core.management.base import NoArgsCommand
-from toolbar.models import Button
+from toolbar.models import Button, ButtonGroup
 from django.utils import simplejson as json
 import re
 
@@ -55,4 +56,14 @@ class Command(NoArgsCommand):
             b.group.clear()
 
             a.save()
-            b.delete()
+            if remove_duplicate:
+                b.delete()
+
+        print "Searching for empty groups and orphaned buttons:"
+        for g in ButtonGroup.objects.all():
+            if len(g.button_set.all()) == 0:
+                print "Empty group: '%s'"  % g.slug
+                
+        for b in Button.objects.all():
+            if len(b.group.all()) == 0:
+                print "orphan: '%s'"  % b.slug
-- 
2.20.1
From 4419f93a01685b9864a6e78cb905c803ec0970b0 Mon Sep 17 00:00:00 2001
From: =?utf8?q?=C5=81ukasz=20Rekucki?= 
Date: Wed, 9 Sep 2009 01:52:14 +0200
Subject: [PATCH 16/16] Skroty klawiszowe z Ctrl i Shift. Update przyciskow.
---
 apps/explorer/forms.py                        |    7 +-
 apps/explorer/views.py                        |   10 +-
 apps/toolbar/admin.py                         |   39 +-
 apps/toolbar/models.py                        |   15 +
 apps/toolbar/templates/toolbar/toolbar.html   |    2 +-
 apps/toolbar/templatetags/toolbar_tags.py     |    5 -
 fixtures/przyciski.xml                        | 2090 ++++++++---------
 fixtures/przyciski.yaml                       |   73 -
 project/static/js/editor.js                   |   59 +-
 project/templates/explorer/file_list.html     |    4 +-
 .../templates/explorer/panels/xmleditor.html  |    2 +-
 11 files changed, 1097 insertions(+), 1209 deletions(-)
 mode change 100644 => 100755 fixtures/przyciski.xml
 delete mode 100644 fixtures/przyciski.yaml
diff --git a/apps/explorer/forms.py b/apps/explorer/forms.py
index da8a1179..e453f1fd 100644
--- a/apps/explorer/forms.py
+++ b/apps/explorer/forms.py
@@ -62,6 +62,7 @@ class BookUploadForm(forms.Form):
     file = forms.FileField(label='Source OCR file')
     bookname = forms.RegexField(regex='[\w-]+',  \
         label='Publication name', help_text='Example: slowacki-beniowski')
+    autoxml = forms.BooleanField(required=False, initial=True, label=u"Generate DublinCore template")
 
 class ImageFoldersForm(forms.Form):
     folders = forms.ChoiceField(required=False)
@@ -78,13 +79,13 @@ class DublinCoreForm(forms.Form):
     kinds = ListField()
     genres = ListField()
     created_at = forms.DateField()
-    released_to_public_domain_at = forms.DateField()
+    released_to_public_domain_at = forms.DateField(required=False)
     editors = ListField(widget=forms.Textarea, required=False, converter=person_conv)
     translators = ListField(widget=forms.Textarea, required=False, converter=person_conv)
     technical_editors = ListField(widget=forms.Textarea, required=False, converter=person_conv)
     publisher = forms.CharField()
-    source_name = forms.CharField(widget=forms.Textarea)
-    source_url = forms.URLField(verify_exists=False)
+    source_name = forms.CharField(widget=forms.Textarea, required=False)
+    source_url = forms.URLField(verify_exists=False, required=False)
     url = forms.URLField(verify_exists=False)
     parts = forms.CharField(widget=forms.Textarea, required=False)
     license = forms.CharField(required=False)
diff --git a/apps/explorer/views.py b/apps/explorer/views.py
index 18f79b3e..b3cc09bc 100644
--- a/apps/explorer/views.py
+++ b/apps/explorer/views.py
@@ -1,7 +1,10 @@
 # -*- coding: utf-8 -*-
 import urllib2
 import hg
-from librarian import html, parser, dcparser, ParseError, ValidationError
+from datetime import date
+
+from librarian import html, parser, dcparser, wrap_text
+from librarian import ParseError, ValidationError
 
 from django.conf import settings
 from django.contrib.auth.decorators import login_required, permission_required
@@ -65,9 +68,12 @@ def file_upload(request, repo):
                 f = request.FILES['file']
                 decoded = f.read().decode('utf-8')
                 path = form.cleaned_data['bookname']
+
+                if form.cleaned_data['autoxml']:
+                    decoded = wrap_text(decoded, unicode(date.today()) )
                 
                 def upload_action():
-                    repo._add_file(path ,decoded.encode('utf-8') )
+                    repo._add_file(path, decoded.encode('utf-8') )
                     repo._commit(message="File %s uploaded by user %s" % \
                         (path, request.user.username), user=request.user.username)
 
diff --git a/apps/toolbar/admin.py b/apps/toolbar/admin.py
index 58b5f746..dca934cc 100644
--- a/apps/toolbar/admin.py
+++ b/apps/toolbar/admin.py
@@ -12,8 +12,41 @@ from toolbar import models
 #    list_editable = ('position',)
 
 
+class KeyModSelector(forms.MultiWidget):
+    def __init__(self):
+        super(KeyModSelector, self).__init__(
+            [forms.CheckboxInput() for x in xrange(0,3)])
+
+    def decompress(self, v):
+        r = [(v&0x01) != 0, (v&0x02) != 0, (v&0x04) != 0]
+        print "DECOMPRESS: " , v, repr(r)
+        return r
+
+    def format_output(self, widgets):
+        out = u''
+        out += u'' + widgets[0] + u' Alt 
'
+        out += u'' + widgets[1] + u' Ctrl 
'
+        out += u'' + widgets[2] + u' Shift 
'
+        return out
+
+class KeyModField(forms.MultiValueField):
+
+    def __init__(self):
+        super(KeyModField, self).__init__(\
+            fields=tuple(forms.BooleanField() for x in xrange(0,3)), \
+            widget=KeyModSelector() )
+
+    def compress(self, dl):
+        v = int(dl[0]) | (int(dl[1]) << 1) | (int(dl[2]) << 2)
+        print "COMPRESS", v
+        return v
+    
+
 class ButtonAdminForm(forms.ModelForm):
-    model = models.Button
+    key_mod = KeyModField()
+
+    class Meta:
+        model = models.Button
 
     def clean_params(self):
         value = self.cleaned_data['params']
@@ -22,9 +55,11 @@ class ButtonAdminForm(forms.ModelForm):
         except Exception, e:
             raise forms.ValidationError(e)
 
+
+
 class ButtonAdmin(admin.ModelAdmin):
     form = ButtonAdminForm
-    list_display = ('label', 'scriptlet', 'key', 'params')
+    list_display = ('label', 'scriptlet', 'hotkey_name', 'params')
     prepopulated_fields = {'slug': ('label',)}
 
 admin.site.register(models.Button, ButtonAdmin)
diff --git a/apps/toolbar/models.py b/apps/toolbar/models.py
index ae101e07..004fde9f 100644
--- a/apps/toolbar/models.py
+++ b/apps/toolbar/models.py
@@ -24,6 +24,7 @@ class Button(models.Model):
 
     # ui related stuff
     key = models.CharField(blank=True, max_length=1)
+    key_mod = models.PositiveIntegerField(blank=True, default=1)
     tooltip = models.CharField(blank=True, max_length=120)
 
     # Why the button is restricted to have the same position in each group ?
@@ -33,6 +34,20 @@ class Button(models.Model):
     class Meta:
         ordering = ('label',)
         verbose_name, verbose_name_plural = _('button'), _('buttons')
+
+    def hotkey_code(self):
+        return ord(self.key.upper()) | (self.key_mod << 8)
+
+    def hotkey_name(self):
+        if not self.key:
+            return ''
+
+        mods = []
+        if self.key_mod & 0x01: mods.append('Alt')
+        if self.key_mod & 0x02: mods.append('Ctrl')
+        if self.key_mod & 0x04: mods.append('Shift')
+        mods.append('"'+self.key+'"')
+        return '+'.join(mods)
     
     def __unicode__(self):
         return self.label
diff --git a/apps/toolbar/templates/toolbar/toolbar.html b/apps/toolbar/templates/toolbar/toolbar.html
index 21135305..a6241d1e 100644
--- a/apps/toolbar/templates/toolbar/toolbar.html
+++ b/apps/toolbar/templates/toolbar/toolbar.html
@@ -19,7 +19,7 @@
             
diff --git a/apps/toolbar/templatetags/toolbar_tags.py b/apps/toolbar/templatetags/toolbar_tags.py
index e29c8a0d..a38ed017 100644
--- a/apps/toolbar/templatetags/toolbar_tags.py
+++ b/apps/toolbar/templatetags/toolbar_tags.py
@@ -7,8 +7,3 @@ register = template.Library()
 def toolbar():
     groups = models.ButtonGroup.objects.all()
     return {'groups': groups}
-
-@register.filter
-def keycode(value):
-    return ord(str(value).upper())
-
diff --git a/fixtures/przyciski.xml b/fixtures/przyciski.xml
old mode 100644
new mode 100755
index ea1d7246..ec2acaba
--- a/fixtures/przyciski.xml
+++ b/fixtures/przyciski.xml
@@ -1,1108 +1,982 @@
-
-
-  
-    Akapity i dÅugie cytaty
-    akapity-i-dlugie-cytaty
-    0
-  
-  
-    Autokorekta
-    autokorekta
-    0
-  
-  
-    Bloki
-    bloki
-    0
-  
-  
-    Bloki poczÄ
tkowe
-    bloki-poczatkowe
-    0
-  
-  
-    Deklaracje
-    deklaracje
-    0
-  
-  
-    Dramat wierszowany
-    dramat-wierszowany
-    0
-  
-  
-    Dramat wspóÅczesny
-    dramat-wspolczesny
-    0
-  
-  
-    Elementy poczÄ
tkowe
-    elementy-poczatkowe
-    0
-  
-  
-    Mastery
-    mastery
-    0
-  
-  
-    NagÅówki
-    naglowki
-    0
-  
-  
-    PoczÄ
tek dramatu
-    poczatek-dramatu
-    0
-  
-  
-    Polecenia
-    polecenia
-    0
-  
-  
-    Strukturalne
-    strukturalne
-    0
-  
-  
-    Style znakowe
-    style-znakowe
-    0
-  
-  
-    Wersy
-    wersy
-    0
-  
-  
-    Widok
-    display_options
-    2
-  
-  
-    A<sup>+</sup>
-    increase_font_size
-    {"change": 2}
-    codemirror_fontsize
-    +
-    ZwiÄksz rozmiar czcionki.
-    
-      
-    
-  
-  
-    A<sup>-</sup>
-    descrease_font_size
-    {"change": -2}
-    codemirror_fontsize
-    -
-    Zmniejsz rozmiar czcionki.
-    
-      
-    
-  
-  
-    A<sup>=</sup>
-    reset_font_size
-    {"fontSize": 13}
-    codemirror_fontsize
-    =
-    PrzywrÃ³Ä orginalny rozmiar czcionki.
-    
-      
-    
-  
-  
-    A<sup>â</sup>
-    tolowercase
-    []
-    lowercase
-    u
-    ZamieÅ wielkie litery na maÅe.
-    
-      
-    
-  
-  
-    Podstawowa
-    basic_correction
-    {"exprs": [["\n\\d+\n", "\n"], ["-\\s*\n+", ""], ["\\,\\.\\.|\\.\\,\\.|\\.\\.\\,", "..."]]}
-    fulltextregexp
-    
-    WykonujÄ operacjÄ z novel-pages i poem-pages.
-    
-      
-    
-  
-  
-    UsuÅ spacjÄ
-    strip_whitespace
-    {"exprs": [["^\\s+|\\s+$", ""], ["\\s+", " "]]}
-    lineregexp
-    
-    Usuwa zbÄdne spacjÄ z dokumentu.
-    
-      
-    
-  
-  
-    ZamieÅ CudzysÅowy
-    zamien-cudzyslowy
-    {"exprs": [["\u00bb|\u201e", ",,"], ["\u00ab", "\""], ["\"([\u0104\u0118\u00d3\u0141\u017b\u0179\u0106\u0143\u0105\u017c\u017a\u015b\u0144\u00f3\u0142\u0107\\w])", ",,$1"]]}
-    lineregexp
-    
-    
-    
-      
-    
-  
-  
-    ZamieÅ dywiz
-    zamien_dywiz
-    {"exprs": [["(\\d)[\u2014-](\\d)", "$1--$2"], ["\u2014", "---"]]}
-    lineregexp
-    
-    Zamienia 'â' na '---', oraz '1â2' na '1--2'.
-    
-      
-    
-  
-  
-    akapit
-    akapit
-    {"tag": "akap"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    akapit cd.
-    akapit-cd
-    {"tag": "akap_cd"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    akapit dialogowy
-    akapit-dialogowy
-    {"tag": "akap_dialog"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    akt
-    akt
-    {"tag": "akt"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    autor
-    autor
-    {"tag": "autor"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    czÄÅÄ/ksiÄga
-    czesc
-    {"tag": "naglowek_czesc"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dedykacja
-    dedykacja
-    {"tag": "dedykacja"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dedykacja
-    dedykacja
-    {"tag": "dedykacja"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    didaskalia
-    didaskalia
-    {"tag": "didaskalia"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    didaskalia
-    didaskalia
-    {"tag": "didaskalia"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    didaskalia wewn.
-    didaskalia-wewn
-    {"tag": "didask_tekst"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    didaskalia wewn.
-    didaskalia-wewn
-    {"tag": "didask_tekst"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dramat wiersz.
-    dramat-wiersz
-    {"tag": "dramat_wierszowany_l"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dramat wiersz./w. Åam
-    dramat-wiersz-w-lam
-    {"tag": "dramat_wierszowany_lp"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dramat wspóÅczesny
-    dramat-wspolczesny
-    {"tag": "dramat_wspolczesny"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dzieÅo nadrzÄdne
-    dzielo-nadrzedne
-    {"tag": "dzielo_nadrzedne"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dÅugi cyt. poet.
-    dlugi-cyt-poet
-    {"tag": "poezja_cyt"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dÅugi cyta. poet.
-    dlugi-cyt-poet
-    {"tag": "poezja_cyt"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    dÅugi cytat
-    dlugi-cytat
-    {"tag": "dlugi_cyt"}
-    insert_tag
-    
-    
-    
-      
-      
-    
-  
-  
-    ekstra
-    ekstra
-    {"tag": "ekstra"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    kwestia
-    kwestia
-    {"tag": "kwestia"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    kwestia
-    kwestia
-    {"tag": "kwestia"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    liryka
-    liryka
-    {"tag": "liryka_l"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    liryka/w. Åam
-    liryka-w-lam
-    {"tag": "liryka_lp"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    mamtemat.
-    matemat
-    {"tag": "mat"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    motto
-    motto
-    {"tag": "motto"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    motto
-    motto
-    {"tag": "motto"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    motto podpis
-    motto-podpis
-    {"tag": "motto_podpis"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    nagÅówek kwestii
-    naglowek-kwestii
-    {"tag": "naglowek_osoba"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    nota
-    nota
-    {"tag": "nota"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    opowiadanie
-    opowiadanie
-    {"tag": "opowiadanie"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    osoba
-    osoba
-    {"tag": "osoba"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    osoba
-    osoba
-    {"tag": "osoba"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    podrozdziaÅ
-    podrozdzial
-    {"tag": "naglowek_podrozdzial"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    podtytuÅ
-    podtytul
-    {"tag": "podtytul"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    powieÅÄ
-    powiesc
-    {"tag": "powiesc"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    przypis autorski
-    przypis-autorski
-    {"tag": "pa"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    przypis edytorski
-    przypis-edytorski
-    {"tag": "pe"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    przypis redaktorski
-    przypis-redaktorski
-    {"tag": "pr"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    przypis tÅumacza
-    przypis-tlumacza
-    {"tag": "pt"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    rozdziaÅ
-    rozdzial
-    {"tag": "naglowek_rozdzial"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    scena
-    scena
-    {"tag": "naglowek_scena"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    sep. asteryks
-    sep-asteryks
-    {"tag": "sekcja_asteryks"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    sep. linia
-    sep-linia
-    {"tag": "separator_linia"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    sep. ÅwiatÅo
-    sep-swiatlo
-    {"tag": "sekcja_swiatlo"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    strofa
-    strofa
-    {"tag": "strofa"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    sÅowo obce
-    slowo-obce
-    {"tag": "slowo_obce"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    tagi gÅówne
-    tagi-glowne
-    {"tag": "utwor"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    tytuÅ
-    tytul
-    {"tag": "nazwa_utworu"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    tytuÅ dzieÅa
-    tytul-dziela
-    {"tag": "tytul_dziela", "attrs": {"typ": " "}}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    uwaga
-    uwaga
-    {"tag": "uwaga"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wers akap.
-    wers-akap
-    {"tag": "wers_akap"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wers akap.
-    wers-akap
-    {"tag": "wers_akap"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wers cd.
-    wers-cd
-    {"tag": "wers_cd"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wers cd.
-    wers-cd
-    {"tag": "wers_cd"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wers wciÄty
-    wers-wciety
-    {"tag": "wers_wciety"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wers wciÄty
-    wers-wciety
-    {"tag": "wers_wciety"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    www
-    www
-    {"tag": "www"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wyróżnienie
-    wyroznienie
-    {"tag": "wyroznienie"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wywiad
-    wywiad
-    {"tag": "wywiad"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wywiad odpowiedź
-    wywiad-odpowiedz
-    {"tag": "wywiad_odp"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    wywiad pytanie
-    wywiad-pytanie
-    {"tag": "wywiad_pyt"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    zastÄpnik wersu
-    zastepnik-wersu
-    {"tag": "zastepnik_wersu"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    ÅródtytuÅ
-    srodtytul
-    {"tag": "srodtytul"}
-    insert_tag
-    
-    
-    
-      
-    
-  
-  
-    var texteditor = panel.texteditor;
-
-var text = texteditor.selection();
-
-var out = '<'+params.tag;
-
-for (var attr in params.attrs) {
-
-    out += ' '+attr+'="' + params.attrs[attr] + '"';
-
-};
-
-out += '>';
-
-out += text;
-
-out += '</' + params.tag + '>';
-
- 
-
-texteditor.replaceSelection(out);
-
-
-
-if (text.length == 0) {
-
-    var pos = texteditor.cursorPosition();
-
-    texteditor.selectLines(pos.line, pos.character + params.tag.length + 2);
-
-}
-
-
-
-panel.fireEvent('contentChanged');
-  
-  
-    // params: {exprs: list of {expr: "", repl: "" [, opts: "g"]}}
-
-var cm = panel.texteditor;
-
-
-
-var exprs = $.map(params.exprs, function(expr) {
-
-    var opts = "g";
-
-    if(expr.length > 2)
-
-        opts = expr[2];
-
-    return {rx: new RegExp(expr[0], opts), repl: expr[1]};
-
-});
-
-
-
-var partial = true;
-
-var text = cm.selection();
-
-
-
-if(!text) {
-
-    var cpos = cm.cursorPosition();
-
-    cpos.line = cm.lineNumber(cpos.line)
-
-    cm.selectLines(cm.firstLine(), 0, cm.lastLine(), 0);
-
-    text = cm.selection();
-
-    partial = false;
-
-}
-
-
-
-var changed = false;
-
-var lines = text.split('\n');
-
-var lines = $.map(lines, function(line) { 
-
-    var old_line = line;
-
-    $(exprs).each(function() { 
-
-        var expr = this;
-
-        line = line.replace(expr.rx, expr.repl);
-
-    });
-
-    if(old_line != line) changed = true;
-
-    return line;
-
-});
-
-
-
-if(changed) 
-
-{
-
-    cm.replaceSelection( lines.join('\n') );
-
-    panel.fireEvent('contentChanged');
-
-}
-
-
-
-if(!partial)
-
-    cm.selectLines( cm.nthLine(cpos.line), cpos.character )
-  
-  
-    var texteditor = panel.texteditor;
-
-var frameBody = $('body', $(texteditor.frame).contents());
-
-
-
-if(params.fontSize) {
-
-    frameBody.css('font-size', params.fontSize);
-
-}
-
-else {
-
-    var old_size = parseInt(frameBody.css('font-size'));
-
-    frameBody.css('font-size', old_size + (params.change || 0) );
-
-}
-  
-  
-    var cm = panel.texteditor;
-
-var exprs = $.map(params.exprs, function(expr) {
-
-    var opts = "mg";
-
-    if(expr.length > 2)
-
-        opts = expr[2];
-
-    return {rx: new RegExp(expr[0], opts), repl: expr[1]};
-
-});
-
-
-
-var partial = true;
-
-var text = cm.selection();
-
-
-
-if(!text) {
-
-    var cpos = cm.cursorPosition();
-
-    cpos.line = cm.lineNumber(cpos.line)
-
-    cm.selectLines(cm.firstLine(), 0, cm.lastLine(), 0);
-
-    text = cm.selection();
-
-    partial = false;
-
-}
-
-
-
-var original = text;
-
-
-
-$(exprs).each(function() { 
-
-    text = text.replace(this.rx, this.repl);
-
-});
-
-
-
-if( original != text) {
-
-    cm.replaceSelection(text);
-
-    panel.fireEvent('contentChanged');
-
-}
-
-
-
-if(!partial) {
-
-    cm.selectLines( cm.nthLine(cpos.line), cpos.character );
-
-}
-  
-  
-    params.each(function() {
-
-    editor.callScriptlet(this[0], this[1]);
-
-});
-  
-  
-    var cm = panel.texteditor;
-
-var text = cm.selection();
-
-if(!text) return;
-
-var repl = text.toLowerCase();
-
-if(repl != text) {
-
-    cm.replaceSelection(repl);
-
-    panel.fireEvent('contentChanged');
-
-};
-  
-
+
+
+  
+    Akapity i dÅugie cytaty
+    akapity-i-dlugie-cytaty
+    0
+  
+  
+    Autokorekta
+    autokorekta
+    0
+  
+  
+    Bloki
+    bloki
+    0
+  
+  
+    Dramat wierszowany
+    dramat-wierszowany
+    0
+  
+  
+    Dramat wspóÅczesny
+    dramat-wspolczesny
+    0
+  
+  
+    Elementy poczÄ
tkowe
+    elementy-poczatkowe
+    0
+  
+  
+    NagÅówki
+    naglowki
+    0
+  
+  
+    PoczÄ
tek dramatu
+    poczatek-dramatu
+    0
+  
+  
+    Polecenia
+    polecenia
+    0
+  
+  
+    Strukturalne
+    strukturalne
+    0
+  
+  
+    Style znakowe
+    style-znakowe
+    0
+  
+  
+    Wersy
+    wersy
+    0
+  
+  
+    Widok
+    display_options
+    2
+  
+  
+    A<sup>+</sup>
+    increase_font_size
+    {"change": 2}
+    codemirror_fontsize
+    +
+    ZwiÄksz rozmiar czcionki.
+    
+      
+    
+  
+  
+    A<sup>-</sup>
+    descrease_font_size
+    {"change": -2}
+    codemirror_fontsize
+    -
+    Zmniejsz rozmiar czcionki.
+    
+      
+    
+  
+  
+    A<sup>=</sup>
+    reset_font_size
+    {"fontSize": 13}
+    codemirror_fontsize
+    =
+    PrzywrÃ³Ä orginalny rozmiar czcionki.
+    
+      
+    
+  
+  
+    A<sup>â</sup>
+    tolowercase
+    []
+    lowercase
+    u
+    ZamieÅ wielkie litery na maÅe.
+    
+      
+    
+  
+  
+    akapit
+    akapit
+    {"tag": "akap"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    akapit cd.
+    akapit-cd
+    {"tag": "akap_cd"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    akapit dialogowy
+    akapit-dialogowy
+    {"tag": "akap_dialog"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    akt
+    akt
+    {"tag": "akt"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    autor
+    autor
+    {"tag": "autor"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    czÄÅÄ/ksiÄga
+    czesc
+    {"tag": "naglowek_czesc"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    dedykacja
+    dedykacja
+    {"tag": "dedykacja"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    didaskalia
+    didaskalia
+    {"tag": "didaskalia"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    didaskalia poczÄ
tkowe
+    didaskalia-poczatkowe
+    {"tag": "miejsce_czas"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    didaskalia wewn.
+    didaskalia-wewn
+    {"tag": "didask_tekst"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    dramat wiersz.
+    dramat-wiersz
+    {"tag": "dramat_wierszowany_l"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    dramat wiersz./w. Åam
+    dramat-wiersz-w-lam
+    {"tag": "dramat_wierszowany_lp"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    dramat wspóÅczesny
+    dramat-wspolczesny
+    {"tag": "dramat_wspolczesny"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    dzieÅo nadrzÄdne
+    dzielo-nadrzedne
+    {"tag": "dzielo_nadrzedne"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    dÅugi cyt. poet.
+    dlugi-cyt-poet
+    {"tag": "poezja_cyt"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    dÅugi cytat
+    dlugi-cytat
+    {"tag": "dlugi_cyt"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    ekstra
+    ekstra
+    {"tag": "ekstra"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    kwestia
+    kwestia
+    {"tag": "kwestia"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    liryka
+    liryka
+    {"tag": "liryka_l"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    liryka/w. Åam
+    liryka-w-lam
+    {"tag": "liryka_lp"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    lista osób: pole
+    lista-osob-pole
+    {"tag": "lista_osoba", "attrs": {"typ": ""}}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    mamtemat.
+    matemat
+    {"tag": "mat"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    motto
+    motto
+    {"tag": "motto"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    motto podpis
+    motto-podpis
+    {"tag": "motto_podpis"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    nagÅówek kwestii
+    naglowek-kwestii
+    {"tag": "naglowek_osoba"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    nota
+    nota
+    {"tag": "nota"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    opowiadanie
+    opowiadanie
+    {"tag": "opowiadanie"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    osoba
+    osoba
+    {"tag": "osoba"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    podrozdziaÅ
+    podrozdzial
+    {"tag": "naglowek_podrozdzial"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    Podstawowa
+    basic_correction
+    {"exprs": [["\n\\d+\n", "\n"], ["-\\s*\n+", ""], ["\\,\\.\\.|\\.\\,\\.|\\.\\.\\,", "..."]]}
+    fulltextregexp
+    
+    WykonujÄ operacjÄ z novel-pages i poem-pages.
+    
+      
+    
+  
+  
+    podtytuÅ
+    podtytul
+    {"tag": "podtytul"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    powieÅÄ
+    powiesc
+    {"tag": "powiesc"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    przypis autorski
+    przypis-autorski
+    {"tag": "pa"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    przypis edytorski
+    przypis-edytorski
+    {"tag": "pe"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    przypis redaktorski
+    przypis-redaktorski
+    {"tag": "pr"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    przypis tÅumacza
+    przypis-tlumacza
+    {"tag": "pt"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    rozdziaÅ
+    rozdzial
+    {"tag": "naglowek_rozdzial"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    scena
+    scena
+    {"tag": "naglowek_scena"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    sep. asteryks
+    sep-asteryks
+    {"tag": "sekcja_asteryks"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    sep. linia
+    sep-linia
+    {"tag": "separator_linia"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    sep. ÅwiatÅo
+    sep-swiatlo
+    {"tag": "sekcja_swiatlo"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    ÅródtytuÅ
+    srodtytul
+    {"tag": "srodtytul"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    strofa
+    strofa
+    {"tag": "strofa"}
+    insert_tag
+    s
+    
+    
+      
+      
+    
+  
+  
+    sÅowo obce
+    slowo-obce
+    {"tag": "slowo_obce"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    tagi gÅówne
+    tagi-glowne
+    {"tag": "utwor"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    tytuÅ
+    tytul
+    {"tag": "nazwa_utworu"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    tytuÅ dzieÅa
+    tytul-dziela
+    {"tag": "tytul_dziela", "attrs": {"typ": "1"}}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    UsuÅ spacjÄ
+    strip_whitespace
+    {"exprs": [["^\\s+|\\s+$", ""], ["\\s+", " "]]}
+    lineregexp
+    
+    Usuwa zbÄdne spacjÄ z dokumentu.
+    
+      
+    
+  
+  
+    uwaga
+    uwaga
+    {"tag": "uwaga"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    wers akap.
+    wers-akap
+    {"tag": "wers_akap"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    wers cd.
+    wers-cd
+    {"tag": "wers_cd"}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    Wers wciÄty
+    wers-wciety
+    {"tag": "wers_wciety", "attrs": {"typ": ""}}
+    insert_tag
+    
+    
+    
+      
+      
+    
+  
+  
+    www
+    www
+    {"tag": "www"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    wyróżnienie
+    wyroznienie
+    {"tag": "wyroznienie"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    wywiad
+    wywiad
+    {"tag": "wywiad"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    wywiad odpowiedź
+    wywiad-odpowiedz
+    {"tag": "wywiad_odp"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    wywiad pytanie
+    wywiad-pytanie
+    {"tag": "wywiad_pyt"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    ZamieÅ cudzysÅowy
+    zamien-cudzyslowy
+    {"exprs": [["\u00bb|\u201e", ",,"], ["\u00ab", "\""], ["\"([\u0104\u0118\u00d3\u0141\u017b\u0179\u0106\u0143\u0105\u017c\u017a\u015b\u0144\u00f3\u0142\u0107\\w])", ",,$1"]]}
+    lineregexp
+    
+    
+    
+      
+    
+  
+  
+    ZamieÅ dywiz
+    zamien_dywiz
+    {"exprs": [["(\\d)[\u2014-](\\d)", "$1--$2"], ["\u2014", "---"]]}
+    lineregexp
+    
+    Zamienia 'â' na '---', oraz '1â2' na '1--2'.
+    
+      
+    
+  
+  
+    zastÄpnik wersu
+    zastepnik-wersu
+    {"tag": "zastepnik_wersu"}
+    insert_tag
+    
+    
+    
+      
+    
+  
+  
+    var texteditor = panel.texteditor;
+
+var text = texteditor.selection();
+
+var out = '<'+params.tag;
+
+for (var attr in params.attrs) {
+
+    out += ' '+attr+'="' + params.attrs[attr] + '"';
+
+};
+
+out += '>';
+
+out += text;
+
+out += '</' + params.tag + '>';
+
+ 
+
+texteditor.replaceSelection(out);
+
+
+
+if (text.length == 0) {
+
+    var pos = texteditor.cursorPosition();
+
+    texteditor.selectLines(pos.line, pos.character + params.tag.length + 2);
+
+}
+
+
+
+panel.fireEvent('contentChanged');
+  
+  
+    editor.showPopup('generic-info', 'Przetwarzanie zaznaczonego tekstu...', '', -1);
+
+var cm = panel.texteditor;
+var exprs = $.map(params.exprs, function(expr) {
+
+    var opts = "g";
+
+    if(expr.length > 2)
+
+        opts = expr[2];
+
+    return {rx: new RegExp(expr[0], opts), repl: expr[1]};
+
+});
+
+
+
+var partial = true;
+
+var text = cm.selection();
+
+
+
+if(!text) {
+
+    var cpos = cm.cursorPosition();
+
+    cpos.line = cm.lineNumber(cpos.line)
+
+    cm.selectLines(cm.firstLine(), 0, cm.lastLine(), 0);
+
+    text = cm.selection();
+
+    partial = false;
+
+}
+
+
+
+var changed = 0;
+var lines = text.split('\n');
+var lines = $.map(lines, function(line) { 
+    var old_line = line;
+    $(exprs).each(function() { 
+        var expr = this;
+        line = line.replace(expr.rx, expr.repl);
+    });
+
+    if(old_line != line) changed += 1;
+    return line;
+});
+
+if(changed > 0) 
+{
+    cm.replaceSelection( lines.join('\n') );
+    panel.fireEvent('contentChanged');
+    editor.showPopup('generic-yes', 'Zmieniono ' + changed + ' linii.', 1500);
+    editor.advancePopupQueue();
+}
+else {
+    editor.showPopup('generic-info',  'Brak zmian w tekÅcie', 1500);
+    editor.advancePopupQueue();
+}
+
+if(!partial)
+    cm.selectLines( cm.nthLine(cpos.line), cpos.character )
+  
+  
+    var texteditor = panel.texteditor;
+
+var frameBody = $('body', $(texteditor.frame).contents());
+
+
+
+if(params.fontSize) {
+
+    frameBody.css('font-size', params.fontSize);
+
+}
+
+else {
+
+    var old_size = parseInt(frameBody.css('font-size'));
+
+    frameBody.css('font-size', old_size + (params.change || 0) );
+
+}
+  
+  
+    editor.showPopup('generic-info', 'Przetwarzanie zaznaczonego tekstu...', '', -1);
+
+var cm = panel.texteditor;
+
+var exprs = $.map(params.exprs, function(expr) {
+    var opts = "mg";
+    if(expr.length > 2)
+        opts = expr[2];
+
+    return {rx: new RegExp(expr[0], opts), repl: expr[1]};
+});
+
+var partial = true;
+var text = cm.selection();
+
+if(!text) {
+    var cpos = cm.cursorPosition();
+    cpos.line = cm.lineNumber(cpos.line)
+    cm.selectLines(cm.firstLine(), 0, cm.lastLine(), 0);
+
+    text = cm.selection();
+    partial = false;
+}
+
+
+
+var original = text;
+$(exprs).each(function() { 
+    text = text.replace(this.rx, this.repl);
+});
+
+if( original != text) 
+{    
+    cm.replaceSelection(text);
+    panel.fireEvent('contentChanged');
+    editor.showPopup('generic-yes', 'Zmieniono tekst' );
+    editor.advancePopupQueue();
+}
+else {
+    editor.showPopup('generic-info', 'Brak zmian w tekÅcie.');
+    editor.advancePopupQueue();
+}
+
+if(!partial) {
+    cm.selectLines( cm.nthLine(cpos.line), cpos.character );
+}
+  
+  
+    params.each(function() {
+
+    editor.callScriptlet(this[0], this[1]);
+
+});
+  
+  
+    var cm = panel.texteditor;
+
+var text = cm.selection();
+
+if(!text) return;
+
+var repl = text.toLowerCase();
+
+if(repl != text) {
+
+    cm.replaceSelection(repl);
+
+    panel.fireEvent('contentChanged');
+
+};
+  
+
diff --git a/fixtures/przyciski.yaml b/fixtures/przyciski.yaml
deleted file mode 100644
index a8cef760..00000000
--- a/fixtures/przyciski.yaml
+++ /dev/null
@@ -1,73 +0,0 @@
-- fields: {name: Autokorekta, position: 0, slug: autokorekta}
-  model: toolbar.buttongroup
-  pk: 2
-- fields: {name: Formatowanie, position: 0, slug: formatowanie}
-  model: toolbar.buttongroup
-  pk: 1
-- fields:
-    group: [2]
-    key: ''
-    label: Novelpages
-    params: "({exprs: [\r\n  [\"\\\\,\\\\.\\\\.|\\\\.\\\\,\\\\.|\\\\.\\\\.\\\\,\"\
-      , \"...\"],\r\n  [\"\u201E\", \",,\"] /* DOUBLE LOW-9 QUOTATION MARK */\r\n\
-      ]})\r\n  "
-    scriptlet: lineregexp
-    slug: novelpages
-    tooltip: "Wykonuj\u0119 operacj\u0119 z novel-pages."
-  model: toolbar.button
-  pk: 4
-- fields:
-    group: [2]
-    key: ''
-    label: "Usu\u0144 spacj\u0119"
-    params: '({exprs: [ ["^\\s+|\\s+$", ""], ["\\s+", " "] ]})'
-    scriptlet: lineregexp
-    slug: strip_whitespace
-    tooltip: "Usuwa zb\u0119dne spacj\u0119 z dokumentu."
-  model: toolbar.button
-  pk: 3
-- fields:
-    group: [1]
-    key: w
-    label: Wers
-    params: '({tag: ''wers''})'
-    scriptlet: insert_tag
-    slug: insert_verse
-    tooltip: Otacza zaznaczony tekst tagiem 'wers'.
-  model: toolbar.button
-  pk: 1
-- fields:
-    group: [2]
-    key: ''
-    label: "Zamie\u0144 dywiz"
-    params: "({exprs:[ [\"\u2014\",\"---\"] ]})"
-    scriptlet: lineregexp
-    slug: zamien_dywiz
-    tooltip: "Zamienia '\u2014' na '---'."
-  model: toolbar.button
-  pk: 2
-- fields: {code: "$.log(editor, panel, params);\r\n\r\nvar texteditor = panel.texteditor;\r\
-      \nvar text = texteditor.selection();\r\ntexteditor.replaceSelection('<' + params.tag\
-      \ + '>' + text + '' + params.tag + '>');\r\nif (text.length == 0) \r\n{\r\n\
-      \    var pos = texteditor.cursorPosition();\r\n    texteditor.selectLines(pos.line,\
-      \ pos.character + params.tag.length + 2);\r\n}\r\n\r\npanel.fireEvent('contentChanged');"}
-  model: toolbar.scriptlet
-  pk: insert_tag
-- fields: {code: "// params: {exprs: list of {expr: \"\", repl: \"\" [, opts: \"g\"\
-      ]}}\r\nvar cm = panel.texteditor;\r\n\r\nvar exprs = $.map(params.exprs, function(expr)\
-      \ {\r\n    var opts = \"g\";\r\n    if(expr.length > 2)\r\n        opts = expr[2];\r\
-      \n    return {rx: new RegExp(expr[0], opts), repl: expr[1]};\r\n});\r\n\r\n\
-      var selection = cm.selection();\r\n\r\nif(selection) \r\n{\r\n    var lines\
-      \ = selection.split('\\n');\r\n    lines = $.map(lines, function(line) { \r\n\
-      \        $(exprs).each(function() { \r\n            var expr = this;\r\n   \
-      \         line = line.replace(expr.rx, expr.repl);\r\n        });\r\n      \
-      \  return line;\r\n    });\r\n    cm.replaceSelection( lines.join('\\n') );\r\
-      \n}\r\nelse {\r\n    var line = cm.firstLine();\r\n    do {\r\n        var content\
-      \ = cm.lineContent(line);\r\n        $.log(\"Swapping line: $\" + content +\
-      \ \"$\");\r\n    \r\n        $(exprs).each(function() { var expr = this;\r\n\
-      \            content = content.replace(expr.rx, expr.repl);\r\n        });\r\
-      \n        cm.setLineContent(line, content);\r\n        line = cm.nextLine(line);\r\
-      \n    } while( !(line === false) );\r\n}"}
-  model: toolbar.scriptlet
-  pk: lineregexp
-
diff --git a/project/static/js/editor.js b/project/static/js/editor.js
index 3490b9a1..f7f4cf31 100644
--- a/project/static/js/editor.js
+++ b/project/static/js/editor.js
@@ -1,3 +1,21 @@
+function Hotkey(code) {
+    this.code = code
+    this.has_alt = ((code & 0x01 << 8) != 0)
+    this.has_ctrl = ((code & 0x01 << 9) != 0)
+    this.has_shift = ((code & 0x01 << 10) != 0)
+    this.character = String.fromCharCode(code & 0xff)
+}
+
+
+Hotkey.prototype.toString = function() {
+    mods = []
+    if(this.has_alt) mods.push('Alt')
+    if(this.has_ctrl) mods.push('Ctrl')
+    if(this.has_shift) mods.push('Shift')
+    mods.push('"'+this.character+'"')
+    return mods.join('+')
+}
+
 function Panel(panelWrap) {
     var self = this;
     self.hotkeys = [];
@@ -122,12 +140,12 @@ Panel.prototype.connectToolbar = function()
     // connect group-switch buttons
     var group_buttons = $('*.toolbar-tabs-container button', toolbar);
 
-    $.log('Found groups:', group_buttons);
+    // $.log('Found groups:', group_buttons);
 
     group_buttons.each(function() {
         var group = $(this);
         var group_name = group.attr('ui:group');
-        $.log('Connecting group: ' + group_name);
+        // $.log('Connecting group: ' + group_name);
 
         group.click(function() {
             // change the active group
@@ -151,6 +169,7 @@ Panel.prototype.connectToolbar = function()
     action_buttons.each(function() {
         var button = $(this);
         var hk = button.attr('ui:hotkey');
+        if(hk) hk = new Hotkey( parseInt(hk) );
 
         try {
             var params = $.evalJSON(button.attr('ui:action-params'));
@@ -166,15 +185,18 @@ Panel.prototype.connectToolbar = function()
 
         // connect button
         button.click(callback);
-        
+       
         // connect hotkey
-        if(hk) self.hotkeys[parseInt(hk)] = callback;
-
+        if(hk) {
+            self.hotkeys[hk.code] = callback;
+             $.log('hotkey', hk);
+        }
+        
         // tooltip
         if (button.attr('ui:tooltip') )
         {
             var tooltip = button.attr('ui:tooltip');
-            if(hk) tooltip += ' [Alt+'+hk+']';
+            if(hk) tooltip += ' ['+hk+']';
 
             button.wTooltip({
                 delay: 1000,
@@ -193,13 +215,24 @@ Panel.prototype.connectToolbar = function()
 
 Panel.prototype.hotkeyPressed = function(event)
 {
-    var callback = this.hotkeys[event.keyCode];
+    code = event.keyCode;
+    if(event.altKey) code = code | 0x100;
+    if(event.ctrlKey) code = code | 0x200;
+    if(event.shiftKey) code = code | 0x400;
+
+    var callback = this.hotkeys[code];
     if(callback) callback();
 }
 
 Panel.prototype.isHotkey = function(event) {
-    if( event.altKey && (this.hotkeys[event.keyCode] != null) )
+    code = event.keyCode;
+    if(event.altKey) code = code | 0x100;
+    if(event.ctrlKey) code = code | 0x200;
+    if(event.shiftKey) code = code | 0x400;
+
+    if(this.hotkeys[code] != null)
         return true;
+        
     return false;
 }
 
@@ -522,7 +555,7 @@ Editor.prototype.showPopup = function(name, text, timeout)
 
     var box = $('#message-box > #' + name);
     $('*.data', box).html(text || '');
-    box.fadeIn();
+    box.fadeIn(100);
  
     if(timeout > 0)
         setTimeout( $.fbind(self, self.advancePopupQueue), timeout);
@@ -534,14 +567,14 @@ Editor.prototype.advancePopupQueue = function() {
     if(elem) {
         var box = $('#message-box > #' + elem[0]);
 
-        box.fadeOut(200, function()
+        box.fadeOut(100, function()
         {
-            $('*.data', box).html();
+            $('*.data', box).html('');
 
             if( self.popupQueue.length > 0) {
                 var ibox = $('#message-box > #' + self.popupQueue[0][0]);
-                $('*.data', ibox).html(self.popupQueue[0][1]);
-                ibox.fadeIn();
+                $('*.data', ibox).html(self.popupQueue[0][1] || '');
+                ibox.fadeIn(100);
                 if(self.popupQueue[0][2] > 0)
                     setTimeout( $.fbind(self, self.advancePopupQueue), self.popupQueue[0][2]);
             }
diff --git a/project/templates/explorer/file_list.html b/project/templates/explorer/file_list.html
index a36d353a..d47a158a 100644
--- a/project/templates/explorer/file_list.html
+++ b/project/templates/explorer/file_list.html
@@ -48,7 +48,9 @@ $(function() {
 
diff --git a/project/templates/explorer/panels/xmleditor.html b/project/templates/explorer/panels/xmleditor.html
index 3a1baf1c..5c622bbf 100644
--- a/project/templates/explorer/panels/xmleditor.html
+++ b/project/templates/explorer/panels/xmleditor.html
@@ -16,7 +16,7 @@ panel_hooks = {
         var textareaId = 'xmleditor-' + Math.ceil(Math.random() * 1000000000);
 	$('textarea', panel).attr('id', textareaId);
 
-	var texteditor = CodeMirror.fromTextArea(textareaId, {
+	var texteditor = CodeMirror.fromTextArea(textareaId, {            
             parserfile: 'parsexml.js',
             path: "{{STATIC_URL}}js/codemirror/",
             stylesheet: "{{STATIC_URL}}css/xmlcolors.css",
-- 
2.20.1