Merge branch 'gilbert' of https://github.com/melinath/philo into gilbert
authorHarris Lapiroff <hlapirof@oberlin.edu>
Thu, 9 Jun 2011 17:19:06 +0000 (13:19 -0400)
committerHarris Lapiroff <hlapirof@oberlin.edu>
Thu, 9 Jun 2011 17:19:06 +0000 (13:19 -0400)
358 files changed:
.gitignore
.gitmodules [new file with mode: 0644]
README
README.markdown
__init__.py [deleted file]
docs/Makefile [new file with mode: 0644]
docs/_ext/djangodocs.py [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/dummy-settings.py [moved from contrib/__init__.py with 100% similarity]
docs/index.rst [new file with mode: 0644]
docs/intro.rst [new file with mode: 0644]
docs/make.bat [new file with mode: 0644]
docs/models/entities.rst [new file with mode: 0644]
docs/models/intro.rst [new file with mode: 0644]
docs/models/nodes-and-views.rst [new file with mode: 0644]
philo/LICENSE [moved from LICENSE with 100% similarity]
philo/__init__.py [new file with mode: 0644]
philo/admin/__init__.py [moved from admin/__init__.py with 100% similarity]
philo/admin/base.py [moved from admin/base.py with 100% similarity]
philo/admin/collections.py [moved from admin/collections.py with 100% similarity]
philo/admin/forms/__init__.py [moved from admin/forms/__init__.py with 100% similarity]
philo/admin/forms/attributes.py [moved from admin/forms/attributes.py with 100% similarity]
philo/admin/forms/containers.py [moved from admin/forms/containers.py with 100% similarity]
philo/admin/nodes.py [moved from admin/nodes.py with 79% similarity]
philo/admin/pages.py [moved from admin/pages.py with 84% similarity]
philo/admin/widgets.py [moved from admin/widgets.py with 86% similarity]
philo/contrib/__init__.py [moved from contrib/julian/__init__.py with 100% similarity]
philo/contrib/gilbert/README [new file with mode: 0644]
philo/contrib/gilbert/__init__.py [new file with mode: 0644]
philo/contrib/gilbert/admin.py [new file with mode: 0644]
philo/contrib/gilbert/exceptions.py [new file with mode: 0644]
philo/contrib/gilbert/extdirect/__init__.py [new file with mode: 0644]
philo/contrib/gilbert/extdirect/core.py [new file with mode: 0644]
philo/contrib/gilbert/extdirect/forms.py [new file with mode: 0644]
philo/contrib/gilbert/gilbert.py [new file with mode: 0644]
philo/contrib/gilbert/models.py [new file with mode: 0644]
philo/contrib/gilbert/plugins/__init__.py [moved from contrib/julian/migrations/__init__.py with 100% similarity]
philo/contrib/gilbert/plugins/auth.py [new file with mode: 0644]
philo/contrib/gilbert/plugins/base.py [new file with mode: 0644]
philo/contrib/gilbert/plugins/models.py [new file with mode: 0644]
philo/contrib/gilbert/sites.py [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/gilbert.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/lib/app.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/lib/models.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/lib/plugins.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/lib/ui/forms.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/lib/ui/ui.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/lib/ui/windows.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/arrv.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Down.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Normal.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Over.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Pressed.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/left-over.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/left.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/menu.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/right-over.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/right.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/split.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/splith.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/splitv.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Down.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Normal.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Over.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Pressed.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/fills/30b.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/fills/30w.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/fills/70w.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/fills/80b.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/form/checked.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/form/field.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/form/field.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/form/radioed.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/form/unchecked.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/form/unradioed.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/gloss/light-gloss.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/gradients/30white-to-trans.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/gradients/dark-inner-shadow.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-bottom.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-top.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/grid/row-editor-btns.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/balloon.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/cross.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/d-first.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/d-last.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/d-next.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/d-prev.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/d-refresh.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation32.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/first.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/information.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/last.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/next.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/no.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/prev.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/refresh.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/icons/yes.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/listview/az.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/cols.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/header.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/headerdrop.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-asc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-desc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-asc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-desc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/listview/za.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/load.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-bottom.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-left.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-right.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-top.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/progress.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/e.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/n.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/ne.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/nw.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/s.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/se.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/sw.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/resizable/w.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/scroller/l.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/scroller/r.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/slider/h.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbh.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbv.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/slider/v.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/30-b.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/30-w.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/30w.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/30white-to-trans.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/70w.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/80-b.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/?.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/arrow buttons.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/btn.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/check.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-bottom.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-top.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/dark-inner-shadow.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/exclamation.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/grad-height-25.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/h.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/light-gloss.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/listview-sort.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/listviewheader.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/mini.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/progress.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/resizable corner.psd [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/resizable long.psd [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/row-editor-btns.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/scroller.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/sort.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/tabs.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/tb-sprite.psd [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/tb.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/thumbh.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/thumbv.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/tools.psd [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip-invalid.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/clear.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/date.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/drop.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/search.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/v.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/window-dark.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/window.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/src/x.opacity [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Bottom.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Top.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/close.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Bottom.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Top.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Bottom.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Top.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tb-sprite.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tb.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tools.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/arrows.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-add.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-between.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-no.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-over.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-under.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-yes.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus-nl.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus-nl.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-line.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus-nl.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus-nl.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/folder-open.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/tree/folder.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/tree/leaf.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/murano/images/tree/loading.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tree/s.gif [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/trigger/blank.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/trigger/clear.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/trigger/date.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/trigger/drop.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/trigger/search.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/bc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/bl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/br.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/c.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-br.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-c.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-ml.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-mr.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tr.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/ml.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/mr.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/tc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/tl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/tt/tr.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/br.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/c.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/l.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/r.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tr.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/br.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/c.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/l.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/r.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tc.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tl.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tr.png [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/murano/murano.css [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/plugins/auth.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/plugins/models.js [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/superboxselect/SuperBoxSelect.js [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/superboxselect/clear.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/superboxselect/close.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/superboxselect/expand.png [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/superboxselect/superboxselect-gray-extend.css [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/superboxselect/superboxselect.css [new file with mode: 0755]
philo/contrib/gilbert/static/gilbert/wallpaper.README [new file with mode: 0644]
philo/contrib/gilbert/static/gilbert/wallpaper.jpg [new file with mode: 0644]
philo/contrib/gilbert/templates/gilbert/api.js [new file with mode: 0644]
philo/contrib/gilbert/templates/gilbert/base.html [new file with mode: 0644]
philo/contrib/gilbert/templates/gilbert/icons.css [new file with mode: 0644]
philo/contrib/gilbert/templates/gilbert/index.html [new file with mode: 0644]
philo/contrib/gilbert/templates/gilbert/login.html [new file with mode: 0644]
philo/contrib/julian/__init__.py [moved from contrib/penfield/__init__.py with 100% similarity]
philo/contrib/julian/admin.py [moved from contrib/julian/admin.py with 100% similarity]
philo/contrib/julian/feedgenerator.py [moved from contrib/julian/feedgenerator.py with 100% similarity]
philo/contrib/julian/migrations/0001_initial.py [moved from contrib/julian/migrations/0001_initial.py with 100% similarity]
philo/contrib/julian/migrations/__init__.py [moved from contrib/penfield/migrations/__init__.py with 100% similarity]
philo/contrib/julian/models.py [moved from contrib/julian/models.py with 100% similarity]
philo/contrib/penfield/__init__.py [moved from contrib/penfield/templatetags/__init__.py with 100% similarity]
philo/contrib/penfield/admin.py [moved from contrib/penfield/admin.py with 100% similarity]
philo/contrib/penfield/exceptions.py [moved from contrib/penfield/exceptions.py with 100% similarity]
philo/contrib/penfield/gilbert.py [new file with mode: 0644]
philo/contrib/penfield/middleware.py [moved from contrib/penfield/middleware.py with 100% similarity]
philo/contrib/penfield/migrations/0001_initial.py [moved from contrib/penfield/migrations/0001_initial.py with 100% similarity]
philo/contrib/penfield/migrations/0002_auto.py [moved from contrib/penfield/migrations/0002_auto.py with 100% similarity]
philo/contrib/penfield/migrations/0003_auto__add_field_newsletterview_feed_type__add_field_newsletterview_ite.py [moved from contrib/penfield/migrations/0003_auto__add_field_newsletterview_feed_type__add_field_newsletterview_ite.py with 100% similarity]
philo/contrib/penfield/migrations/0004_auto__add_field_newsletterview_feed_length__add_field_blogview_feed_le.py [moved from contrib/penfield/migrations/0004_auto__add_field_newsletterview_feed_length__add_field_blogview_feed_le.py with 100% similarity]
philo/contrib/penfield/migrations/__init__.py [moved from contrib/shipherd/__init__.py with 100% similarity]
philo/contrib/penfield/models.py [moved from contrib/penfield/models.py with 100% similarity]
philo/contrib/penfield/templatetags/__init__.py [moved from contrib/shipherd/migrations/__init__.py with 100% similarity]
philo/contrib/penfield/templatetags/penfield.py [moved from contrib/penfield/templatetags/penfield.py with 100% similarity]
philo/contrib/penfield/validators.py [moved from contrib/penfield/validators.py with 100% similarity]
philo/contrib/shipherd/__init__.py [moved from contrib/shipherd/templatetags/__init__.py with 100% similarity]
philo/contrib/shipherd/admin.py [moved from contrib/shipherd/admin.py with 100% similarity]
philo/contrib/shipherd/migrations/0001_initial.py [moved from contrib/shipherd/migrations/0001_initial.py with 100% similarity]
philo/contrib/shipherd/migrations/0002_auto.py [moved from contrib/shipherd/migrations/0002_auto.py with 100% similarity]
philo/contrib/shipherd/migrations/__init__.py [moved from contrib/waldo/__init__.py with 100% similarity]
philo/contrib/shipherd/models.py [moved from contrib/shipherd/models.py with 100% similarity]
philo/contrib/shipherd/templatetags/__init__.py [moved from loaders/__init__.py with 100% similarity]
philo/contrib/shipherd/templatetags/shipherd.py [moved from contrib/shipherd/templatetags/shipherd.py with 96% similarity]
philo/contrib/sobol/__init__.py [moved from contrib/sobol/__init__.py with 100% similarity]
philo/contrib/sobol/admin.py [moved from contrib/sobol/admin.py with 100% similarity]
philo/contrib/sobol/forms.py [moved from contrib/sobol/forms.py with 100% similarity]
philo/contrib/sobol/models.py [moved from contrib/sobol/models.py with 100% similarity]
philo/contrib/sobol/search.py [moved from contrib/sobol/search.py with 100% similarity]
philo/contrib/sobol/templates/admin/sobol/search/grappelli_results.html [moved from contrib/sobol/templates/admin/sobol/search/grappelli_results.html with 100% similarity]
philo/contrib/sobol/templates/admin/sobol/search/results.html [moved from contrib/sobol/templates/admin/sobol/search/results.html with 100% similarity]
philo/contrib/sobol/templates/search/googlesearch.html [moved from contrib/sobol/templates/search/googlesearch.html with 100% similarity]
philo/contrib/sobol/utils.py [moved from contrib/sobol/utils.py with 100% similarity]
philo/contrib/waldo/__init__.py [moved from templatetags/__init__.py with 100% similarity]
philo/contrib/waldo/forms.py [moved from contrib/waldo/forms.py with 100% similarity]
philo/contrib/waldo/models.py [moved from contrib/waldo/models.py with 100% similarity]
philo/contrib/waldo/tokens.py [moved from contrib/waldo/tokens.py with 100% similarity]
philo/exceptions.py [moved from exceptions.py with 100% similarity]
philo/fixtures/test_fixtures.json [moved from fixtures/test_fixtures.json with 100% similarity]
philo/forms/__init__.py [moved from forms/__init__.py with 100% similarity]
philo/forms/entities.py [moved from forms/entities.py with 100% similarity]
philo/forms/fields.py [moved from forms/fields.py with 100% similarity]
philo/gilbert.py [new file with mode: 0644]
philo/hacks.py [new file with mode: 0644]
philo/loaders/__init__.py [new file with mode: 0644]
philo/loaders/database.py [moved from loaders/database.py with 100% similarity]
philo/middleware.py [moved from middleware.py with 100% similarity]
philo/migrations/0001_initial.py [moved from migrations/0001_initial.py with 100% similarity]
philo/migrations/0002_auto__add_field_attribute_value.py [moved from migrations/0002_auto__add_field_attribute_value.py with 100% similarity]
philo/migrations/0003_move_json.py [moved from migrations/0003_move_json.py with 100% similarity]
philo/migrations/0004_auto__del_field_attribute_json_value.py [moved from migrations/0004_auto__del_field_attribute_json_value.py with 100% similarity]
philo/migrations/0005_add_attribute_values.py [moved from migrations/0005_add_attribute_values.py with 100% similarity]
philo/migrations/0006_move_attribute_and_relationship_values.py [moved from migrations/0006_move_attribute_and_relationship_values.py with 100% similarity]
philo/migrations/0007_auto__del_relationship__del_field_attribute_value.py [moved from migrations/0007_auto__del_relationship__del_field_attribute_value.py with 100% similarity]
philo/migrations/0008_auto__del_field_manytomanyvalue_object_ids.py [moved from migrations/0008_auto__del_field_manytomanyvalue_object_ids.py with 100% similarity]
philo/migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py [moved from migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py with 100% similarity]
philo/migrations/0010_auto__add_field_redirect_target_node__add_field_redirect_url_or_subpat.py [moved from migrations/0010_auto__add_field_redirect_target_node__add_field_redirect_url_or_subpat.py with 100% similarity]
philo/migrations/0011_move_target_url.py [moved from migrations/0011_move_target_url.py with 100% similarity]
philo/migrations/0012_auto__del_field_redirect_target.py [moved from migrations/0012_auto__del_field_redirect_target.py with 100% similarity]
philo/migrations/0013_auto.py [moved from migrations/0013_auto.py with 100% similarity]
philo/migrations/0014_auto.py [moved from migrations/0014_auto.py with 100% similarity]
philo/migrations/__init__.py [moved from migrations/__init__.py with 100% similarity]
philo/models/__init__.py [moved from models/__init__.py with 100% similarity]
philo/models/base.py [moved from models/base.py with 73% similarity]
philo/models/collections.py [moved from models/collections.py with 100% similarity]
philo/models/fields/__init__.py [moved from models/fields/__init__.py with 96% similarity]
philo/models/fields/entities.py [moved from models/fields/entities.py with 100% similarity]
philo/models/nodes.py [moved from models/nodes.py with 100% similarity]
philo/models/pages.py [moved from models/pages.py with 100% similarity]
philo/signals.py [moved from signals.py with 100% similarity]
philo/static/admin/js/TagCreation.js [moved from media/admin/js/TagCreation.js with 100% similarity]
philo/templates/admin/philo/edit_inline/grappelli_tabular_attribute.html [moved from templates/admin/philo/edit_inline/grappelli_tabular_attribute.html with 56% similarity]
philo/templates/admin/philo/edit_inline/grappelli_tabular_container.html [moved from templates/admin/philo/edit_inline/grappelli_tabular_container.html with 81% similarity]
philo/templates/admin/philo/edit_inline/tabular_attribute.html [moved from templates/admin/philo/edit_inline/tabular_attribute.html with 100% similarity]
philo/templates/admin/philo/edit_inline/tabular_container.html [moved from templates/admin/philo/edit_inline/tabular_container.html with 100% similarity]
philo/templates/admin/philo/page/add_form.html [new file with mode: 0644]
philo/templatetags/__init__.py [new file with mode: 0644]
philo/templatetags/collections.py [moved from templatetags/collections.py with 100% similarity]
philo/templatetags/containers.py [moved from templatetags/containers.py with 100% similarity]
philo/templatetags/embed.py [moved from templatetags/embed.py with 100% similarity]
philo/templatetags/include_string.py [moved from templatetags/include_string.py with 100% similarity]
philo/templatetags/nodes.py [moved from templatetags/nodes.py with 100% similarity]
philo/tests.py [moved from tests.py with 94% similarity]
philo/urls.py [moved from urls.py with 100% similarity]
philo/utils.py [moved from utils.py with 100% similarity]
philo/validators.py [moved from validators.py with 98% similarity]
philo/views.py [moved from views.py with 100% similarity]
setup.py [new file with mode: 0644]
templates/admin/philo/page/add_form.html [deleted file]

index 0d20b64..073067c 100644 (file)
@@ -1 +1,2 @@
 *.pyc
+docs/_build/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..dce4632
--- /dev/null
@@ -0,0 +1,6 @@
+[submodule "philo/contrib/gilbert/static/gilbert/extjs"]
+       path = contrib/gilbert/static/gilbert/extjs
+       url = git://git.ithinksw.org/extjs.git
+[submodule "philo/contrib/gilbert/static/gilbert/fugue-icons"]
+       path = contrib/gilbert/static/gilbert/fugue-icons
+       url = git://git.ithinksw.org/fugue-icons.git
diff --git a/README b/README
index 4b1a6f7..cb5f47a 100644 (file)
--- a/README
+++ b/README
@@ -2,11 +2,11 @@ Philo is a foundation for developing web content management systems.
 
 Prerequisites:
        * Python 2.5.4+ <http://www.python.org/>
-       * Django 1.2+ <http://www.djangoproject.com/>
-       * django-mptt e734079+ <https://github.com/django-mptt/django-mptt/> 
+       * Django 1.3+ <http://www.djangoproject.com/>
+       * django-mptt e734079+ <https://github.com/django-mptt/django-mptt/>
        * (Optional) django-grappelli 2.0+ <http://code.google.com/p/django-grappelli/>
-       * (Optional) recaptcha-django r6 <http://code.google.com/p/recaptcha-django/>
        * (Optional) south 0.7.2+ <http://south.aeracode.org/>
+       * (Optional) recaptcha-django r6 <http://code.google.com/p/recaptcha-django/>
 
 To contribute, please visit the project website <http://philo.ithinksw.org/>. Feel free to join us on IRC at irc://irc.oftc.net/#philo.
 
@@ -21,3 +21,7 @@ After installing philo and mptt on your python path, make sure to complete the f
 4. Optionally add a root node to your current Site.
 
 Philo should be ready to go!
+
+If you are using philo.contrib.gilbert, you will additionally need to complete the following steps:
+
+1. add 'django.core.context_processors.request' to settings.TEMPLATE_CONTEXT_PROCESSORS
index 8060db8..b529ca2 100644 (file)
@@ -3,13 +3,13 @@ Philo is a foundation for developing web content management systems.
 Prerequisites:
 
  * [Python 2.5.4+ &lt;http://www.python.org&gt;](http://www.python.org/)
- * [Django 1.2+ &lt;http://www.djangoproject.com/&gt;](http://www.djangoproject.com/)
+ * [Django 1.3+ &lt;http://www.djangoproject.com/&gt;](http://www.djangoproject.com/)
  * [django-mptt e734079+ &lt;https://github.com/django-mptt/django-mptt/&gt;](https://github.com/django-mptt/django-mptt/)
  * (Optional) [django-grappelli 2.0+ &lt;http://code.google.com/p/django-grappelli/&gt;](http://code.google.com/p/django-grappelli/)
  * (Optional) [south 0.7.2+ &lt;http://south.aeracode.org/)](http://south.aeracode.org/)
  * (Optional) [recaptcha-django r6 &lt;http://code.google.com/p/recaptcha-django/&gt;](http://code.google.com/p/recaptcha-django/)
 
-To contribute, please visit the [project website](http://philo.ithinksw.org/). Feel free to join us on IRC at [irc://irc.oftc.net/#philo](irc://irc.oftc.net/#philo>).
+To contribute, please visit the [project website &lt;http://philo.ithinksw.org/&lt;](http://philo.ithinksw.org/). Feel free to join us on IRC at [irc://irc.oftc.net/#philo](irc://irc.oftc.net/#philo).
 
 Using philo
 ===========
@@ -22,3 +22,7 @@ After installing philo and mptt on your python path, make sure to complete the f
 4. Optionally add a root node to your current Site.
 
 Philo should be ready to go!
+
+If you are using philo.contrib.gilbert, you will additionally need to complete the following steps:
+
+1. add 'django.core.context_processors.request' to settings.TEMPLATE_CONTEXT_PROCESSORS
diff --git a/__init__.py b/__init__.py
deleted file mode 100644 (file)
index ba78dda..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-from philo.loaders.database import Loader
-
-
-_loader = Loader()
-
-
-def load_template_source(template_name, template_dirs=None):
-    # For backwards compatibility
-    import warnings
-    warnings.warn(
-        "'philo.load_template_source' is deprecated; use 'philo.loaders.database.Loader' instead.",
-        PendingDeprecationWarning
-    )
-    return _loader.load_template_source(template_name, template_dirs)
-load_template_source.is_usable = True
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..16c56a5
--- /dev/null
@@ -0,0 +1,130 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html       to make standalone HTML files"
+       @echo "  dirhtml    to make HTML files named index.html in directories"
+       @echo "  singlehtml to make a single large HTML file"
+       @echo "  pickle     to make pickle files"
+       @echo "  json       to make JSON files"
+       @echo "  htmlhelp   to make HTML files and a HTML help project"
+       @echo "  qthelp     to make HTML files and a qthelp project"
+       @echo "  devhelp    to make HTML files and a Devhelp project"
+       @echo "  epub       to make an epub"
+       @echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+       @echo "  text       to make text files"
+       @echo "  man        to make manual pages"
+       @echo "  changes    to make an overview of all changed/added/deprecated items"
+       @echo "  linkcheck  to check all external links for integrity"
+       @echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       -rm -rf $(BUILDDIR)/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+       $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+       @echo
+       @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+       @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Philo.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Philo.qhc"
+
+devhelp:
+       $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+       @echo
+       @echo "Build finished."
+       @echo "To view the help file:"
+       @echo "# mkdir -p $$HOME/.local/share/devhelp/Philo"
+       @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Philo"
+       @echo "# devhelp"
+
+epub:
+       $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+       @echo
+       @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+       @echo "Run \`make' in that directory to run these through (pdf)latex" \
+             "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through pdflatex..."
+       make -C $(BUILDDIR)/latex all-pdf
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+       $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+       @echo
+       @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+       @echo
+       @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+       @echo
+       @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py
new file mode 100644 (file)
index 0000000..7710786
--- /dev/null
@@ -0,0 +1,248 @@
+"""
+Sphinx plugins for Django documentation.
+"""
+import os
+import re
+
+from docutils import nodes, transforms
+try:
+    import json
+except ImportError:
+    try:
+        import simplejson as json
+    except ImportError:
+        try:
+            from django.utils import simplejson as json
+        except ImportError:
+            json = None
+
+from sphinx import addnodes, roles
+from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.writers.html import SmartyPantsHTMLTranslator
+from sphinx.util.console import bold
+from sphinx.util.compat import Directive
+
+# RE for option descriptions without a '--' prefix
+simple_option_desc_re = re.compile(
+    r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
+
+def setup(app):
+    app.add_crossref_type(
+        directivename = "setting",
+        rolename      = "setting",
+        indextemplate = "pair: %s; setting",
+    )
+    app.add_crossref_type(
+        directivename = "templatetag",
+        rolename      = "ttag",
+        indextemplate = "pair: %s; template tag"
+    )
+    app.add_crossref_type(
+        directivename = "templatefilter",
+        rolename      = "tfilter",
+        indextemplate = "pair: %s; template filter"
+    )
+    app.add_crossref_type(
+        directivename = "fieldlookup",
+        rolename      = "lookup",
+        indextemplate = "pair: %s; field lookup type",
+    )
+    app.add_description_unit(
+        directivename = "django-admin",
+        rolename      = "djadmin",
+        indextemplate = "pair: %s; django-admin command",
+        parse_node    = parse_django_admin_node,
+    )
+    app.add_description_unit(
+        directivename = "django-admin-option",
+        rolename      = "djadminopt",
+        indextemplate = "pair: %s; django-admin command-line option",
+        parse_node    = parse_django_adminopt_node,
+    )
+    app.add_config_value('django_next_version', '0.0', True)
+    app.add_directive('versionadded', VersionDirective)
+    app.add_directive('versionchanged', VersionDirective)
+    app.add_transform(SuppressBlockquotes)
+    app.add_builder(DjangoStandaloneHTMLBuilder)
+
+
+class VersionDirective(Directive):
+    has_content = True
+    required_arguments = 1
+    optional_arguments = 1
+    final_argument_whitespace = True
+    option_spec = {}
+
+    def run(self):
+        env = self.state.document.settings.env
+        arg0 = self.arguments[0]
+        is_nextversion = env.config.django_next_version == arg0
+        ret = []
+        node = addnodes.versionmodified()
+        ret.append(node)
+        if not is_nextversion:
+            if len(self.arguments) == 1:
+                linktext = 'Please, see the release notes </releases/%s>' % (arg0)
+                xrefs = roles.XRefRole()('doc', linktext, linktext, self.lineno, self.state)
+                node.extend(xrefs[0])
+            node['version'] = arg0
+        else:
+            node['version'] = "Development version"
+        node['type'] = self.name
+        if len(self.arguments) == 2:
+            inodes, messages = self.state.inline_text(self.arguments[1], self.lineno+1)
+            node.extend(inodes)
+            if self.content:
+                self.state.nested_parse(self.content, self.content_offset, node)
+            ret = ret + messages
+        env.note_versionchange(node['type'], node['version'], node, self.lineno)
+        return ret
+
+
+class SuppressBlockquotes(transforms.Transform):
+    """
+    Remove the default blockquotes that encase indented list, tables, etc.
+    """
+    default_priority = 300
+
+    suppress_blockquote_child_nodes = (
+        nodes.bullet_list,
+        nodes.enumerated_list,
+        nodes.definition_list,
+        nodes.literal_block,
+        nodes.doctest_block,
+        nodes.line_block,
+        nodes.table
+    )
+
+    def apply(self):
+        for node in self.document.traverse(nodes.block_quote):
+            if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes):
+                node.replace_self(node.children[0])
+
+class DjangoHTMLTranslator(SmartyPantsHTMLTranslator):
+    """
+    Django-specific reST to HTML tweaks.
+    """
+
+    # Don't use border=1, which docutils does by default.
+    def visit_table(self, node):
+        self.body.append(self.starttag(node, 'table', CLASS='docutils'))
+
+    # <big>? Really?
+    def visit_desc_parameterlist(self, node):
+        self.body.append('(')
+        self.first_param = 1
+
+    def depart_desc_parameterlist(self, node):
+        self.body.append(')')
+
+    #
+    # Don't apply smartypants to literal blocks
+    #
+    def visit_literal_block(self, node):
+        self.no_smarty += 1
+        SmartyPantsHTMLTranslator.visit_literal_block(self, node)
+
+    def depart_literal_block(self, node):
+        SmartyPantsHTMLTranslator.depart_literal_block(self, node)
+        self.no_smarty -= 1
+
+    #
+    # Turn the "new in version" stuff (versionadded/versionchanged) into a
+    # better callout -- the Sphinx default is just a little span,
+    # which is a bit less obvious that I'd like.
+    #
+    # FIXME: these messages are all hardcoded in English. We need to change
+    # that to accomodate other language docs, but I can't work out how to make
+    # that work.
+    #
+    version_text = {
+        'deprecated':       'Deprecated in Django %s',
+        'versionchanged':   'Changed in Django %s',
+        'versionadded':     'New in Django %s',
+    }
+
+    def visit_versionmodified(self, node):
+        self.body.append(
+            self.starttag(node, 'div', CLASS=node['type'])
+        )
+        title = "%s%s" % (
+            self.version_text[node['type']] % node['version'],
+            len(node) and ":" or "."
+        )
+        self.body.append('<span class="title">%s</span> ' % title)
+
+    def depart_versionmodified(self, node):
+        self.body.append("</div>\n")
+
+    # Give each section a unique ID -- nice for custom CSS hooks
+    def visit_section(self, node):
+        old_ids = node.get('ids', [])
+        node['ids'] = ['s-' + i for i in old_ids]
+        node['ids'].extend(old_ids)
+        SmartyPantsHTMLTranslator.visit_section(self, node)
+        node['ids'] = old_ids
+
+def parse_django_admin_node(env, sig, signode):
+    command = sig.split(' ')[0]
+    env._django_curr_admin_command = command
+    title = "django-admin.py %s" % sig
+    signode += addnodes.desc_name(title, title)
+    return sig
+
+def parse_django_adminopt_node(env, sig, signode):
+    """A copy of sphinx.directives.CmdoptionDesc.parse_signature()"""
+    from sphinx.domains.std import option_desc_re
+    count = 0
+    firstname = ''
+    for m in option_desc_re.finditer(sig):
+        optname, args = m.groups()
+        if count:
+            signode += addnodes.desc_addname(', ', ', ')
+        signode += addnodes.desc_name(optname, optname)
+        signode += addnodes.desc_addname(args, args)
+        if not count:
+            firstname = optname
+        count += 1
+    if not count:
+        for m in simple_option_desc_re.finditer(sig):
+            optname, args = m.groups()
+            if count:
+                signode += addnodes.desc_addname(', ', ', ')
+            signode += addnodes.desc_name(optname, optname)
+            signode += addnodes.desc_addname(args, args)
+            if not count:
+                firstname = optname
+            count += 1
+    if not firstname:
+        raise ValueError
+    return firstname
+
+
+class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder):
+    """
+    Subclass to add some extra things we need.
+    """
+
+    name = 'djangohtml'
+
+    def finish(self):
+        super(DjangoStandaloneHTMLBuilder, self).finish()
+        if json is None:
+            self.warn("cannot create templatebuiltins.js due to missing simplejson dependency")
+            return
+        self.info(bold("writing templatebuiltins.js..."))
+        xrefs = self.env.domaindata["std"]["objects"]
+        templatebuiltins = {
+            "ttags": [n for ((t, n), (l, a)) in xrefs.items()
+                        if t == "templatetag" and l == "ref/templates/builtins"],
+            "tfilters": [n for ((t, n), (l, a)) in xrefs.items()
+                        if t == "templatefilter" and l == "ref/templates/builtins"],
+        }
+        outfilename = os.path.join(self.outdir, "templatebuiltins.js")
+        f = open(outfilename, 'wb')
+        f.write('var django_template_builtins = ')
+        json.dump(templatebuiltins, f)
+        f.write(';\n')
+        f.close();
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..043219d
--- /dev/null
@@ -0,0 +1,220 @@
+# -*- coding: utf-8 -*-
+#
+# Philo documentation build configuration file, created by
+# sphinx-quickstart on Fri Jan 28 14:04:16 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext")))
+sys.path.append(os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'dummy-settings'
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['djangodocs', 'sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Philo'
+copyright = u'2011, Joseph Spiros'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+from philo import VERSION
+version = '%s.%s' % (VERSION[0], VERSION[1])
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Philodoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'Philo.tex', u'Philo Documentation',
+   u'Stephen Burrows', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'philo', u'Philo Documentation',
+     [u'Stephen Burrows'], 1)
+]
similarity index 100%
rename from contrib/__init__.py
rename to docs/dummy-settings.py
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..cfc7136
--- /dev/null
@@ -0,0 +1,38 @@
+.. Philo documentation master file, created by
+   sphinx-quickstart on Fri Jan 28 14:04:16 2011.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to Philo's documentation!
+=================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+   
+   intro
+   models/intro
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+What is Philo?
+==============
+
+Philo is a foundation for developing web content management systems.
+
+Prerequisites:
+
+* `Python 2.5.4+ <http://www.python.org>`_
+* `Django 1.2+ <http://www.djangoproject.com/>`_
+* `django-mptt e734079+ <https://github.com/django-mptt/django-mptt/>`_
+* (Optional) `django-grappelli 2.0+ <http://code.google.com/p/django-grappelli/>`_
+* (Optional) `south 0.7.2+ <http://south.aeracode.org/>`_
+* (Optional) `recaptcha-django r6 <http://code.google.com/p/recaptcha-django/>`_
+
+To contribute, please visit the `project website <http://philo.ithinksw.org/>`_ or make a fork of the `git repository <http://github.com/ithinksw/philo/>`_. Feel free to join us on IRC at `irc://irc.oftc.net/#philo <irc://irc.oftc.net/#philo>`_.
diff --git a/docs/intro.rst b/docs/intro.rst
new file mode 100644 (file)
index 0000000..33d1a98
--- /dev/null
@@ -0,0 +1,35 @@
+How to get started with philo
+=============================
+
+After installing `philo`_ and `mptt`_ on your python path, make sure to complete the following steps:
+
+1. add :mod:`philo` and :mod:`mptt` to :setting:`settings.INSTALLED_APPS`::
+       
+       INSTALLED_APPS = (
+               ...
+               'philo',
+               'mptt',
+               ...
+       )
+       
+2. add :class:`philo.middleware.RequestNodeMiddleware` to :setting:`settings.MIDDLEWARE_CLASSES`::
+       
+       MIDDLEWARE_CLASSES = (
+               ...
+               'philo.middleware.RequestNodeMiddleware',
+               ...
+       )
+       
+3. include :mod:`philo.urls` somewhere in your urls.py file. For example::
+       
+       from django.conf.urls.defaults import patterns, include, url
+       urlpatterns = patterns('',
+               url(r'^', include('philo.urls')),
+       )
+       
+4. Optionally add a root :class:`node <philo.models.Node>` to your current :class:`Site` in the admin interface.
+
+Philo should be ready to go!
+
+.. _philo: http://github.com/ithinksw/philo
+.. _mptt: http://github.com/django-mptt/django-mptt
\ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644 (file)
index 0000000..25f0d2a
--- /dev/null
@@ -0,0 +1,170 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+       set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+       set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+       :help
+       echo.Please use `make ^<target^>` where ^<target^> is one of
+       echo.  html       to make standalone HTML files
+       echo.  dirhtml    to make HTML files named index.html in directories
+       echo.  singlehtml to make a single large HTML file
+       echo.  pickle     to make pickle files
+       echo.  json       to make JSON files
+       echo.  htmlhelp   to make HTML files and a HTML help project
+       echo.  qthelp     to make HTML files and a qthelp project
+       echo.  devhelp    to make HTML files and a Devhelp project
+       echo.  epub       to make an epub
+       echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+       echo.  text       to make text files
+       echo.  man        to make manual pages
+       echo.  changes    to make an overview over all changed/added/deprecated items
+       echo.  linkcheck  to check all external links for integrity
+       echo.  doctest    to run all doctests embedded in the documentation if enabled
+       goto end
+)
+
+if "%1" == "clean" (
+       for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+       del /q /s %BUILDDIR%\*
+       goto end
+)
+
+if "%1" == "html" (
+       %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+       goto end
+)
+
+if "%1" == "dirhtml" (
+       %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+       goto end
+)
+
+if "%1" == "singlehtml" (
+       %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+       goto end
+)
+
+if "%1" == "pickle" (
+       %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can process the pickle files.
+       goto end
+)
+
+if "%1" == "json" (
+       %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can process the JSON files.
+       goto end
+)
+
+if "%1" == "htmlhelp" (
+       %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+       goto end
+)
+
+if "%1" == "qthelp" (
+       %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+       echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Philo.qhcp
+       echo.To view the help file:
+       echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Philo.ghc
+       goto end
+)
+
+if "%1" == "devhelp" (
+       %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished.
+       goto end
+)
+
+if "%1" == "epub" (
+       %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The epub file is in %BUILDDIR%/epub.
+       goto end
+)
+
+if "%1" == "latex" (
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+       goto end
+)
+
+if "%1" == "text" (
+       %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The text files are in %BUILDDIR%/text.
+       goto end
+)
+
+if "%1" == "man" (
+       %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The manual pages are in %BUILDDIR%/man.
+       goto end
+)
+
+if "%1" == "changes" (
+       %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.The overview file is in %BUILDDIR%/changes.
+       goto end
+)
+
+if "%1" == "linkcheck" (
+       %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+       goto end
+)
+
+if "%1" == "doctest" (
+       %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+       goto end
+)
+
+:end
diff --git a/docs/models/entities.rst b/docs/models/entities.rst
new file mode 100644 (file)
index 0000000..a834b13
--- /dev/null
@@ -0,0 +1,54 @@
+Entities and Attributes
+=======================
+
+.. module:: philo.models.base
+
+One of the core concepts in Philo is the relationship between the :class:`Entity` and :class:`Attribute` classes. :class:`Attribute`\ s represent an arbitrary key/value pair by having one :class:`GenericForeignKey` to an :class:`Entity` and another to an :class:`AttributeValue`.
+
+
+Attributes
+----------
+
+.. autoclass:: Attribute
+   :members:
+
+.. autoclass:: AttributeValue
+   :members:
+
+.. automodule:: philo.models.base
+   :members: attribute_value_limiter
+
+.. autoclass:: JSONValue
+   :show-inheritance:
+
+.. autoclass:: ForeignKeyValue
+   :show-inheritance:
+
+.. autoclass:: ManyToManyValue
+   :show-inheritance:
+
+.. automodule:: philo.models.base
+   :members: value_content_type_limiter
+
+.. autofunction:: register_value_model(model)
+.. autofunction:: unregister_value_model(model)
+
+Entities
+--------
+
+.. autoclass:: Entity
+   :members:
+   :exclude-members: attribute_set
+
+.. autoclass:: TreeManager
+   :members:
+
+.. autoclass:: TreeEntity
+   :members:
+   :exclude-members: attribute_set
+
+   .. attribute:: objects
+
+      An instance of :class:`TreeManager`.
+   
+   .. automethod:: get_path
\ No newline at end of file
diff --git a/docs/models/intro.rst b/docs/models/intro.rst
new file mode 100644 (file)
index 0000000..49b2ac1
--- /dev/null
@@ -0,0 +1,13 @@
+Philo's models
+==============
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+   
+   entities
+   nodes-and-views
+
+
+.. :module: philo.models
diff --git a/docs/models/nodes-and-views.rst b/docs/models/nodes-and-views.rst
new file mode 100644 (file)
index 0000000..bd31ceb
--- /dev/null
@@ -0,0 +1,270 @@
+Nodes and Views: Building Website structure
+===========================================
+.. currentmodule:: philo.models
+
+Nodes
+-----
+
+:class:`Node`\ s are the basic building blocks of a website using Philo. They define the URL hierarchy and connect each URL to a :class:`View` subclass instance which is used to generate an HttpResponse.
+
+.. class:: Node
+
+   :class:`!Node` subclasses :class:`TreeEntity`. It defines the following additional methods and attributes:
+
+   .. attribute:: view
+
+      :class:`GenericForeignKey` to a non-abstract subclass of :class:`View`
+
+   .. attribute:: accepts_subpath
+
+      A property shortcut for :attr:`self.view.accepts_subpath <View.accepts_subpath>`
+
+   .. method:: render_to_response(request[, extra_context=None])
+
+      This is a shortcut method for :meth:`View.render_to_response`
+
+   .. method:: get_absolute_url([request=None, with_domain=False, secure=False])
+
+      This is essentially a shortcut for calling :meth:`construct_url` without a subpath - which will return the URL of the Node.
+
+   .. method:: construct_url([subpath="/", request=None, with_domain=False, secure=False])
+
+      This method will do its best to construct a URL based on the Node's location. If with_domain is True, that URL will include a domain and a protocol; if secure is True as well, the protocol will be https. The request will be used to construct a domain in cases where a call to :meth:`Site.objects.get_current` fails.
+
+      Node urls will not contain a trailing slash unless a subpath is provided which ends with a trailing slash. Subpaths are expected to begin with a slash, as if returned by :func:`django.core.urlresolvers.reverse`.
+
+      :meth:`construct_url` may raise the following exceptions:
+
+      - :class:`NoReverseMatch` if "philo-root" is not reversable -- for example, if :mod:`philo.urls` is not included anywhere in your urlpatterns.
+      - :class:`Site.DoesNotExist <ObjectDoesNotExist>` if with_domain is True but no :class:`Site` or :class:`RequestSite` can be built.
+      - :class:`AncestorDoesNotExist` if the root node of the site isn't an ancestor of the node constructing the URL.
+
+Views
+-----
+
+Abstract View Models
+++++++++++++++++++++
+.. class:: View
+
+   :class:`!View` is an abstract model that represents an item which can be "rendered", either in response to an :class:`HttpRequest` or as a standalone. It subclasses :class:`Entity`, and defines the following additional methods and attributes:
+
+   .. attribute:: accepts_subpath
+
+      Defines whether this :class:`View` can handle subpaths. Default: ``False``
+
+   .. method:: handles_subpath(subpath)
+
+      Returns True if the the :class:`View` handles the given subpath, and False otherwise.
+
+   .. attribute:: nodes
+
+      A generic relation back to nodes.
+
+   .. method:: reverse([view_name=None, args=None, kwargs=None, node=None, obj=None])
+
+      If :attr:`accepts_subpath` is True, try to reverse a URL using the given parameters using ``self`` as the urlconf.
+
+      If ``obj`` is provided, :meth:`get_reverse_params` will be called and the results will be combined with any ``view_name``, ``args``, and ``kwargs`` that may have been passed in.
+
+      This method will raise the following exceptions:
+
+      - :class:`ViewDoesNotProvideSubpaths` if :attr:`accepts_subpath` is False.
+      - :class:`ViewCanNotProvideSubpath` if a reversal is not possible.
+
+   .. method:: get_reverse_params(obj)
+
+      This method is not implemented on the base class. It should return a ``view_name``, ``args``, ``kwargs`` tuple suitable for reversing a url for the given ``obj`` using ``self`` as the urlconf. If a reversal will not be possible, this method should raise :class:`ViewCanNotProvideSubpath`.
+
+   .. method:: attributes_with_node(node)
+
+      Returns a :class:`QuerySetMapper` using the :class:`node <Node>`'s attributes as a passthrough.
+
+   .. method:: render_to_response(request[, extra_context=None])
+
+      Renders the :class:`View` as an :class:`HttpResponse`. This will raise :const:`philo.exceptions.MIDDLEWARE_NOT_CONFIGURED` if the `request` doesn't have an attached :class:`Node`. This can happen if :class:`philo.middleware.RequestNodeMiddleware` is not in :setting:`settings.MIDDLEWARE_CLASSES` or if it is not functioning correctly.
+
+      :meth:`!render_to_response` will send the :obj:`view_about_to_render <philo.signals.view_about_to_render>` signal, then call :meth:`actually_render_to_response`, and finally send the :obj:`view_finished_rendering <philo.signals.view_finished_rendering>` signal before returning the ``response``.
+
+   .. method:: actually_render_to_response(request[, extra_context=None])
+
+      Concrete subclasses must override this method to provide the business logic for turning a ``request`` and ``extra_context`` into an :class:`HttpResponse`.
+
+.. class:: MultiView
+
+   :class:`!MultiView` is an abstract model which represents a section of related pages - for example, a :class:`~philo.contrib.penfield.BlogView` might have a foreign key to :class:`Page`\ s for an index, an entry detail, an entry archive by day, and so on. :class:`!MultiView` subclasses :class:`View`, and defines the following additional methods and attributes:
+
+   .. attribute:: accepts_subpath
+
+      Same as :attr:`View.accepts_subpath`. Default: ``True``
+
+   .. attribute:: urlpatterns
+
+      Returns urlpatterns that point to views (generally methods on the class). :class:`!MultiView`\ s can be thought of as "managing" these subpaths.
+
+   .. method:: actually_render_to_response(request[, extra_context=None])
+
+      Resolves the remaining subpath left after finding this :class:`View`'s node using :attr:`self.urlpatterns <urlpatterns>` and renders the view function (or method) found with the appropriate args and kwargs.
+
+   .. method:: get_context()
+
+      Hook for providing instance-specific context - such as the value of a Field - to all views.
+
+   .. method:: basic_view(field_name)
+
+      Given the name of a field on ``self``, accesses the value of that field and treats it as a :class:`View` instance. Creates a basic context based on :meth:`get_context` and any extra_context that was passed in, then calls the :class:`View` instance's :meth:`~View.render_to_response` method. This method is meant to be called to return a view function appropriate for :attr:`urlpatterns`.
+
+Concrete View Subclasses
+++++++++++++++++++++++++
+
+.. class:: Redirect
+
+   A :class:`View` subclass. Defines a 301 or 302 redirect to a different url on an absolute or relative path.
+
+   .. attribute:: STATUS_CODES
+
+      A choices tuple of redirect status codes (temporary or permanent).
+
+   .. attribute:: status_code
+
+      An :class:`IntegerField` which uses :attr:`STATUS_CODES` as its choices. Determines whether the redirect is considered temporary or permanent.
+
+   .. attribute:: target_node
+
+      An optional :class:`ForeignKey` to a :class:`Node`. If provided, that node will be used as the basis for the redirect.
+
+   .. attribute:: url_or_subpath
+
+      A :class:`CharField` which may contain an absolute or relative URL. This will be validated with :class:`philo.validators.RedirectValidator`.
+
+   .. attribute:: reversing_parameters
+
+      A :class:`~philo.models.fields.JSONField` instance. If the value of :attr:`reversing_parameters` is not None, the :attr:`url_or_subpath` will be treated as the name of a view to be reversed. The value of :attr:`reversing_parameters` will be passed into the reversal as args if it is a list or as kwargs if it is a dictionary.
+
+   .. attribute:: target_url
+
+      Calculates and returns the target url based on the :attr:`target_node`, :attr:`url_or_subpath`, and :attr:`reversing_parameters`.
+
+   .. method:: actually_render_to_response(request[, extra_context=None])
+
+      Returns an :class:`HttpResponseRedirect` to :attr:`self.target`.
+
+.. class:: File
+
+   A :class:`View` subclass. Stores an arbitrary file.
+
+   .. attribute:: mimetype
+
+      Defines the mimetype of the uploaded file. This will not be validated.
+
+   .. attribute:: file
+
+      Contains the uploaded file. Files are uploaded to ``philo/files/%Y/%m/%d``.
+
+   .. method:: __unicode__()
+
+      Returns the name of :attr:`self.file <file>`.
+
+Pages
+*****
+
+:class:`Page`\ s are the most frequently used :class:`View` subclass. They define a basic HTML page and its associated content. Each :class:`Page` renders itself according to a :class:`Template`. The :class:`Template` may contain :ttag:`container` tags, which define related :class:`Contentlet`\ s and :class:`ContentReference`\ s for any page using that :class:`Template`.
+
+.. class:: Page
+
+   A :class:`View` subclass. Represents a page - something which is rendered according to a template. The page will have a number of related Contentlets depending on the template selected - but these will appear only after the page has been saved with that template.
+
+   .. attribute:: template
+
+      A :class:`ForeignKey` to the :class:`Template` used to render this :class:`Page`.
+
+   .. attribute:: title
+
+      The name of this page. Chances are this will be used for organization - i.e. finding the page in a list of pages - rather than for display.
+
+   .. attribute:: containers
+
+      Returns :attr:`self.template.containers <Template.containers>` - a tuple containing the specs of all :ttag:`container`\ s defined in the :class:`Template`. The value will be cached on the instance so that multiple accesses will be less expensive.
+
+   .. method:: render_to_string([request=None, extra_context=None])
+
+      In addition to rendering as an :class:`HttpResponse`, a :class:`Page` can also render as a string. This means, for example, that :class:`Page`\ s can be used to render emails or other non-HTML-related content with the same :ttag:`container`-based functionality as is used for HTML.
+
+   .. method:: actually_render_to_response(request[, extra_context=None])
+
+      Returns an :class:`HttpResponse` with the content of the :meth:`render_to_string` method and the mimetype set to :attr:`self.template.mimetype <Template.mimetype>`.
+
+   .. clean_fields(self[, exclude=None)
+
+      This is an override of the default model clean_fields method. Essentially, in addition to validating the fields, this method validates the :class:`Template` instance that is used to render this :class:`Page`. This is useful for catching template errors before they show up as 500 errors on a live site.
+
+   .. method:: __unicode__()
+
+      Returns :meth:`self.title <title>`
+
+.. class:: Template
+
+   Subclasses :class:`TreeModel`. Represents a database-driven django template. Defines the following additional methods and attributes:
+
+   .. attribute:: name
+
+      The name of the template. Used for organization and debugging.
+
+   .. attribute:: documentation
+
+      Can be used to let users know what the template is meant to be used for.
+
+   .. attribute:: mimetype
+
+      Defines the mimetype of the template. This is not validated. Default: ``text/html``.
+
+   .. attribute:: code
+
+      An insecure :class:`~philo.models.fields.TemplateField` containing the django template code for this template.
+
+   .. attribute:: containers
+
+      Returns a tuple where the first item is a list of names of contentlets referenced by containers, and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers. This will break if there is a recursive extends or includes in the template code. Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
+
+   .. method:: __unicode__()
+
+      Returns the results of the :meth:`~TreeModel.get_path` method, using the "name" field and a chevron joiner.
+
+.. class:: Contentlet
+
+   Defines a piece of content on a page. This content is treated as a secure :class:`~philo.models.fields.TemplateField`.
+
+   .. attribute:: page
+
+      The page which this :class:`Contentlet` is related to.
+
+   .. attribute:: name
+
+      This represents the name of the container as defined by a :ttag:`container` tag.
+
+   .. attribute:: content
+
+      A secure :class:`~philo.models.fields.TemplateField` holding the content for this :class:`Contentlet`. Note that actually using this field as a template requires use of the :ttag:`include_string` template tag.
+
+   .. method:: __unicode__()
+
+      Returns :attr:`self.name <name>`
+
+.. class:: ContentReference
+
+   Defines a model instance related to a page.
+
+   .. attribute:: page
+
+      The page which this :class:`ContentReference` is related to.
+
+   .. attribute:: name
+
+      This represents the name of the container as defined by a :ttag:`container` tag.
+
+   .. attribute:: content
+
+      A :class:`GenericForeignKey` to a model instance. The content type of this instance is defined by the :ttag:`container` tag which defines this :class:`ContentReference`.
+
+   .. method:: __unicode__()
+
+      Returns :attr:`self.name <name>`
\ No newline at end of file
similarity index 100%
rename from LICENSE
rename to philo/LICENSE
diff --git a/philo/__init__.py b/philo/__init__.py
new file mode 100644 (file)
index 0000000..32297e0
--- /dev/null
@@ -0,0 +1 @@
+VERSION = (0, 0)
similarity index 100%
rename from admin/__init__.py
rename to philo/admin/__init__.py
similarity index 100%
rename from admin/base.py
rename to philo/admin/base.py
similarity index 79%
rename from admin/nodes.py
rename to philo/admin/nodes.py
index 66be107..e2a9c9d 100644 (file)
@@ -1,12 +1,14 @@
 from django.contrib import admin
 from philo.admin.base import EntityAdmin, TreeEntityAdmin, COLLAPSE_CLASSES
 from philo.models import Node, Redirect, File
+from mptt.admin import MPTTModelAdmin
 
 
 class NodeAdmin(TreeEntityAdmin):
        list_display = ('slug', 'view', 'accepts_subpath')
+       raw_id_fields = ('parent',)
        related_lookup_fields = {
-               'fk': [],
+               'fk': raw_id_fields,
                'm2m': [],
                'generic': [['view_content_type', 'view_object_id']]
        }
@@ -14,6 +16,9 @@ class NodeAdmin(TreeEntityAdmin):
        def accepts_subpath(self, obj):
                return obj.accepts_subpath
        accepts_subpath.boolean = True
+       
+       def formfield_for_foreignkey(self, db_field, request, **kwargs):
+               return super(MPTTModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
 
 class ViewAdmin(EntityAdmin):
similarity index 84%
rename from admin/pages.py
rename to philo/admin/pages.py
index 13d4098..f9e96c0 100644 (file)
@@ -46,6 +46,12 @@ class PageAdmin(ViewAdmin):
        list_filter = ('template',)
        search_fields = ['title', 'contentlets__content']
        inlines = [ContentletInline, ContentReferenceInline] + ViewAdmin.inlines
+       
+       def response_add(self, request, obj, post_url_continue='../%s/'):
+               # Shamelessly cribbed from django/contrib/auth/admin.py:143
+               if '_addanother' not in request.POST and '_popup' not in request.POST:
+                       request.POST['_continue'] = 1
+               return super(PageAdmin, self).response_add(request, obj, post_url_continue)
 
 
 class TemplateAdmin(TreeAdmin):
similarity index 86%
rename from admin/widgets.py
rename to philo/admin/widgets.py
index aa0aa30..fb13ac7 100644 (file)
@@ -48,15 +48,12 @@ class TagFilteredSelectMultiple(FilteredSelectMultiple):
        catalog has been loaded in the page
        """
        class Media:
-               js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
-                         settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
-                         settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js")
-               
-               if 'staticmedia' in settings.INSTALLED_APPS:
-                       import staticmedia
-                       js += (staticmedia.url('admin/js/TagCreation.js'),)
-               else:
-                       js += (settings.ADMIN_MEDIA_PREFIX + "js/TagCreation.js",)
+               js = (
+                       settings.ADMIN_MEDIA_PREFIX + "js/core.js",
+                       settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
+                       settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js",
+                       settings.ADMIN_MEDIA_PREFIX + "js/TagCreation.js",
+               )
 
        def render(self, name, value, attrs=None, choices=()):
                if attrs is None: attrs = {}
diff --git a/philo/contrib/gilbert/README b/philo/contrib/gilbert/README
new file mode 100644 (file)
index 0000000..6763313
--- /dev/null
@@ -0,0 +1 @@
+Gilbert requires Django 1.3+.
\ No newline at end of file
diff --git a/philo/contrib/gilbert/__init__.py b/philo/contrib/gilbert/__init__.py
new file mode 100644 (file)
index 0000000..de7dbc1
--- /dev/null
@@ -0,0 +1,24 @@
+__version__ = 'alpha'
+
+
+from philo.contrib.gilbert.sites import GilbertSite, site
+
+
+def autodiscover():
+       import copy
+       from django.conf import settings
+       from django.utils.importlib import import_module
+       from django.utils.module_loading import module_has_submodule
+       
+       for app in settings.INSTALLED_APPS:
+               mod = import_module(app)
+               try:
+                       before_import_model_routers = copy.copy(site.model_routers)
+                       before_import_core_router = copy.copy(site.core_router)
+                       import_module('%s.gilbert' % app)
+               except:
+                       site.model_routers = before_import_model_routers
+                       site.core_router = before_import_core_router
+                       
+                       if module_has_submodule(mod, 'gilbert'):
+                               raise
\ No newline at end of file
diff --git a/philo/contrib/gilbert/admin.py b/philo/contrib/gilbert/admin.py
new file mode 100644 (file)
index 0000000..af46cb8
--- /dev/null
@@ -0,0 +1,4 @@
+from django.contrib.admin import site
+from .models import UserPreferences
+
+site.register(UserPreferences)
\ No newline at end of file
diff --git a/philo/contrib/gilbert/exceptions.py b/philo/contrib/gilbert/exceptions.py
new file mode 100644 (file)
index 0000000..6c4fe17
--- /dev/null
@@ -0,0 +1,26 @@
+class AlreadyRegistered(Exception):
+       pass
+
+
+class NotRegistered(Exception):
+       pass
+
+
+class ExtException(Exception):
+       """ Base class for all Ext.Direct-related exceptions. """
+       pass
+
+
+class InvalidExtMethod(ExtException):
+       """ Indicate that a function cannot be an Ext.Direct method. """
+       pass
+
+
+class NotExtAction(ExtException):
+       """ Indicate that an object is not an Ext.Direct action. """
+       pass
+
+
+class NotExtMethod(ExtException):
+       """ Indicate that a function is not an Ext.Direct method. """
+       pass
\ No newline at end of file
diff --git a/philo/contrib/gilbert/extdirect/__init__.py b/philo/contrib/gilbert/extdirect/__init__.py
new file mode 100644 (file)
index 0000000..ff48f94
--- /dev/null
@@ -0,0 +1,2 @@
+from .core import *
+from .forms import *
\ No newline at end of file
diff --git a/philo/contrib/gilbert/extdirect/core.py b/philo/contrib/gilbert/extdirect/core.py
new file mode 100644 (file)
index 0000000..f0a4e2a
--- /dev/null
@@ -0,0 +1,637 @@
+from django.db.models import Q
+from django.http import HttpResponse
+from django.utils import simplejson as json
+from django.utils.encoding import smart_str
+from django.views.debug import ExceptionReporter
+from inspect import isclass, ismethod, isfunction, getmembers, getargspec
+from traceback import format_tb
+from abc import ABCMeta, abstractproperty
+from collections import Callable, Sized, Mapping
+import sys, datetime
+
+
+# __all__ = ('ext_action', 'ext_method', 'is_ext_action', 'is_ext_method', 'ExtAction', 'ExtMethod')
+
+
+class ExtRequest(object):
+       """
+       Represents a single Ext.Direct request along with the :class:`django.http.HttpRequest` it originates from.
+       
+       .. note::
+               
+               Passes undefined attribute accesses through to the underlying :class:`django.http.HttpRequest`.
+       
+       """
+       
+       @classmethod
+       def parse(cls, request, object_hook=None):
+               """
+               Parses Ext.Direct request(s) from the originating HTTP request.
+               
+               :arg request: the originating HTTP request
+               :type request: :class:`django.http.HttpRequest`
+               :returns: list of :class:`ExtRequest` instances
+               
+               """
+               
+               requests = []
+               
+               if request.META['CONTENT_TYPE'].startswith('application/x-www-form-urlencoded') or request.META['CONTENT_TYPE'].startswith('multipart/form-data'):
+                       requests.append(cls(request,
+                               type = request.POST.get('extType'),
+                               tid = request.POST.get('extTID'),
+                               action = request.POST.get('extAction'),
+                               method = request.POST.get('extMethod'),
+                               data = request.POST.get('extData', None),
+                               upload = True if request.POST.get('extUpload', False) in (True, 'true', 'True') else False,
+                               form_request = True,
+                       ))
+               else:
+                       decoded_requests = json.loads(request.raw_post_data, object_hook=object_hook)
+                       if type(decoded_requests) is dict:
+                               decoded_requests = [decoded_requests]
+                       for inner_request in decoded_requests:
+                               requests.append(cls(request,
+                                       type = inner_request.get('type'),
+                                       tid = inner_request.get('tid'),
+                                       action = inner_request.get('action'),
+                                       method = inner_request.get('method'),
+                                       data = inner_request.get('data', None),
+                               ))
+               
+               return requests
+       
+       def __init__(self, request, type, tid, action, method, data, upload=False, form_request=False):
+               """
+               :arg request: the originating HTTP request
+               :type request: :class:`django.http.HttpRequest`
+               :arg type: Ext.Direct request type
+               :type type: str
+               :arg tid: Ext.Direct transaction identifier
+               :type tid: str
+               :arg action: Ext.Direct action name
+               :type action: str
+               :arg method: Ext.Direct method name
+               :type method: str
+               :arg data: Ext.Direct method arguments
+               :type data: list
+               :arg upload: request includes uploaded file(s)
+               :type upload: bool
+               :arg form_request: request made by form submission
+               :type form_request: bool
+               
+               """
+               
+               self.type = type
+               self.tid = tid
+               self.request = request
+               self.action = action
+               self.method = method
+               self.data = data if data is not None else []
+               self.upload = upload
+               self.form_request = form_request
+       
+       def __getattr__(self, key):
+               try:
+                       return getattr(self.request, key)
+               except:
+                       raise AttributeError
+
+
+class ExtMethod(Callable, Sized):
+       """
+       Wraps a (previously :meth:`decorated <ExtMethod.decorate>`) function as an Ext.Direct method.
+       
+       """
+       
+       @classmethod
+       def decorate(cls, function=None, name=None, form_handler=False):
+               """
+               Applies metadata to function identifying it as wrappable, or returns a decorator for doing the same::
+               
+                       @ExtMethod.decorate
+                       def method(self, request):
+                               pass
+                       
+                       @ExtMethod.decorate(name='custom_name', form_handler=True)
+                       def form_handler_with_custom_name(self, request):
+                               pass
+               
+               Intended for use on methods of classes already decorated by :meth:`ExtAction.decorate`.
+               
+               :arg name: custom Ext.Direct method name
+               :type name: str
+               :arg form_handler: function handles form submissions
+               :type form_handler: bool
+               :returns: function with metadata applied
+               
+               """
+               
+               def setter(function):
+                       setattr(function, '_ext_method', True)
+                       setattr(function, '_ext_method_form_handler', form_handler)
+                       if name is not None:
+                               setattr(function, '_ext_method_name', name)
+                       return function
+               if function is not None:
+                       return setter(function)
+               return setter
+       
+       @classmethod
+       def validate(cls, function):
+               """
+               Validates that function has been :meth:`decorated <ExtMethod.decorate>` and is therefore wrappable.
+               
+               """
+               
+               return getattr(function, '_ext_method', False)
+       
+       def __init__(self, function):
+               """
+               :arg function: function to wrap
+               :type function: callable
+               
+               If the function accepts variable positional arguments, the Ext.Direct method argument count will be increased by one for acceptance of a list.
+               
+               Similarly, if the function accepts variable keyword arguments, the argument count will be increased by one for acceptance of a dictionary.
+               
+               .. warning::
+                       
+                       Wrapped functions **must** accept at least one positional argument (in addition to self if function is a method): the :class:`ExtRequest` that caused the invocation.
+               
+               .. warning::
+                       
+                       Wrapped functions identified as handling form submissions **must** return a tuple containing:
+                       
+                       * a boolean indicating success or failure
+                       * a dictionary of fields mapped to errors, if any, or None
+               
+               """
+               self.function = function
+               self.form_handler = getattr(function, '_ext_method_form_handler', False)
+               self.name = getattr(function, '_ext_method_name', function.__name__)
+               
+               argspec = getargspec(self.function)
+               len_ = len(argspec.args)
+               
+               if len_ >= 2 and ismethod(self.function):
+                       len_ -= 2
+               elif len_ >= 1 and not ismethod(self.function):
+                       len_ -= 1
+               else:
+                       raise TypeError('%s cannot be wrapped as an Ext.Direct method as it does not take an ExtRequest as its first positional argument')
+               
+               if argspec.varargs is not None:
+                       len_ += 1
+                       self.accepts_varargs = True
+               else:
+                       self.accepts_varargs = False
+               
+               if argspec.keywords is not None:
+                       len_ += 1
+                       self.accepts_keywords = True
+               else:
+                       self.accepts_keywords = False
+               
+               self.len = len_
+       
+       @property
+       def spec(self):
+               return {
+                       'name': self.name,
+                       'len': self.len,
+                       'formHandler': self.form_handler
+               }
+       
+       def __len__(self):
+               return self.len
+       
+       def __call__(self, request):
+               """
+               Invoke the wrapped function using the provided :class:`ExtRequest` and return the raw result.
+               
+               :arg request: the :class:`ExtRequest`
+               
+               :raises TypeError: the request did not provide the required number of arguments
+               :raises Exception: the (form handling) function did not return a valid result for a form submission request
+               
+               """
+               
+               args = request.data
+               args_len = len(args)
+               
+               if args_len != self.len:
+                       raise TypeError('%s takes exactly %i arguments (%i given)' % (self.name, self.len, args_len))
+               
+               keywords = {}
+               if self.accepts_keywords:
+                       keywords = dict([(smart_str(k, 'ascii'), v) for k,v in args.pop().items()])
+               
+               varargs = []
+               if self.accepts_varargs:
+                       varargs = args.pop()
+               
+               result = self.function(request, *(args + varargs), **keywords)
+               
+               if self.form_handler:
+                       try:
+                               new_result = {
+                                       'success': result[0],
+                                       'errors': result[1],
+                               }
+                               if len(result) > 2:
+                                       new_result['pk'] = result[2]
+                               
+                               if new_result['success']:
+                                       del new_result['errors']
+                               
+                               result = new_result
+                       except:
+                               raise Exception # pick a better one
+               
+               return result
+
+
+ext_method = ExtMethod.decorate
+"""
+Convenience alias for :meth:`ExtMethod.decorate`.
+
+"""
+
+
+is_ext_method = ExtMethod.validate
+"""
+Convenience alias for :meth:`ExtMethod.validate`.
+
+"""
+
+
+class ExtAction(Callable, Mapping):
+       """
+       Wraps a (previously :meth:`decorated <ExtAction.decorate>`) object as an Ext.Direct action.
+       
+       """
+       
+       method_class = ExtMethod
+       """
+       The :class:`ExtMethod` subclass used when wrapping the wrapped object's members.
+       
+       """
+       
+       @classmethod
+       def decorate(cls, obj=None, name=None):
+               """
+               Applies metadata to obj identifying it as wrappable, or returns a decorator for doing the same::
+               
+                       @ExtAction.decorate
+                       class MyAction(object):
+                               pass
+                       
+                       @ExtAction.decorate(name='GoodAction')
+                       class BadAction(object):
+                               pass
+               
+               Intended for use on classes with member functions (methods) already decorated with :meth:`ExtMethod.decorate`.
+               
+               :arg name: custom Ext.Direct action name
+               :type name: str
+               :returns: obj with metadata applied
+               
+               """
+               
+               def setter(obj):
+                       setattr(obj, '_ext_action', True)
+                       if name is not None:
+                               setattr(obj, '_ext_action_name', name)
+                       return obj
+               if obj is not None:
+                       return setter(obj)
+               return setter
+       
+       @classmethod
+       def validate(cls, obj):
+               """
+               Validates that obj has been :meth:`decorated <ExtAction.decorate>` and is therefore wrappable.
+               
+               """
+               
+               return getattr(obj, '_ext_action', False)
+       
+       def __init__(self, obj):
+               self.obj = obj
+               self.name = getattr(obj, '_ext_action_name', obj.__name__ if isclass(obj) else obj.__class__.__name__)
+               self._methods = None
+       
+       @property
+       def methods(self):
+               if not self._methods:
+                       self._methods = dict((method.name, method) for method in (self.method_class(member) for name, member in getmembers(self.obj, self.method_class.validate)))
+               return self._methods
+       
+       def __len__(self):
+               return len(self.methods)
+       
+       def __iter__(self):
+               return iter(self.methods)
+       
+       def __getitem__(self, name):
+               return self.methods[name]
+               
+       @property
+       def spec(self):
+               """
+               Returns a tuple containing:
+                       
+                       * the action name
+                       * a list of :class:`method specs <ExtMethod.spec>`
+               
+               Used internally by :class:`providers <ExtProvider>` to construct an Ext.Direct provider spec.
+               
+               """
+               
+               return self.name, list(method.spec for method in self.itervalues())
+       
+       def __call__(self, request):
+               return self[request.method](request)
+
+
+ext_action = ExtAction.decorate
+"""
+Convenience alias for :meth:`ExtAction.decorate`.
+
+"""
+
+
+is_ext_action = ExtAction.validate
+"""
+Convenience alias for :meth:`ExtAction.validate`.
+
+"""
+
+
+class ExtResponse(object):
+       """
+       Abstract base class for responses to :class:`requests <ExtRequest>`.
+       
+       """
+       __metaclass__ = ABCMeta
+       
+       @abstractproperty
+       def as_ext(self):
+               raise NotImplementedError
+
+
+class ExtResult(ExtResponse):
+       """
+       Represents a successful response to a :class:`request <ExtRequest>`.
+       
+       """
+       
+       def __init__(self, request, result):
+               """
+               :arg request: the originating Ext.Direct request
+               :type request: :class:`ExtRequest`
+               :arg result: the raw result
+               
+               """
+               self.request = request
+               self.result = result
+       
+       @property
+       def as_ext(self):
+               return {
+                       'type': self.request.type,
+                       'tid': self.request.tid,
+                       'action': self.request.action,
+                       'method': self.request.method,
+                       'result': self.result
+               }
+
+
+class ExtException(ExtResponse):
+       """
+       Represents an exception raised by an unsuccessful response to a :class:`request <ExtRequest>`.
+       
+       .. warning::
+               
+               If :data:`django.conf.settings.DEBUG` is True, information about which exception was raised and where it was raised (including a traceback in both plain text and HTML) will be provided to the client.
+       
+       """
+       
+       def __init__(self, request, exc_info):
+               """
+               
+               """
+               self.request = request
+               self.exc_info = exc_info
+       
+       @property
+       def as_ext(self):
+               from django.conf import settings
+               if settings.DEBUG:
+                       reporter = ExceptionReporter(self.request.request, *self.exc_info)
+                       return {
+                               'type': 'exception',
+                               'tid': self.request.tid,
+                               'message': '%s: %s' % (self.exc_info[0], self.exc_info[1]),
+                               'where': format_tb(self.exc_info[2])[0],
+                               'identifier': '%s.%s' % (self.exc_info[0].__module__, self.exc_info[0].__name__),
+                               'html': reporter.get_traceback_html()
+                       }
+               else:
+                       return {
+                               'type': 'exception',
+                               'tid': self.request.tid
+                       }
+
+
+class ExtProvider(Callable, Mapping):
+       """
+       Abstract base class for Ext.Direct provider implementations.
+       
+       """
+       
+       __metaclass__ = ABCMeta
+       
+       result_class = ExtResult
+       """
+       The :class:`ExtResponse` subclass used to represent the results of a successful Ext.Direct method invocation.
+       
+       """
+       
+       exception_class = ExtException
+       """
+       The :class:`ExtResponse` subclass used to represent the exception raised during an unsuccessful Ext.Direct method invocation.
+       
+       """
+       
+       @abstractproperty
+       def namespace(self):
+               """
+               The Ext.Direct provider namespace.
+               
+               """
+               raise NotImplementedError
+       
+       @abstractproperty
+       def url(self):
+               """
+               The Ext.Direct provider url.
+               
+               """
+               raise NotImplementedError
+       
+       @abstractproperty
+       def type(self):
+               """
+               The Ext.Direct provider type.
+               
+               """
+               raise NotImplementedError
+       
+       @abstractproperty
+       def actions(self):
+               """
+               The dictionary of action names and :class:`ExtAction` instances handled by the provider.
+               
+               """
+               raise NotImplementedError
+       
+       def __len__(self):
+               return len(self.actions)
+       
+       def __iter__(self):
+               return iter(self.actions)
+       
+       def __getitem__(self, name):
+               return self.actions[name]
+       
+       @property
+       def spec(self):
+               return {
+                       'namespace': self.namespace,
+                       'url': self.url,
+                       'type': self.type,
+                       'actions': dict(action.spec for action in self.itervalues())
+               }
+       
+       def __call__(self, request):
+               """
+               Returns a :class:`response <ExtResponse>` to the :class:`request <ExtRequest>`.
+               
+               """
+               try:
+                       return self.result_class(request=request, result=self[request.action](request))
+               except Exception:
+                       return self.exception_class(request=request, exc_info=sys.exc_info())
+
+
+class ExtRouter(ExtProvider):
+       """
+       A :class:`provider <ExtProvider>` base class with an implementation capable of handling the complete round-trip from a :class:`django.http.HttpRequest` to a :class:`django.http.HttpResponse`.
+       
+       """
+       
+       __metaclass__ = ABCMeta
+       
+       request_class = ExtRequest
+       """
+       The :class:`ExtRequest` subclass used parse and to represent the individual Ext.Direct requests within a :class:`django.http.HttpRequest`.
+       
+       """
+       
+       @classmethod
+       def json_object_hook(cls, obj):
+               if obj.get('q_object', False):
+                       return Q._new_instance(obj['children'], obj['connector'], obj['negated'])
+               return obj
+       
+       @classmethod
+       def json_default(cls, obj):
+               from django.forms.models import ModelChoiceIterator
+               from django.db.models.query import ValuesListQuerySet
+               from django.utils.functional import Promise
+               
+               if isinstance(obj, ExtResponse):
+                       return obj.as_ext
+               elif isinstance(obj, datetime.datetime):
+                       obj = obj.replace(microsecond=0)
+                       return obj.isoformat(' ')
+               elif isinstance(obj, ModelChoiceIterator) or isinstance(obj, ValuesListQuerySet):
+                       return list(obj)
+               elif isinstance(obj, Promise):
+                       return unicode(obj)
+               elif isinstance(obj, Q):
+                       return {
+                               'q_object': True,
+                               'connector': obj.connector,
+                               'negated': obj.negated,
+                               'children': obj.children
+                       }
+               else:
+                       raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))
+       
+       def render_to_response(self, request):
+               """
+               Returns a :class:`django.http.HttpResponse` containing the :class:`response(s) <ExtResponse>` to the :class:`request(s) <ExtRequest>` in the provided :class:`django.http.HttpRequest`.
+               
+               """
+               
+               from django.conf import settings
+               
+               requests = self.request_class.parse(request, object_hook=self.json_object_hook)
+               responses = []
+               html_response = False
+               
+               for request in requests:
+                       if request.form_request and request.upload:
+                               html_response = True
+                       responses.append(self(request))
+               
+               response = responses[0] if len(responses) == 1 else responses
+               json_response = json.dumps(responses, default=self.json_default)
+               
+               if html_response:
+                       return HttpResponse('<html><body><textarea>%s</textarea></body></html>' % json_response)
+               return HttpResponse(json_response, content_type='application/json; charset=%s' % settings.DEFAULT_CHARSET)
+
+
+class SimpleExtRouter(ExtRouter):
+       """
+       A simple concrete :class:`router <ExtRouter>` implementation.
+       
+       """
+       
+       def __init__(self, namespace, url, actions=None, type='remoting'):
+               """
+               :arg namespace: the Ext.Direct provider namespace
+               :type namespace: str
+               :arg url: the Ext.Direct provider url
+               :type url: str
+               :arg actions: the dictionary of action names and :class:`ExtAction` instances handled by the provider
+               :type actions: dict
+               :arg type: the Ext.Direct provider type
+               :type type: str
+               
+               """
+               
+               self._type = type
+               self._namespace = namespace
+               self._url = url
+               self._actions = actions if actions is not None else {}
+       
+       @property
+       def namespace(self):
+               return self._namespace
+       
+       @property
+       def url(self):
+               return self._url
+       
+       @property
+       def type(self):
+               return self._type
+       
+       @property
+       def actions(self):
+               return self._actions
\ No newline at end of file
diff --git a/philo/contrib/gilbert/extdirect/forms.py b/philo/contrib/gilbert/extdirect/forms.py
new file mode 100644 (file)
index 0000000..ca757c3
--- /dev/null
@@ -0,0 +1,305 @@
+import os.path
+from django.forms.widgets import Widget, TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, Textarea, DateInput, DateTimeInput, TimeInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, RadioSelect, CheckboxSelectMultiple, MultiWidget, SplitHiddenDateTimeWidget
+from django.forms.forms import BaseForm, BoundField
+from django.forms.fields import FileField
+from django.forms.models import ModelForm, ModelChoiceField, ModelMultipleChoiceField
+from django.db.models import ForeignKey, ManyToManyField, Q
+from django.utils.encoding import force_unicode
+from philo.utils import ContentTypeLimiter
+from philo.hacks import Category
+
+
+# The "categories" in this module are listed in reverse order, because I wasn't able to ensure that they'd all take effect otherwise...
+
+
+#still to do: SplitHiddenDateTimeWidget
+
+
+class MultiWidget(MultiWidget):
+       __metaclass__ = Category
+       
+       def render_extdirect(self, name, data):
+               if not isinstance(data, list):
+                       data = self.decompress(data)
+               
+               specs = []
+               for i, widget in enumerate(self.widgets):
+                       try:
+                               widget_data = data[i]
+                       except IndexError:
+                               widget_data = None
+                       specs.extend(widget.render_extdirect(name + '_%s' % i, widget_data))
+               return specs
+
+
+#still to do: RadioSelect, CheckboxSelectMultiple
+
+
+class SelectMultiple(SelectMultiple):
+       __metaclass__ = Category
+       extdirect_xtype = 'superboxselect'
+       
+       def render_extdirect(self, name, data):
+               if self.choices:
+                       store = [choice for choice in self.choices if choice[0]]
+               else:
+                       store = []
+               spec = {
+                       'name': name,
+                       'value': data,
+                       'store': store,
+                       'xtype': self.extdirect_xtype,
+                       'forceFormValue': False
+               }
+               if not spec['value']:
+                       del spec['value']
+               return [spec]
+
+
+class NullBooleanSelect(NullBooleanSelect):
+       __metaclass__ = Category
+       
+       def render_extdirect(self, name, data):
+               try:
+                       data = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[data]
+               except KeyError:
+                       data = u'1'
+               return super(NullBooleanSelect, self).render_extdirect(name, data)
+
+
+class Select(Select):
+       __metaclass__ = Category
+       extdirect_xtype = 'combo'
+       
+       def render_extdirect(self, name, data):
+               if self.choices:
+                       store = [choice for choice in self.choices if choice[0]]
+               else:
+                       store = []
+               spec = {
+                       'hiddenName': name,
+                       'hiddenValue': data,
+                       'value': data,
+                       'xtype': self.extdirect_xtype,
+                       'store': store,
+                       'editable': False,
+                       'disableKeyFilter': True,
+                       'forceSelection': True,
+                       'triggerAction': 'all',
+               }
+               if not spec['value']:
+                       del spec['value']
+               return [spec]
+
+
+class CheckboxInput(CheckboxInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'checkbox'
+       
+       def render_extdirect(self, name, data):
+               data = bool(data)
+               specs = super(CheckboxInput, self).render_extdirect(name, data)
+               specs[0]['checked'] = data
+               return specs
+
+
+class DateTimeInput(DateTimeInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'gilbertdatetimefield'
+
+
+class TimeInput(TimeInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'timefield'
+
+
+class DateInput(DateInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'datefield'
+
+
+class Textarea(Textarea):
+       __metaclass__ = Category
+       extdirect_xtype = 'textarea'
+
+
+class FileInput(FileInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'fileuploadfield'
+       
+       def render_extdirect(self, name, data):
+               if data is not None:
+                       data = os.path.split(data.name)[1]
+               return super(FileInput, self).render_extdirect(name, data)
+
+
+class MultipleHiddenInput(MultipleHiddenInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'hidden'
+       
+       def render_extdirect(self, name, data):
+               if data is None:
+                       data = []
+               return [specs.extend(super(MultipleHiddenInput, self).render_extdirect(name, data)) for datum in data]
+
+
+class HiddenInput(HiddenInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'hidden'
+
+
+class PasswordInput(PasswordInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'textfield'
+       
+       def render_extdirect(self, name, data):
+               specs = super(PasswordInput, self).render_extdirect(name, data)
+               specs[0]['inputType'] = self.input_type
+               return specs
+
+
+class TextInput(TextInput):
+       __metaclass__ = Category
+       extdirect_xtype = 'textfield'
+
+
+class Widget(Widget):
+       __metaclass__ = Category
+       extdirect_xtype = None
+       
+       def render_extdirect(self, name, data):
+               if not self.extdirect_xtype:
+                       raise NotImplementedError
+               spec = {
+                       'name': name,
+                       'value': data,
+                       'xtype': self.extdirect_xtype
+               }
+               if not spec['value']:
+                       del spec['value']
+               return [spec]
+
+
+class BoundField(BoundField):
+       __metaclass__ = Category
+       
+       def as_hidden_extdirect(self, only_initial=False):
+               return self.as_widget_extdirect(self.field.hidden_widget(), only_initial)
+       
+       def as_widget_extdirect(self, widget=None, only_initial=False):
+               if not widget:
+                       widget = self.field.widget
+                       standard_widget = True
+               else:
+                       standard_widget = False
+               
+               if not self.form.is_bound:
+                       data = self.form.initial.get(self.name, self.field.initial)
+                       if callable(data):
+                               data = data()
+               else:
+                       if isinstance(self.field, FileField) and self.data is None:
+                               data = self.form.initial.get(self.name, self.field.initial)
+                       else:
+                               data = self.data
+               data = self.field.prepare_value(data)
+               
+               if not only_initial:
+                       name = self.html_name
+               else:
+                       name = self.html_initial_name
+               
+               specs = widget.render_extdirect(name, data)
+               
+               if standard_widget and isinstance(self.field, ModelChoiceField):
+                       limit_choices_to = None
+                       
+                       if isinstance(self.form, ModelForm):
+                               model = self.form._meta.model
+                               model_fields = model._meta.fields + model._meta.many_to_many
+                               
+                               for model_field in model_fields:
+                                       if model_field.name == self.name and (isinstance(model_field, ForeignKey) or isinstance(model_field, ManyToManyField)):
+                                               limit_choices_to = model_field.rel.limit_choices_to
+                                               if limit_choices_to is None:
+                                                       limit_choices_to = {}
+                                               elif isinstance(limit_choices_to, ContentTypeLimiter):
+                                                       limit_choices_to = limit_choices_to.q_object()
+                                               elif not isinstance(limit_choices_to, dict):
+                                                       limit_choices_to = None # can't support other objects with add_to_query methods
+                                               break
+                       
+                       if limit_choices_to is not None:
+                               specs[0]['model_filters'] = limit_choices_to
+                       else:
+                               specs[0]['model_filters'] = {
+                                       'pk__in': self.field.queryset.values_list('pk', flat=True)
+                               }
+                               
+                       specs[0]['model_app_label'] = self.field.queryset.model._meta.app_label
+                       specs[0]['model_name'] = self.field.queryset.model._meta.object_name
+                       
+                       if isinstance(self.field, ModelMultipleChoiceField):
+                               specs[0]['xtype'] = 'gilbertmodelmultiplechoicefield'
+                       else:
+                               specs[0]['xtype'] = 'gilbertmodelchoicefield'
+                               specs[0]['backup_store'] = specs[0]['store']
+                               del specs[0]['store']
+               
+               return specs
+                       
+       def as_extdirect(self):
+               if self.field.show_hidden_initial:
+                       return self.as_widget_extdirect() + self.as_hidden_extdirect(only_initial=True)
+               return self.as_widget_extdirect()
+
+
+class BaseForm(BaseForm):
+       __metaclass__ = Category
+       
+       def as_extdirect(self):
+               fields = []
+               
+               for bound_field in self:
+                       if bound_field.label:
+                               label = bound_field.label
+                       else:
+                               label = ''
+                       
+                       if bound_field.field.help_text:
+                               help_text = bound_field.field.help_text
+                       else:
+                               help_text = ''
+                       
+                       specs = bound_field.as_extdirect()
+                       
+                       if len(specs) < 1:
+                               continue
+                       
+                       if len(specs) > 1:
+                               specs = [{
+                                       'xtype': 'compositefield',
+                                       'items': specs
+                               }]
+                       
+                       if label:
+                               specs[0]['fieldLabel'] = label
+                       if help_text:
+                               specs[0]['help_text'] = help_text
+                       
+                       fields.extend(specs)
+               
+               if isinstance(self, ModelForm):
+                       pk = self.instance.pk
+                       if pk is not None:
+                               fields.append({
+                                       'name': 'pk',
+                                       'value': pk,
+                                       'xtype': 'hidden'
+                               })
+               
+               return {
+                       'items': fields,
+                       'labelSeparator': self.label_suffix,
+                       'fileUpload': self.is_multipart()
+               }
\ No newline at end of file
diff --git a/philo/contrib/gilbert/gilbert.py b/philo/contrib/gilbert/gilbert.py
new file mode 100644 (file)
index 0000000..b7f8253
--- /dev/null
@@ -0,0 +1,6 @@
+from . import site
+from django.contrib.auth.models import User, Group
+
+
+site.register_model(User, icon_name='user')
+site.register_model(Group, icon_name='users')
\ No newline at end of file
diff --git a/philo/contrib/gilbert/models.py b/philo/contrib/gilbert/models.py
new file mode 100644 (file)
index 0000000..08ed4f5
--- /dev/null
@@ -0,0 +1,8 @@
+from django.db import models
+from django.contrib.auth.models import User
+from philo.models.fields import JSONField
+
+
+class UserPreferences(models.Model):
+       user = models.OneToOneField(User, related_name='gilbert_userpreferences')
+       preferences = JSONField(default=dict())
\ No newline at end of file
diff --git a/philo/contrib/gilbert/plugins/auth.py b/philo/contrib/gilbert/plugins/auth.py
new file mode 100644 (file)
index 0000000..d0c514b
--- /dev/null
@@ -0,0 +1,72 @@
+from django.conf import settings
+from django.contrib.auth import logout
+from django.contrib.auth.forms import PasswordChangeForm
+from .base import Plugin
+from ..extdirect import ext_action, ext_method
+from ..models import UserPreferences
+
+
+@ext_action(name='auth')
+class Auth(Plugin):
+       @property
+       def index_js_urls(self):
+               return super(Auth, self).index_js_urls + [
+                       settings.STATIC_URL + 'gilbert/plugins/auth.js',
+               ]
+       
+       @property
+       def icon_names(self):
+               return super(Auth, self).icon_names + [
+                       'user-silhouette',
+                       'mask',
+                       'key--pencil',
+                       'door-open-out',
+               ]
+       
+       @ext_method
+       def whoami(self, request):
+               user = request.user
+               return user.get_full_name() or user.username
+       
+       @ext_method
+       def logout(self, request):
+               logout(request)
+               return True
+       
+       @ext_method
+       def get_passwd_form(self, request):
+               return PasswordChangeForm(request.user).as_extdirect()
+       
+       @ext_method(form_handler=True)
+       def save_passwd_form(self, request):
+               form = PasswordChangeForm(request.user, data=request.POST)
+               if form.is_valid():
+                       form.save()
+                       return True, None
+               else:
+                       return False, form.errors
+       
+       @ext_method
+       def get_preferences(self, request):
+               user_preferences, created = UserPreferences.objects.get_or_create(user=request.user)
+               
+               return user_preferences.preferences
+       
+       @ext_method
+       def set_preferences(self, request, preferences):
+               user_preferences, created = UserPreferences.objects.get_or_create(user=request.user)
+               user_preferences.preferences = preferences
+               
+               user_preferences.save()
+               return True
+       
+       @ext_method
+       def get_preference(self, request, key):
+               preferences = self.get_preferences(request)
+               return preferences.get(key, None)
+       
+       @ext_method
+       def set_preference(self, request, key, value):
+               preferences = self.get_preferences(request)
+               preferences[key] = value
+               return self.set_preferences(request, preferences)
\ No newline at end of file
diff --git a/philo/contrib/gilbert/plugins/base.py b/philo/contrib/gilbert/plugins/base.py
new file mode 100644 (file)
index 0000000..2be851b
--- /dev/null
@@ -0,0 +1,23 @@
+from ..extdirect import ext_action
+
+
+@ext_action
+class Plugin(object):
+       def __init__(self, site):
+               self.site = site
+       
+       @property
+       def index_css_urls(self):
+               return []
+       
+       @property
+       def index_js_urls(self):
+               return []
+       
+       @property
+       def index_extrahead(self):
+               return ''
+       
+       @property
+       def icon_names(self):
+               return []
\ No newline at end of file
diff --git a/philo/contrib/gilbert/plugins/models.py b/philo/contrib/gilbert/plugins/models.py
new file mode 100644 (file)
index 0000000..ee13989
--- /dev/null
@@ -0,0 +1,386 @@
+from django.conf import settings
+from django.contrib.admin.util import lookup_field, label_for_field, display_for_field, NestedObjects
+from django.core.exceptions import PermissionDenied
+from django.db import router
+from django.db.models import Q
+from django.db.models.fields.related import ManyToOneRel
+from django.db.models.fields.files import FieldFile, ImageFieldFile, FileField
+from django.forms.models import ModelForm, modelform_factory
+from django.template.defaultfilters import capfirst
+from django.utils import simplejson as json
+from django.utils.encoding import smart_unicode
+from .base import Plugin
+from ..extdirect import ext_action, ext_method
+import operator
+
+
+@ext_action(name='models')
+class Models(Plugin):
+       """
+       Plugin providing model-related UI and functionality on the client
+       side.
+       
+       """
+       
+       @property
+       def index_js_urls(self):
+               return super(Models, self).index_js_urls + [
+                       settings.STATIC_URL + 'gilbert/extjs/examples/ux/SearchField.js',
+                       settings.STATIC_URL + 'gilbert/plugins/models.js',
+               ]
+       
+       @property
+       def icon_names(self):
+               return super(Models, self).icon_names + [
+                       'databases',
+                       'database',
+                       'plus',
+                       'minus',
+                       'gear',
+                       'pencil',
+                       'database-import',
+                       'block',
+               ]
+
+
+@ext_action
+class ModelAdmin(Plugin):
+       """
+       Default ModelAdmin class used by Sites to expose a model-centric API
+       on the client side.
+       
+       """
+       
+       form = ModelForm
+       icon_name = 'block'
+       search_fields = ()
+       data_columns = ('__unicode__',)
+       data_editable_columns = ()
+       
+       def __init__(self, site, model):
+               super(ModelAdmin, self).__init__(site)
+               self.model = model
+               self.model_meta = model._meta
+       
+       @classmethod
+       def data_serialize_model_instance(cls, obj):
+               return {
+                       'app_label': obj._meta.app_label,
+                       'name': obj._meta.module_name,
+                       'pk': obj.pk,
+                       '__unicode__': unicode(obj),
+               }
+       
+       @classmethod
+       def data_serialize_field_value(cls, field, value):
+               if field is None:
+                       #return smart_unicode(value)
+                       return value
+               if isinstance(field.rel, ManyToOneRel):
+                       if value is not None:
+                               return cls.data_serialize_model_instance(value)
+               elif isinstance(value, FieldFile):
+                       new_value = {
+                               'path': value.path,
+                               'url': value.url,
+                               'size': value.size,
+                       }
+                       if isinstance(value, ImageFieldFile):
+                               new_value.update({
+                                       'width': value.width,
+                                       'height': value.height,
+                               })
+               else:
+                       return value
+       
+       @property
+       def sortable_fields(self):
+               return [field.name for field in self.model_meta.fields]
+       
+       @property
+       def data_fields(self):
+               fields = ['pk', '__unicode__']
+               fields.extend(self.data_columns)
+               fields.extend(field.name for field in self.model_meta.fields)
+               return tuple(set(fields))
+       
+       @property
+       def data_fields_spec(self):
+               spec = []
+               for field_name in self.data_fields:
+                       field_spec = {
+                               'name': field_name,
+                       }
+                       if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
+                               field_spec['type'] = 'gilbertmodelforeignkey'
+                       elif field_name in [field.name for field in self.model_meta.fields if isinstance(field, FileField)]:
+                               field_spec['type'] = 'gilbertmodelfilefield'
+                       spec.append(field_spec)
+               return spec
+       
+       @property
+       def data_columns_spec(self):
+               spec = []
+               
+               for field_name in self.data_columns:
+                       column = {
+                               'dataIndex': field_name,
+                               'sortable': False,
+                               'editable': False,
+                       }
+                       header, attr = label_for_field(field_name, self.model, model_admin=self, return_attr=True)
+                       column['header'] = capfirst(header)
+                       if (field_name in self.sortable_fields) or (getattr(attr, 'admin_order_field', None) in self.sortable_fields):
+                               column['sortable'] = True
+                       if field_name in self.data_editable_columns:
+                               column['editable'] = True
+                       if field_name in [field.name for field in self.model_meta.fields if isinstance(field.rel, ManyToOneRel)]:
+                               column['xtype'] = 'foreignkeycolumn'
+                       spec.append(column)
+               return spec
+       
+       @property
+       def data_columns_spec_json(self):
+               return json.dumps(self.data_columns_spec)
+       
+       @property
+       def icon_names(self):
+               return super(ModelAdmin, self).icon_names + [
+                       self.icon_name
+               ]
+       
+       @ext_method
+       def has_permission(self, request):
+               return self.has_read_permission(request) or self.has_add_permission(request)
+       
+       @ext_method
+       def has_read_permission(self, request):
+               return self.has_change_permission(request)
+       
+       @ext_method
+       def has_add_permission(self, request):
+               return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_add_permission())
+       
+       @ext_method
+       def has_change_permission(self, request):
+               return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_change_permission())
+       
+       @ext_method
+       def has_delete_permission(self, request):
+               return request.user.has_perm(self.model_meta.app_label + '.' + self.model_meta.get_delete_permission())
+       
+       @ext_method
+       def all(self, request):
+               if not self.has_read_permission(request):
+                       raise PermissionDenied
+               return self.model._default_manager.all()
+       
+       def queryset(self, request):
+               return self.model._default_manager.get_query_set()
+       
+       @ext_method
+       def filter(self, request, **kwargs):
+               if not self.has_read_permission(request):
+                       raise PermissionDenied
+               return self.queryset(request).filter(**kwargs)
+       
+       @ext_method
+       def get(self, request, **kwargs):
+               if not self.has_read_permission(request):
+                       raise PermissionDenied
+               return self.queryset(request).values().get(**kwargs)
+       
+       @property
+       def form_class(self):
+               return modelform_factory(self.model, form=self.form)
+       
+       @ext_method
+       def get_form(self, request, **kwargs):
+               if len(kwargs) > 0:
+                       instance = self.model._default_manager.all().get(**kwargs)
+               else:
+                       if not self.has_add_permission(request):
+                               raise PermissionDenied
+                       instance = None
+               
+               if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
+                       raise PermissionDenied
+               
+               return self.form_class(instance=instance).as_extdirect()
+       
+       @ext_method(form_handler=True)
+       def save_form(self, request):
+               if 'pk' in request.POST:
+                       try:
+                               instance = self.model._default_manager.all().get(pk=request.POST['pk'])
+                       except ObjectDoesNotExist:
+                               instance = None
+               else:
+                       instance = None
+               
+               if (instance and not self.has_change_permission(request)) or not self.has_add_permission(request):
+                       raise PermissionDenied
+               
+               form = self.form_class(request.POST, request.FILES, instance=instance)
+               
+               if form.is_valid():
+                       saved = form.save()
+                       return True, None, saved.pk
+               else:
+                       return False, form.errors
+       
+       def data_serialize_object(self, obj):
+               row = {}
+               for field_name in self.data_fields:
+                       result = None
+                       try:
+                               field, attr, value = lookup_field(field_name, obj, self)
+                       except (AttributeError, ObjectDoesNotExist):
+                               pass
+                       else:
+                               result = self.data_serialize_field_value(field, value)
+                       row[field_name] = result
+               return row
+       
+       @property
+       def data_metadata(self):
+               return {
+                       'idProperty': 'pk',
+                       'root': 'root',
+                       'totalProperty': 'total',
+                       'successProperty': 'success',
+                       'fields': self.data_fields_spec,
+               }
+       
+       def data_serialize_queryset(self, queryset, params=None):
+               serialized = {
+                       'metaData': self.data_metadata,
+                       'root': [],
+                       'total': queryset.count(),
+                       'success': True,
+               }
+               
+               if params is not None:
+                       if 'sort' in params:
+                               order_by = params['sort']
+                               if order_by in self.data_fields:
+                                       if order_by not in self.sortable_fields:
+                                               try:
+                                                       if hasattr(self, order_by):
+                                                               attr = getattr(self, order_by)
+                                                       else:
+                                                               attr = getattr(self.model, order_by)
+                                                       order_by = attr.admin_order_field
+                                               except AttributeError:
+                                                       order_by = None
+                                       if order_by is not None:
+                                               if params.get('dir', 'ASC') == 'DESC':
+                                                       order_by = '-' + order_by
+                                               serialized['metaData']['sortInfo'] = {
+                                                       'field': params['sort'],
+                                                       'direction': params.get('dir', 'ASC'),
+                                               }
+                                               queryset = queryset.order_by(order_by)
+                       if 'start' in params:
+                               start = params['start']
+                               serialized['metaData']['start'] = start
+                               if 'limit' in params:
+                                       limit = params['limit']
+                                       serialized['metaData']['limit'] = limit
+                                       queryset = queryset[start:(start+limit)]
+                               else:
+                                       queryset = queryset[start:]
+               
+               for obj in queryset:
+                       serialized['root'].append(self.data_serialize_object(obj))
+               
+               return serialized
+       
+       @ext_method
+       def data_read(self, request, **params):
+               if not self.has_read_permission(request):
+                       raise PermissionDenied
+               
+               queryset = self.model._default_manager.all()
+               query = params.pop('query', None)
+               filters = params.pop('filters', None)
+               
+               if filters:
+                       if isinstance(filters, Q):
+                               queryset = queryset.filter(filters)
+                       elif isinstance(filters, dict):
+                               queryset = queryset.filter(**filters)
+                       else:
+                               raise TypeError('Invalid filters parameter')
+               
+               def construct_search(field_name):
+                       if field_name.startswith('^'):
+                               return "%s__istartswith" % field_name[1:]
+                       elif field_name.startswith('='):
+                               return "%s__iexact" % field_name[1:]
+                       elif field_name.startswith('@'):
+                               return "%s__search" % field_name[1:]
+                       else:
+                               return "%s__icontains" % field_name
+               
+               if self.search_fields and query:
+                       for word in query.split():
+                               or_queries = [Q(**{construct_search(str(field_name)): word}) for field_name in self.search_fields]
+                               queryset = queryset.filter(reduce(operator.or_, or_queries))
+                       for field_name in self.search_fields:
+                               if '__' in field_name:
+                                       queryset = queryset.distinct()
+                                       break
+               
+               return self.data_serialize_queryset(queryset, params)
+       
+       @ext_method
+       def data_create(self, request, **kwargs):
+               if not self.has_add_permission(request):
+                       raise PermissionDenied
+               
+               return kwargs
+       
+       @ext_method
+       def data_update(self, request, **kwargs):
+               if not self.has_change_permission(request):
+                       raise PermissionDenied
+               
+               return kwargs
+       
+       @ext_method
+       def data_destroy(self, request, **params):
+               if not self.has_delete_permission(request):
+                       raise PermissionDenied
+               
+               pks = params['root']
+               
+               if type(pks) is not list:
+                       pks = [pks]
+               
+               for pk in pks:
+                       if type(pk) is dict:
+                               pk = pk['pk']
+                       obj = self.model._default_manager.all().get(pk=pk)
+                       obj.delete()
+               
+               return {
+                       'metaData': self.data_metadata,
+                       'success': True,
+                       'root': list(),
+               }
+       
+       @ext_method
+       def data_destroy_consequences(self, request, pks):
+               if not self.has_delete_permission(request):
+                       raise PermissionDenied
+               
+               if type(pks) is not list:
+                       pks = [pks]
+               objs = [self.model._default_manager.all().get(pk=pk) for pk in pks]
+               
+               using = router.db_for_write(self.model)
+               collector = NestedObjects(using=using)
+               collector.collect(objs)
+               
+               return collector.nested(self.data_serialize_model_instance)
\ No newline at end of file
diff --git a/philo/contrib/gilbert/sites.py b/philo/contrib/gilbert/sites.py
new file mode 100644 (file)
index 0000000..3d07a84
--- /dev/null
@@ -0,0 +1,238 @@
+from django.conf import settings
+from django.contrib.auth import authenticate, login
+from django.core.context_processors import csrf
+from django.conf.urls.defaults import url, patterns, include
+from django.core.urlresolvers import reverse
+from django.db.models.base import ModelBase
+from django.forms.models import model_to_dict
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils import simplejson as json
+from django.utils.datastructures import SortedDict
+from django.views.decorators.cache import never_cache
+from philo.utils import fattr
+from . import __version__ as gilbert_version
+from .exceptions import AlreadyRegistered, NotRegistered
+from .extdirect import ExtAction, ExtRouter
+from .plugins.auth import Auth
+from .plugins.models import Models, ModelAdmin
+from inspect import getargspec
+from functools import partial, update_wrapper
+import sys, os, datetime
+
+
+
+__all__ = ('GilbertSite', 'site')
+
+
+class CoreRouter(ExtRouter):
+       def __init__(self, site):
+               self.site = site
+               self._actions = {}
+       
+       @property
+       def namespace(self):
+               return 'Gilbert.api.plugins'
+       
+       @property
+       def url(self):
+               return reverse('%s:router' % self.site.namespace, current_app=self.site.app_name)
+       
+       @property
+       def type(self):
+               return 'remoting'
+       
+       @property
+       def actions(self):
+               return self._actions
+       
+       @property
+       def plugins(self):
+               return list(action.obj for action in self._actions.itervalues())
+       
+       def register_plugin(self, plugin):
+               action = ExtAction(plugin)
+               self._actions[action.name] = action
+
+
+class ModelRouter(ExtRouter):
+       def __init__(self, site, app_label):
+               self.site = site
+               self.app_label = app_label
+               self._actions = {}
+       
+       @property
+       def namespace(self):
+               return 'Gilbert.api.models.%s' % self.app_label
+       
+       @property
+       def url(self):
+               return reverse('%s:model_router' % self.site.namespace, current_app=self.site.app_name, kwargs={'app_label': self.app_label})
+       
+       @property
+       def type(self):
+               return 'remoting'
+       
+       @property
+       def actions(self):
+               return self._actions
+       
+       @property
+       def models(self):
+               return dict((name, action.obj) for name, action in self._actions.iteritems())
+       
+       def register_admin(self, name, admin):
+               action = ExtAction(admin)
+               action.name = name
+               self._actions[action.name] = action
+
+
+class GilbertSite(object):
+       version = gilbert_version
+       
+       def __init__(self, namespace='gilbert', app_name='gilbert', title=None):
+               self.namespace = namespace
+               self.app_name = app_name
+               if title is None:
+                       self.title = getattr(settings, 'GILBERT_TITLE', 'Gilbert')
+               else:
+                       self.title = title
+               
+               self.core_router = CoreRouter(self)
+               self.model_routers = SortedDict()
+               
+               self.register_plugin(Models)
+               self.register_plugin(Auth)
+       
+       def register_plugin(self, plugin):
+               self.core_router.register_plugin(plugin(self))
+       
+       def register_model(self, model_or_iterable, admin_class=ModelAdmin, **admin_attrs):
+               if isinstance(model_or_iterable, ModelBase):
+                       model_or_iterable = [model_or_iterable]
+               for model in model_or_iterable:
+                       app_label = model._meta.app_label
+                       name = model._meta.object_name
+                       
+                       if app_label not in self.model_routers:
+                               self.model_routers[app_label] = ModelRouter(self, app_label)
+                       router = self.model_routers[app_label]
+                       
+                       if admin_attrs:
+                               admin_attrs['__module__'] = __name__
+                               admin_class = type('%sAdmin' % model.__name__, (admin_class,), admin_attrs)
+                       
+                       router.register_admin(name, admin_class(self, model))
+       
+       def has_permission(self, request):
+               return request.user.is_active and request.user.is_staff
+       
+       def protected_view(self, view, login_page=True, cacheable=False):
+               def inner(request, *args, **kwargs):
+                       if not self.has_permission(request):
+                               if login_page:
+                                       return self.login(request)
+                               else:
+                                       return HttpResponse(status=403)
+                       return view(request, *args, **kwargs)
+               if not cacheable:
+                       inner = never_cache(inner)
+               return update_wrapper(inner, view)
+       
+       @property
+       def urls(self):
+               urlpatterns = patterns('',
+                       url(r'^$', self.protected_view(self.index), name='index'),
+                       url(r'^api.js$', self.protected_view(self.api, login_page=False), name='api'),
+                       url(r'^icons.css$', self.protected_view(self.icons, login_page=False), name='icons'),
+                       url(r'^router$', self.protected_view(self.router, login_page=False), name='router'),
+                       url(r'^router/models/(?P<app_label>\w+)$', self.protected_view(self.router, login_page=False), name='model_router'),
+               )
+               return (urlpatterns, self.app_name, self.namespace)
+       
+       def login(self, request):
+               context = {
+                       'gilbert': self,
+                       'form_url': request.get_full_path(),
+               }
+               context.update(csrf(request))
+               
+               if request.POST:
+                       if request.session.test_cookie_worked():
+                               request.session.delete_test_cookie()
+                               username = request.POST.get('username', None)
+                               password = request.POST.get('password', None)
+                               user = authenticate(username=username, password=password)
+                               if user is not None:
+                                       if user.is_active and user.is_staff:
+                                               login(request, user)
+                                               return HttpResponseRedirect(request.get_full_path())
+                                       else:
+                                               context.update({
+                                                       'error_message_short': 'Not staff',
+                                                       'error_message': 'You do not have access to this page.',
+                                               })
+                               else:
+                                       context.update({
+                                               'error_message_short': 'Invalid credentials',
+                                               'error_message': 'Unable to authenticate using the provided credentials. Please try again.',
+                                       })
+                       else:
+                               context.update({
+                                       'error_message_short': 'Cookies disabled',
+                                       'error_message': 'Please enable cookies, reload this page, and try logging in again.',
+                               })
+               
+               request.session.set_test_cookie()
+               return render_to_response('gilbert/login.html', context, context_instance=RequestContext(request))
+       
+       def index(self, request):
+               return render_to_response('gilbert/index.html', {
+                       'gilbert': self,
+                       'plugins': self.core_router.plugins # needed as the template language will not traverse callables
+               }, context_instance=RequestContext(request))
+       
+       def api(self, request):
+               providers = []
+               model_registry = {}
+               
+               for app_label, router in self.model_routers.items():
+                       if request.user.has_module_perms(app_label):
+                               providers.append(router.spec)
+                               model_registry[app_label] = dict((model_name, admin) for model_name, admin in router.models.items() if admin.has_permission(request))
+               
+               providers.append(self.core_router.spec)
+               
+               context = {
+                       'gilbert': self,
+                       'providers': [json.dumps(provider, separators=(',', ':')) for provider in providers],
+                       'model_registry': model_registry,
+               }
+               context.update(csrf(request))
+               
+               return render_to_response('gilbert/api.js', context, mimetype='text/javascript')
+       
+       def icons(self, request):
+               icon_names = []
+               
+               for plugin in self.core_router.plugins:
+                       icon_names.extend(plugin.icon_names)
+               
+               for router in self.model_routers.values():
+                       for admin in router.models.values():
+                               icon_names.extend(admin.icon_names)
+               
+               return render_to_response('gilbert/icons.css', {
+                       'icon_names': set(icon_names),
+                       'STATIC_URL': settings.STATIC_URL
+               }, mimetype='text/css')
+       
+       def router(self, request, app_label=None, extra_context=None):
+               if app_label is None:
+                       return self.core_router.render_to_response(request)
+               else:
+                       return self.model_routers[app_label].render_to_response(request)
+
+
+site = GilbertSite()
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/gilbert.js b/philo/contrib/gilbert/static/gilbert/gilbert.js
new file mode 100644 (file)
index 0000000..663776c
--- /dev/null
@@ -0,0 +1,10 @@
+Ext.override(String, {
+       
+       capfirst: function () {
+               return this.substr(0, 1).toUpperCase() + this.substr(1);
+       },
+       
+});
+
+
+Gilbert = new Gilbert.lib.app.Application(Gilbert);
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/lib/app.js b/philo/contrib/gilbert/static/gilbert/lib/app.js
new file mode 100644 (file)
index 0000000..b21455d
--- /dev/null
@@ -0,0 +1,353 @@
+Ext.ns('Gilbert.lib.app')
+
+
+Gilbert.lib.app.Desktop = Ext.extend(Ext.Panel, {
+       
+       constructor: function(application, config) {
+               var application = this.application = application;
+               Gilbert.lib.app.Desktop.superclass.constructor.call(this, Ext.applyIf(config||{}, {
+                       region: 'center',
+                       border: false,
+                       padding: '5px',
+                       bodyStyle: 'background: none;',
+               }));
+       },
+       
+});
+
+
+Gilbert.lib.app.MainMenu = Ext.extend(Ext.Toolbar, {
+       
+       constructor: function(application, config) {
+               var application = this.application = application;
+               Gilbert.lib.app.MainMenu.superclass.constructor.call(this, Ext.applyIf(config||{}, {
+                       region: 'north',
+                       autoHeight: true,
+                       items: [
+                               {
+                                       xtype: 'tbtext',
+                                       text: '<span style="font-weight: bolder; text-transform: uppercase;">Gilbert</span>',
+                               },
+                               {
+                                       xtype: 'tbseparator',
+                               },
+                       ],
+               }));
+       },
+       
+});
+
+
+Gilbert.lib.app.TaskBar = Ext.extend(Ext.Toolbar, {
+       
+       constructor: function(application, config) {
+               var application = this.application = application;
+               Gilbert.lib.app.TaskBar.superclass.constructor.call(this, Ext.applyIf(config||{}, {
+                       region: 'south',
+                       enableOverflow: true,
+                       autoHeight: true,
+                       items: [],
+                       plugins: [
+                               new Ext.ux.ToolbarReorderer({
+                                       defaultReorderable: true,
+                               }),
+                       ],
+               }));
+       },
+       
+       get_button_for_window: function(win) {
+               return this.find('represented_window', win)[0];
+       },
+       
+       default_button_handler: function(button) {
+               var win = button.represented_window;
+               if (this.active_window === win) {
+                       win.minimize();
+               } else {
+                       win.show();
+               }
+       },
+       
+       register_window: function(win) {
+               win.on('show', this.window_shown, this);
+               win.on('hide', this.window_hidden, this);
+               win.on('minimize', this.window_minimize, this);
+               win.on('deactivate', this.window_deactivated, this);
+               win.on('activate', this.window_activated, this);
+               win.on('titlechange', this.window_titlechanged, this);
+               win.on('iconchange', this.window_iconchanged, this);
+               
+               var button = new Ext.Button({
+                       text: win.title,
+                       iconCls: win.iconCls,
+                       enableToggle: true,
+                       allowDepress: false,
+                       width: 200,
+                       hidden: true,
+               });
+               button.represented_window = win;
+               button.setHandler(this.default_button_handler, this);
+               
+               this.add(button);
+               
+               win.on('destroy', this.window_destroyed, this);
+       },
+       
+       window_destroyed: function(win) {
+               this.remove(this.get_button_for_window(win));
+               this.application.do_layout();
+       },
+       
+       window_shown: function(win) {
+               if (this.minimizing_window !== win) {
+                       this.get_button_for_window(win).show();
+                       this.application.do_layout();
+               }
+       },
+       
+       window_hidden: function(win) {
+               if (this.minimizing_window !== win) {
+                       this.get_button_for_window(win).hide();
+                       this.application.do_layout();
+               }
+       },
+       
+       window_minimize: function(win) {
+               var button = this.get_button_for_window(win);
+               
+               this.minimizing_window = win;
+               win.hide(button.el, function () {
+                       this.minimizing_window = undefined;
+                       
+                       win.minimized = true;
+                       button.setText('<i>'+win.title+'</i>');
+                       button.setHandler(function (button) {
+                               var win = button.represented_window;
+                               
+                               win.minimized = false;
+                               button.setText(win.title);
+                               button.setHandler(this.default_button_handler, this);
+                               
+                               this.minimizing_window = win;
+                               win.show(button.el, function () {
+                                       this.minimizing_window = undefined;
+                               }, this);
+                       }, this);
+               }, this);
+       },
+       
+       window_deactivated: function(win) {
+               var button = this.get_button_for_window(win);
+               button.toggle(false);
+               button.setText(win.title);
+               
+               if (this.active_window === win) {
+                       this.active_window = undefined;
+               }
+       },
+       
+       window_activated: function(win) {
+               var button = this.get_button_for_window(win);
+               button.toggle(true);
+               button.setText('<b>'+win.title+'</b>');
+               
+               this.active_window = win;
+       },
+       
+       window_titlechanged: function(win) {
+               var button = this.get_button_for_window(win);
+               if (win.minimized) {
+                       button.setText('<i>'+win.title+'</i>');
+               } else {
+                       button.setText(win.title);
+               }
+       },
+       
+       window_iconchanged: function(win) {
+               var button = this.get_button_for_window(win);
+               button.setIconClass(win.iconCls);
+       },
+       
+});
+
+
+Gilbert.lib.app.Application = Ext.extend(Ext.util.Observable, {
+       
+       constructor: function (config) {
+               
+               this.models = {};
+               this.plugins = {};
+               
+               Ext.apply(this, config, {
+                       renderTo: Ext.getBody(),
+               });
+               
+               Gilbert.lib.app.Application.superclass.constructor.call(this);
+               
+               this.addEvents({
+                       'ready': true,
+                       'model_registered': true,
+                       'plugin_registered': true,
+                       'window_created': true,
+               });
+               
+               Ext.onReady(this.pre_init.createDelegate(this));
+       },
+       
+       pre_init: function () {
+               var outer = this;
+               
+               Gilbert.api.plugins.auth.get_preference('gilbert.background', function (background) {
+                       if (background) {
+                               outer.renderTo.setStyle('background', background);
+                       }
+               });
+               Gilbert.api.plugins.auth.get_preference('gilbert.theme', function (theme) {
+                       if (theme) {
+                               outer._set_theme(theme);
+                       }
+                       outer.init();
+               });
+       },
+       
+       _set_theme: function(theme) {
+               var link_element = document.getElementById('gilbert.theme.' + theme);
+               if (link_element) {
+                       Ext.each(document.getElementsByClassName('gilbert.theme'), function (theme_element) {
+                               if (theme_element != link_element) {
+                                       theme_element.disabled = true;
+                               } else {
+                                       theme_element.disabled = false;
+                               }
+                       });
+               }
+       },
+       
+       init: function () {
+               Ext.QuickTips.init();
+               
+               var desktop = this.desktop = new Gilbert.lib.app.Desktop();
+               var mainmenu = this.mainmenu = new Gilbert.lib.app.MainMenu(this);
+               var taskbar = this.taskbar = new Gilbert.lib.app.TaskBar(this);
+               var viewport = this.viewport = new Ext.Viewport({
+                       renderTo: this.renderTo,
+                       layout: 'border',
+                       items: [
+                               this.mainmenu,
+                               this.desktop,
+                               this.taskbar,
+                       ],
+               });
+               
+               var windows = this.windows = new Ext.WindowGroup();
+               
+               Ext.Direct.on('exception', function (exception) {
+                       if (exception.code == Ext.Direct.exceptions.TRANSPORT) {
+                               if (exception.xhr.status == 403) {
+                                       window.alert('You have been unexpectedly logged out.');
+                                       window.location.reload(true);
+                               }
+                       }
+                       if (exception.html) {
+                               var win = this.create_window({
+                                       width: 400,
+                                       height: 300,
+                                       maximizable: true,
+                                       minimizable: false,
+                                       modal: true,
+                                       html_source: exception.html,
+                               }, Gilbert.lib.ui.HTMLWindow);
+                               win.show();
+                       }
+               }, this);
+               
+               var initial_plugins = this.plugins;
+               this.plugins = {};
+               
+               Ext.iterate(initial_plugins, function (name, plugin, plugins) {
+                       this.register_plugin(name, plugin);
+               }, this);
+               
+               this.do_layout();
+               
+               this.renderTo.on('contextmenu', Ext.emptyFn, null, {preventDefault: true});
+               
+               window.onbeforeunload = function (event) {
+                       var notice = 'You will lose all unsaved changes and windows.';
+                       var event = event || window.event;
+                       if (event) {
+                               event.returnValue = notice;
+                       }
+               
+                       return notice;
+               };
+               
+               this.fireEvent('ready', this);
+       },
+       
+       create_window: function(config, cls) {
+               var win = new(cls||Ext.Window)(Ext.applyIf(config||{},{
+                       renderTo: this.desktop.el,
+                       manager: this.windows,
+                       minimizable: true,
+                       constrainHeader: true,
+               }));
+               
+               win.render(this.desktop.el);
+               if (win.modal) {
+                       win.on('show', function () {
+                               this.mainmenu.hide();
+                               this.taskbar.hide();
+                               this.do_layout();
+                       }, this);
+                       win.on('hide', function () {
+                               this.taskbar.show();
+                               this.mainmenu.show();
+                               this.do_layout();
+                       }, this);
+                       win.on('close', function () {
+                               this.taskbar.show();
+                               this.mainmenu.show();
+                               this.do_layout();
+                       }, this);
+               } else {
+                       win.setPosition(Math.floor(Math.random() * ((this.desktop.getInnerWidth() - win.width) - 1)), Math.floor(Math.random() * ((this.desktop.getInnerHeight() - win.height) - 1)));
+               }
+               this.taskbar.register_window(win);
+               this.fireEvent('window_created', win);
+               return win;
+       },
+       
+       do_layout: function() {
+               this.mainmenu.doLayout();
+               this.taskbar.doLayout();
+               this.viewport.doLayout();
+       },
+       
+       register_plugin: function (name, plugin) {
+               if (plugin.init(this) != false) {
+                       this.plugins[name] = plugin;
+                       this.fireEvent('plugin_registered', name, plugin, this);
+               }
+       },
+       
+       get_plugin: function (name) {
+               return this.plugins[name];
+       },
+       
+       register_model: function (app_label, name, model) {
+               if (!this.models[app_label]) {
+                       this.models[app_label] = {};
+               }
+               this.models[app_label][name] = model;
+               this.fireEvent('model_registered', name, model, this);
+       },
+       
+       get_model: function (app_label, name) {
+               if (!this.models[app_label]) {
+                       return undefined;
+               }
+               return this.models[app_label][name];
+       },
+       
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/lib/models.js b/philo/contrib/gilbert/static/gilbert/lib/models.js
new file mode 100644 (file)
index 0000000..427f92d
--- /dev/null
@@ -0,0 +1,87 @@
+Ext.ns('Gilbert.lib.models');
+
+
+Gilbert.lib.models.Model = Ext.extend(Object, {
+       
+       constructor: function (config) {
+               Ext.apply(this, config);
+               this.drag_drop_group = 'Gilbert.lib.models.Model(' + this.app_label + ',' + this.name + ') ';
+       },
+       
+       create_reader: function (config) {
+               return new Ext.data.JsonReader(Ext.applyIf(config||{}, {}));
+       },
+       
+       create_writer: function (config) {
+               return new Ext.data.JsonWriter(Ext.applyIf(config||{}, {
+                       encode: false,
+               }));
+       },
+       
+       create_proxy: function (config) {
+               return new Ext.data.DirectProxy(Ext.applyIf(config||{},{
+                       paramsAsHash: true,
+                       api: {
+                               read: this.api.data_read,
+                               create: this.api.data_create,
+                               update: this.api.data_update,
+                               destroy: this.api.data_destroy,
+                       },
+               }));
+       },
+       
+       create_store: function (config) {
+               return new Ext.data.Store(Ext.applyIf(config||{},{
+                       proxy: this.create_proxy(),
+                       reader: this.create_reader(),
+                       writer: this.create_writer(),
+                       remoteSort: true,
+               }));
+       },
+       
+});
+
+
+Gilbert.lib.models.ModelInstance = Ext.extend(Object, {
+       
+       constructor: function (model, pk, __unicode__) {
+               this.model = model;
+               this.pk = pk;
+               this.__unicode__ = __unicode__
+       },
+       
+});
+
+
+Ext.data.Types.GILBERTMODELFOREIGNKEY = {
+       
+       convert: function (v, data) {
+               if (v) {
+                       return new Gilbert.lib.models.ModelInstance(Gilbert.get_model(v.app_label, v.name), v.pk, v.__unicode__);
+               } else {
+                       return null;
+               }
+       },
+       
+       sortType: Ext.data.SortTypes.none,
+       
+       type: 'gilbertmodelforeignkey',
+       
+}
+
+
+Ext.data.Types.GILBERTMODELFILEFIELD = {
+       
+       convert: function (v, data) {
+               if (v) {
+                       return v.url;
+               } else {
+                       return null;
+               }
+       },
+       
+       sortType: Ext.data.SortTypes.none,
+       
+       type: 'gilbertmodelfilefield',
+       
+}
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/lib/plugins.js b/philo/contrib/gilbert/static/gilbert/lib/plugins.js
new file mode 100644 (file)
index 0000000..66ff46f
--- /dev/null
@@ -0,0 +1,14 @@
+Ext.ns('Gilbert.lib.plugins');
+
+
+Gilbert.lib.plugins.Plugin = Ext.extend(Object, {
+       
+       constructor: function (config) {
+               Ext.apply(this, config);
+       },
+       
+       init: function (application) {
+               var application = this.application = application;
+       }
+       
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/lib/ui/forms.js b/philo/contrib/gilbert/static/gilbert/lib/ui/forms.js
new file mode 100644 (file)
index 0000000..ca7f4e6
--- /dev/null
@@ -0,0 +1,963 @@
+Ext.ns('Gilbert.lib.ui.forms');
+
+
+Gilbert.lib.ui.forms.ClearableComboBox = Ext.extend(Ext.form.ComboBox, {
+       
+       initComponent: function () {
+               Gilbert.lib.ui.forms.ClearableComboBox.superclass.initComponent.call(this);
+               
+               this.triggerConfig = {
+                       tag: 'span',
+                       cls: 'x-form-twin-triggers',
+                       cn: [
+                               {
+                                       tag: 'img',
+                                       src: Ext.BLANK_IMAGE_URL,
+                                       alt: '',
+                                       cls: 'x-form-trigger x-form-clear-trigger', 
+                               },
+                               {
+                                       tag: 'img',
+                                       src: Ext.BLANK_IMAGE_URL,
+                                       alt: '',
+                                       cls: 'x-form-trigger ' + this.triggerClass,
+                               },
+                       ],
+               };
+       },
+       
+       afterRender: function () {
+               Gilbert.lib.ui.forms.ClearableComboBox.superclass.afterRender.call(this);
+               
+               if (this.value && this.allowBlank) {
+                       this.triggers[0].show();
+               } else {
+                       this.triggers[0].hide();
+               }
+       },
+       
+       initTrigger: function () {
+               Ext.form.TwinTriggerField.prototype.initTrigger.call(this);
+       },
+       
+       getTriggerWidth: function () {
+               Ext.form.TwinTriggerField.prototype.getTriggerWidth.call(this);
+       },
+       
+       onTrigger2Click: function () {
+               this.onTriggerClick();
+       },
+       
+       onTrigger1Click: function () {
+               this.clearValue();
+               this.triggers[0].hide();
+       },
+       
+       setValue: function (v) {
+               Gilbert.lib.ui.forms.ClearableComboBox.superclass.setValue.call(this, v);
+               
+               if (this.value && this.allowBlank) {
+                       this.triggers[0].show();
+               } else {
+                       this.triggers[0].hide();
+               }
+       },
+       
+       onDestroy: function () {
+               Ext.destroy(this.triggers);
+               
+               Gilbert.lib.ui.forms.ClearableComboBox.superclass.onDestroy.call(this);
+       },
+       
+});
+Ext.reg('gilbertclearablecombo', Gilbert.lib.ui.forms.ClearableComboBox);
+
+
+Gilbert.lib.ui.forms.ModelChoiceField = Ext.extend(Gilbert.lib.ui.forms.ClearableComboBox, {
+       
+       model_app_label: undefined,
+       
+       model_name: undefined,
+       
+       model_filters: {},
+       
+       initComponent: function () {
+               if (!this.model) {
+                       this.model = Gilbert.get_model(this.model_app_label, this.model_name);
+               }
+               if (!this.store) {
+                       if (!this.model && this.backup_store) {
+                               this.store = this.backup_store;
+                       } else if (this.model) {
+                               this.store = this.model.create_store({
+                                       baseParams: {
+                                               filters: this.model_filters,
+                                       },
+                               });
+                               this.valueField = 'pk';
+                               this.displayField = '__unicode__';
+                       
+                               this.on('beforequery', function () {
+                                       delete this.lastQuery;
+                               }, this);
+                               this.store.on('load', function (store, records, options) {
+                                       this.store_loaded = true;
+                               }, this, {single: true});
+                               this.store.load();
+                               
+                               this.on('render', function () {
+                                       var outer = this;
+                                       this.dropTarget = new Ext.dd.DropTarget(this.el, {
+                                               ddGroup: outer.model.drag_drop_group,
+                                               notifyEnter: function (source, e, data) {
+                                                       outer.el.highlight();
+                                                       return Ext.dd.DropTarget.prototype.notifyEnter.call(this);
+                                               },
+                                               notifyDrop: function (source, e, data) {
+                                                       outer.setValue(data.selections[0].id);
+                                                       return true;
+                                               },
+                                       });
+                               }, this);
+                       }
+               }
+               Gilbert.lib.ui.forms.ModelChoiceField.superclass.initComponent.call(this);
+       },
+       
+       setValue: function (v) {
+               if (this.model && !this.store_loaded) {
+                       this.el.dom.value = this.loadingText;
+                       this.store.on('load', this.setValue.createDelegate(this, [v]), null, {single: true});
+                       return;
+               }
+               return Gilbert.lib.ui.forms.ModelChoiceField.superclass.setValue.call(this, v);
+       }
+       
+});
+Ext.reg('gilbertmodelchoicefield', Gilbert.lib.ui.forms.ModelChoiceField);
+
+
+Gilbert.lib.ui.forms.MultipleChoiceField = Ext.extend(Ext.ux.form.SuperBoxSelect, {});
+Ext.reg('gilbertmultiplechoicefield', Gilbert.lib.ui.forms.MultipleChoiceField);
+
+
+Gilbert.lib.ui.forms.ModelMultipleChoiceField = Ext.extend(Gilbert.lib.ui.forms.MultipleChoiceField, {});
+Ext.reg('gilbertmodelmultiplechoicefield', Gilbert.lib.ui.forms.ModelMultipleChoiceField);
+
+
+/*
+Gilbert.lib.ui.DateTimeField is derived from revision 813 of Ext.ux.form.DateTime by Ing. Jozef SakáloÅ¡ as posted at http://extjs.com/forum/showthread.php?t=22661. 
+It, and the original, is licensed under the GNU LGPL version 3.0 (http://www.gnu.org/licenses/lgpl.html).
+*/
+
+/**
+ * Creates new DateTimeField
+ * @constructor
+ * @param {Object} config A config object
+ */
+Gilbert.lib.ui.DateTimeField = Ext.extend(Ext.form.Field, {
+       /**
+        * @cfg {Function} dateValidator A custom validation function to be called during date field
+        * validation (defaults to null)
+        */
+       dateValidator: null
+       /**
+        * @cfg {String/Object} defaultAutoCreate DomHelper element spec
+        * Let superclass to create hidden field instead of textbox. Hidden will be submittend to server
+        */
+       ,
+       defaultAutoCreate: {
+               tag: 'input',
+               type: 'hidden'
+       }
+       /**
+        * @cfg {String} dtSeparator Date - Time separator. Used to split date and time (defaults to ' ' (space))
+        */
+       ,
+       dtSeparator: ' '
+       /**
+        * @cfg {String} hiddenFormat Format of datetime used to store value in hidden field
+        * and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql format)
+        */
+       ,
+       hiddenFormat: 'Y-m-d H:i:s'
+       /**
+        * @cfg {Boolean} otherToNow Set other field to now() if not explicly filled in (defaults to true)
+        */
+       ,
+       otherToNow: true
+       /**
+        * @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty value.
+        * If it is true then setValue() sets value of field to current date and time (defaults to false)
+        */
+       /**
+        * @cfg {String} timePosition Where the time field should be rendered. 'right' is suitable for forms
+        * and 'below' is suitable if the field is used as the grid editor (defaults to 'right')
+        */
+       ,
+       timePosition: 'right'
+       // valid values:'below', 'right'
+       /**
+        * @cfg {Function} timeValidator A custom validation function to be called during time field
+        * validation (defaults to null)
+        */
+       ,
+       timeValidator: null
+       /**
+        * @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
+        */
+       ,
+       timeWidth: 100
+       /**
+        * @cfg {String} dateFormat Format of DateField. Can be localized. (defaults to 'm/y/d')
+        */
+       ,
+       dateFormat: 'm/d/y'
+       /**
+        * @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults to 'g:i A')
+        */
+       ,
+       timeFormat: 'g:i A'
+       /**
+        * @cfg {Object} dateConfig Config for DateField constructor.
+        */
+       /**
+        * @cfg {Object} timeConfig Config for TimeField constructor.
+        */
+
+       // {{{
+       /**
+        * @private
+        * creates DateField and TimeField and installs the necessary event handlers
+        */
+       ,
+       initComponent: function() {
+               // call parent initComponent
+               Gilbert.lib.ui.DateTimeField.superclass.initComponent.call(this);
+               
+               // create DateField
+               var dateConfig = Ext.apply({},
+               {
+                       id: this.id + '-date'
+                       ,
+                       format: this.dateFormat || Ext.form.DateField.prototype.format
+                       ,
+                       width: this.timeWidth
+                       ,
+                       selectOnFocus: this.selectOnFocus
+                       ,
+                       validator: this.dateValidator
+                       ,
+                       listeners: {
+                               blur: {
+                                       scope: this,
+                                       fn: this.onBlur
+                               }
+                               ,
+                               focus: {
+                                       scope: this,
+                                       fn: this.onFocus
+                               }
+                       }
+               },
+               this.dateConfig);
+               this.df = new Ext.form.DateField(dateConfig);
+               this.df.ownerCt = this;
+               delete(this.dateFormat);
+               
+               // create TimeField
+               var timeConfig = Ext.apply({},
+               {
+                       id: this.id + '-time'
+                       ,
+                       format: this.timeFormat || Ext.form.TimeField.prototype.format
+                       ,
+                       width: this.timeWidth
+                       ,
+                       selectOnFocus: this.selectOnFocus
+                       ,
+                       validator: this.timeValidator
+                       ,
+                       listeners: {
+                               blur: {
+                                       scope: this,
+                                       fn: this.onBlur
+                               }
+                               ,
+                               focus: {
+                                       scope: this,
+                                       fn: this.onFocus
+                               }
+                       }
+               },
+               this.timeConfig);
+               this.tf = new Ext.form.TimeField(timeConfig);
+               this.tf.ownerCt = this;
+               delete(this.timeFormat);
+               
+               // relay events
+               this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
+               this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);
+               
+               this.on('specialkey', this.onSpecialKey, this);
+               
+       }
+       // eo function initComponent
+       // }}}
+       // {{{
+       /**
+        * @private
+        * Renders underlying DateField and TimeField and provides a workaround for side error icon bug
+        */
+       ,
+       onRender: function(ct, position) {
+               // don't run more than once
+               if (this.isRendered) {
+                       return;
+               }
+               
+               // render underlying hidden field
+               Gilbert.lib.ui.DateTimeField.superclass.onRender.call(this, ct, position);
+               
+               // render DateField and TimeField
+               // create bounding table
+               var t;
+               if ('below' === this.timePosition) {
+                       t = Ext.DomHelper.append(ct, {
+                               tag: 'table',
+                               style: 'border-collapse:collapse',
+                               children: [
+                               {
+                                       tag: 'tr',
+                                       children: [{
+                                               tag: 'td',
+                                               style: 'padding-bottom:1px',
+                                               cls: 'ux-datetime-date'
+                                       }]
+                               }
+                               ,
+                               {
+                                       tag: 'tr',
+                                       children: [{
+                                               tag: 'td',
+                                               cls: 'ux-datetime-time'
+                                       }]
+                               }
+                               ]
+                       },
+                       true);
+               }
+               else {
+                       t = Ext.DomHelper.append(ct, {
+                               tag: 'table',
+                               style: 'border-collapse:collapse',
+                               children: [
+                               {
+                                       tag: 'tr',
+                                       children: [
+                                       {
+                                               tag: 'td',
+                                               style: 'padding-right:4px',
+                                               cls: 'ux-datetime-date'
+                                       },
+                                       {
+                                               tag: 'td',
+                                               cls: 'ux-datetime-time'
+                                       }
+                                       ]
+                               }
+                               ]
+                       },
+                       true);
+               }
+               
+               this.tableEl = t;
+               this.wrap = t.wrap({
+                       cls: 'x-form-field-wrap'
+               });
+               //                this.wrap = t.wrap();
+               this.wrap.on("mousedown", this.onMouseDown, this, {
+                       delay: 10
+               });
+               
+               // render DateField & TimeField
+               this.df.render(t.child('td.ux-datetime-date'));
+               this.tf.render(t.child('td.ux-datetime-time'));
+               
+               // workaround for IE trigger misalignment bug
+               // see http://extjs.com/forum/showthread.php?p=341075#post341075
+               //                if(Ext.isIE && Ext.isStrict) {
+               //                        t.select('input').applyStyles({top:0});
+               //                }
+               this.df.el.swallowEvent(['keydown', 'keypress']);
+               this.tf.el.swallowEvent(['keydown', 'keypress']);
+               
+               // create icon for side invalid errorIcon
+               if ('side' === this.msgTarget) {
+                       var elp = this.el.findParent('.x-form-element', 10, true);
+                       if (elp) {
+                               this.errorIcon = elp.createChild({
+                                       cls: 'x-form-invalid-icon'
+                               });
+                       }
+                       
+                       var o = {
+                               errorIcon: this.errorIcon
+                               ,
+                               msgTarget: 'side'
+                               ,
+                               alignErrorIcon: this.alignErrorIcon.createDelegate(this)
+                       };
+                       Ext.apply(this.df, o);
+                       Ext.apply(this.tf, o);
+                       //                        this.df.errorIcon = this.errorIcon;
+                       //                        this.tf.errorIcon = this.errorIcon;
+               }
+               
+               // setup name for submit
+               this.el.dom.name = this.hiddenName || this.name || this.id;
+               
+               // prevent helper fields from being submitted
+               this.df.el.dom.removeAttribute("name");
+               this.tf.el.dom.removeAttribute("name");
+               
+               // we're rendered flag
+               this.isRendered = true;
+               
+               // update hidden field
+               this.updateHidden();
+               
+       }
+       // eo function onRender
+       // }}}
+       // {{{
+       /**
+        * @private
+        */
+       ,
+       adjustSize: Ext.BoxComponent.prototype.adjustSize
+       // }}}
+       // {{{
+       /**
+        * @private
+        */
+       ,
+       alignErrorIcon: function() {
+               this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
+       }
+       // }}}
+       // {{{
+       /**
+        * @private initializes internal dateValue
+        */
+       ,
+       initDateValue: function() {
+               this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
+       }
+       // }}}
+       // {{{
+       /**
+        * Calls clearInvalid on the DateField and TimeField
+        */
+       ,
+       clearInvalid: function() {
+               this.df.clearInvalid();
+               this.tf.clearInvalid();
+       }
+       // eo function clearInvalid
+       // }}}
+       // {{{
+       /**
+        * Calls markInvalid on both DateField and TimeField
+        * @param {String} msg Invalid message to display
+        */
+       ,
+       markInvalid: function(msg) {
+               this.df.markInvalid(msg);
+               this.tf.markInvalid(msg);
+       }
+       // eo function markInvalid
+       // }}}
+       // {{{
+       /**
+        * @private
+        * called from Component::destroy. 
+        * Destroys all elements and removes all listeners we've created.
+        */
+       ,
+       beforeDestroy: function() {
+               if (this.isRendered) {
+                       //                        this.removeAllListeners();
+                       this.wrap.removeAllListeners();
+                       this.wrap.remove();
+                       this.tableEl.remove();
+                       this.df.destroy();
+                       this.tf.destroy();
+               }
+       }
+       // eo function beforeDestroy
+       // }}}
+       // {{{
+       /**
+        * Disable this component.
+        * @return {Ext.Component} this
+        */
+       ,
+       disable: function() {
+               if (this.isRendered) {
+                       this.df.disabled = this.disabled;
+                       this.df.onDisable();
+                       this.tf.onDisable();
+               }
+               this.disabled = true;
+               this.df.disabled = true;
+               this.tf.disabled = true;
+               this.fireEvent("disable", this);
+               return this;
+       }
+       // eo function disable
+       // }}}
+       // {{{
+       /**
+        * Enable this component.
+        * @return {Ext.Component} this
+        */
+       ,
+       enable: function() {
+               if (this.rendered) {
+                       this.df.onEnable();
+                       this.tf.onEnable();
+               }
+               this.disabled = false;
+               this.df.disabled = false;
+               this.tf.disabled = false;
+               this.fireEvent("enable", this);
+               return this;
+       }
+       // eo function enable
+       // }}}
+       // {{{
+       /**
+        * @private Focus date filed
+        */
+       ,
+       focus: function() {
+               this.df.focus();
+       }
+       // eo function focus
+       // }}}
+       // {{{
+       /**
+        * @private
+        */
+       ,
+       getPositionEl: function() {
+               return this.wrap;
+       }
+       // }}}
+       // {{{
+       /**
+        * @private
+        */
+       ,
+       getResizeEl: function() {
+               return this.wrap;
+       }
+       // }}}
+       // {{{
+       /**
+        * @return {Date/String} Returns value of this field
+        */
+       ,
+       getValue: function() {
+               // create new instance of date
+               return this.dateValue ? new Date(this.dateValue) : '';
+       }
+       // eo function getValue
+       // }}}
+       // {{{
+       /**
+        * @return {Boolean} true = valid, false = invalid
+        * @private Calls isValid methods of underlying DateField and TimeField and returns the result
+        */
+       ,
+       isValid: function() {
+               var valid = true;
+               var msg = "Both fields must be supplied."
+               if (!(this.df.isValid() && this.tf.isValid())) valid = false;
+               if (Boolean(this.df.getValue()) != Boolean(this.tf.getValue())) {
+                       valid = false;
+                       if (!this.df.getValue()){
+                               if (!(this.tf.hasFocus || this.df.hasFocus)) this.df.markInvalid(msg);
+                       } else {
+                               if (!(this.tf.hasFocus || this.df.hasFocus)) this.tf.markInvalid(msg);
+                       };
+               };
+               return valid;
+       }
+       // eo function isValid
+       // }}}
+       // {{{
+       /**
+        * Returns true if this component is visible
+        * @return {boolean} 
+        */
+       ,
+       isVisible: function() {
+               return this.df.rendered && this.df.getActionEl().isVisible();
+       }
+       // eo function isVisible
+       // }}}
+       // {{{
+       /** 
+        * @private Handles blur event
+        */
+       ,
+       onBlur: function(f) {
+               // called by both DateField and TimeField blur events
+               // revert focus to previous field if clicked in between
+               if (this.wrapClick) {
+                       f.focus();
+                       this.wrapClick = false;
+               }
+               
+               // update underlying value
+               if (f === this.df) {
+                       this.updateDate();
+               }
+               else {
+                       this.updateTime();
+               }
+               this.updateHidden();
+               
+               this.validate();
+               
+               // fire events later
+               (function() {
+                       if (!this.df.hasFocus && !this.tf.hasFocus) {
+                               var v = this.getValue();
+                               if (String(v) !== String(this.startValue)) {
+                                       this.fireEvent("change", this, v, this.startValue);
+                               }
+                               this.hasFocus = false;
+                               this.fireEvent('blur', this);
+                       }
+               }).defer(100, this);
+               
+       }
+       // eo function onBlur
+       // }}}
+       // {{{
+       /**
+        * @private Handles focus event
+        */
+       ,
+       onFocus: function() {
+               if (!this.hasFocus) {
+                       this.hasFocus = true;
+                       this.startValue = this.getValue();
+                       this.fireEvent("focus", this);
+               }
+       }
+       // }}}
+       // {{{
+       /**
+        * @private Just to prevent blur event when clicked in the middle of fields
+        */
+       ,
+       onMouseDown: function(e) {
+               if (!this.disabled) {
+                       this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
+               }
+       }
+       // }}}
+       // {{{
+       /**
+        * @private
+        * Handles Tab and Shift-Tab events
+        */
+       ,
+       onSpecialKey: function(t, e) {
+               var key = e.getKey();
+               if (key === e.TAB) {
+                       if (t === this.df && !e.shiftKey) {
+                               e.stopEvent();
+                               this.tf.focus();
+                       }
+                       if (t === this.tf && e.shiftKey) {
+                               e.stopEvent();
+                               this.df.focus();
+                       }
+                       this.updateValue();
+               }
+               // otherwise it misbehaves in editor grid
+               if (key === e.ENTER) {
+                       this.updateValue();
+               }
+               
+       }
+       // eo function onSpecialKey
+       // }}}
+       // {{{
+       /**
+        * Resets the current field value to the originally loaded value 
+        * and clears any validation messages. See Ext.form.BasicForm.trackResetOnLoad
+        */
+       ,
+       reset: function() {
+               this.df.setValue(this.originalValue);
+               this.tf.setValue(this.originalValue);
+       }
+       // eo function reset
+       // }}}
+       // {{{
+       /**
+        * @private Sets the value of DateField
+        */
+       ,
+       setDate: function(date) {
+               this.df.setValue(date);
+       }
+       // eo function setDate
+       // }}}
+       // {{{
+       /** 
+        * @private Sets the value of TimeField
+        */
+       ,
+       setTime: function(date) {
+               this.tf.setValue(date);
+       }
+       // eo function setTime
+       // }}}
+       // {{{
+       /**
+        * @private
+        * Sets correct sizes of underlying DateField and TimeField
+        * With workarounds for IE bugs
+        */
+       ,
+       setSize: function(w, h) {
+               if (!w) {
+                       return;
+               }
+               if ('below' === this.timePosition) {
+                       this.df.setSize(w, h);
+                       this.tf.setSize(w, h);
+                       if (Ext.isIE) {
+                               this.df.el.up('td').setWidth(w);
+                               this.tf.el.up('td').setWidth(w);
+                       }
+               }
+               else {
+                       this.df.setSize(w - this.timeWidth - 4, h);
+                       this.tf.setSize(this.timeWidth, h);
+                       
+                       if (Ext.isIE) {
+                               this.df.el.up('td').setWidth(w - this.timeWidth - 4);
+                               this.tf.el.up('td').setWidth(this.timeWidth);
+                       }
+               }
+       }
+       // eo function setSize
+       // }}}
+       // {{{
+       /**
+        * @param {Mixed} val Value to set
+        * Sets the value of this field
+        */
+       ,
+       setValue: function(val) {
+               if (!val && true === this.emptyToNow) {
+                       this.setValue(new Date());
+                       return;
+               }
+               else if (!val) {
+                       this.setDate('');
+                       this.setTime('');
+                       this.updateValue();
+                       return;
+               }
+               if ('number' === typeof val) {
+                       val = new Date(val);
+               }
+               else if ('string' === typeof val && this.hiddenFormat) {
+                       val = Date.parseDate(val, this.hiddenFormat);
+               }
+               val = val ? val: new Date(1970, 0, 1, 0, 0, 0);
+               var da;
+               if (val instanceof Date) {
+                       this.setDate(val);
+                       this.setTime(val);
+                       this.dateValue = new Date(Ext.isIE ? val.getTime() : val);
+               }
+               else {
+                       da = val.split(this.dtSeparator);
+                       this.setDate(da[0]);
+                       if (da[1]) {
+                               if (da[2]) {
+                                       // add am/pm part back to time
+                                       da[1] += da[2];
+                               }
+                               this.setTime(da[1]);
+                       }
+               }
+               this.updateValue();
+       }
+       // eo function setValue
+       // }}}
+       // {{{
+       /**
+        * Hide or show this component by boolean
+        * @return {Ext.Component} this
+        */
+       ,
+       setVisible: function(visible) {
+               if (visible) {
+                       this.df.show();
+                       this.tf.show();
+               } else {
+                       this.df.hide();
+                       this.tf.hide();
+               }
+               return this;
+       }
+       // eo function setVisible
+       // }}}
+       //{{{
+       ,
+       show: function() {
+               return this.setVisible(true);
+       }
+       // eo function show
+       //}}}
+       //{{{
+       ,
+       hide: function() {
+               return this.setVisible(false);
+       }
+       // eo function hide
+       //}}}
+       // {{{
+       /**
+        * @private Updates the date part
+        */
+       ,
+       updateDate: function() {
+               
+               var d = this.df.getValue();
+               if (d) {
+                       if (! (this.dateValue instanceof Date)) {
+                               this.initDateValue();
+                       }
+                       this.dateValue.setMonth(0);
+                       // because of leap years
+                       this.dateValue.setFullYear(d.getFullYear());
+                       this.dateValue.setMonth(d.getMonth(), d.getDate());
+                       //                        this.dateValue.setDate(d.getDate());
+               }
+               else {
+                       this.dateValue = '';
+               }
+       }
+       // eo function updateDate
+       // }}}
+       // {{{
+       /**
+        * @private
+        * Updates the time part
+        */
+       ,
+       updateTime: function() {
+               var t = this.tf.getValue();
+               if (t && !(t instanceof Date)) {
+                       t = Date.parseDate(t, this.tf.format);
+               }
+               if (t && !this.df.getValue()) {
+                       this.initDateValue();
+               }
+               if (this.dateValue instanceof Date) {
+                       if (t) {
+                               this.dateValue.setHours(t.getHours());
+                               this.dateValue.setMinutes(t.getMinutes());
+                               this.dateValue.setSeconds(t.getSeconds());
+                       }
+                       else {
+                               this.dateValue.setHours(0);
+                               this.dateValue.setMinutes(0);
+                               this.dateValue.setSeconds(0);
+                       }
+               }
+       }
+       // eo function updateTime
+       // }}}
+       // {{{
+       /**
+        * @private Updates the underlying hidden field value
+        */
+       ,
+       updateHidden: function() {
+               if (this.isRendered) {
+                       var value = this.dateValue instanceof Date ? this.dateValue.format(this.hiddenFormat) : '';
+                       this.el.dom.value = value;
+               }
+       }
+       // }}}
+       // {{{
+       /**
+        * @private Updates all of Date, Time and Hidden
+        */
+       ,
+       updateValue: function() {
+               
+               this.updateDate();
+               this.updateTime();
+               this.updateHidden();
+               
+               return;
+       }
+       // eo function updateValue
+       // }}}
+       // {{{
+       /**
+        * @return {Boolean} true = valid, false = invalid
+        * calls validate methods of DateField and TimeField
+        */
+       ,
+       validate: function() {
+               var valid = true;
+               var msg = "Both fields must be supplied."
+               if (!(this.df.validate() && this.tf.validate())) valid = false;
+               if (Boolean(this.df.getValue()) != Boolean(this.tf.getValue())) {
+                       valid = false;
+                       if (!this.df.getValue()){
+                               if (!(this.tf.hasFocus || this.df.hasFocus)) this.df.markInvalid(msg);
+                       } else {
+                               if (!(this.tf.hasFocus || this.df.hasFocus)) this.tf.markInvalid(msg);
+                       };
+               };
+               return valid;
+       }
+       // eo function validate
+       // }}}
+       // {{{
+       /**
+        * Returns renderer suitable to render this field
+        * @param {Object} Column model config
+        */
+       ,
+       renderer: function(field) {
+               var format = field.editor.dateFormat || Gilbert.lib.ui.DateTimeField.prototype.dateFormat;
+               format += ' ' + (field.editor.timeFormat || Gilbert.lib.ui.DateTimeField.prototype.timeFormat);
+               var renderer = function(val) {
+                       var retval = Ext.util.Format.date(val, format);
+                       return retval;
+               };
+               return renderer;
+       }
+       // eo function renderer
+       // }}}
+});
+// eo extend
+// register xtype
+Ext.reg('gilbertdatetimefield', Gilbert.lib.ui.DateTimeField);
diff --git a/philo/contrib/gilbert/static/gilbert/lib/ui/ui.js b/philo/contrib/gilbert/static/gilbert/lib/ui/ui.js
new file mode 100644 (file)
index 0000000..9abe372
--- /dev/null
@@ -0,0 +1,44 @@
+Ext.ns('Gilbert.lib.ui');
+
+
+Gilbert.lib.ui.DjangoForm = Ext.extend(Ext.FormPanel, {
+       initComponent: function () {
+               /*if (this.djangoFields) {
+                       this.initDjangoForm();
+               }*/
+               Gilbert.lib.ui.DjangoForm.superclass.initComponent.call(this);
+       },
+/*     initDjangoForm: function () {
+               this.items = this.items || [];
+               Ext.each(this.djangoFields, this.addDjangoField, this);
+       },
+       addDjangoField: function(field, index, all) {
+               this.items.push(Gilbert.lib.ui.DjangoFormHelper.get_field_converter(field.type)(field));
+       },*/
+});
+
+
+Gilbert.lib.ui.HTMLWindow = Ext.extend(Ext.Window, {
+       html_source: undefined,
+       onRender: function() {
+               if (this.html_source) {
+                       this.bodyCfg = {
+                               tag: 'iframe',
+                               cls: this.bodyCls,
+                       };
+                       Gilbert.lib.ui.HTMLWindow.superclass.onRender.apply(this, arguments);
+                       var iframe = this.body.dom;
+                       var doc = iframe.document;
+                       if (iframe.contentDocument) {
+                               doc = iframe.contentDocument;
+                       } else if (iframe.contentWindow) {
+                               doc = iframe.contentWindow.document;
+                       }
+                       doc.open();
+                       doc.writeln(this.html_source);
+                       doc.close();
+               } else {
+                       Gilbert.lib.ui.HTMLWindow.superclass.onRender.apply(this, arguments);
+               }
+       }
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/lib/ui/windows.js b/philo/contrib/gilbert/static/gilbert/lib/ui/windows.js
new file mode 100644 (file)
index 0000000..f60eb11
--- /dev/null
@@ -0,0 +1,41 @@
+Gilbert.lib.ui.DjangoForm = Ext.extend(Ext.FormPanel, {
+       initComponent: function () {
+               /*if (this.djangoFields) {
+                       this.initDjangoForm();
+               }*/
+               Gilbert.lib.ui.DjangoForm.superclass.initComponent.call(this);
+       },
+/*     initDjangoForm: function () {
+               this.items = this.items || [];
+               Ext.each(this.djangoFields, this.addDjangoField, this);
+       },
+       addDjangoField: function(field, index, all) {
+               this.items.push(Gilbert.lib.ui.DjangoFormHelper.get_field_converter(field.type)(field));
+       },*/
+});
+
+
+Gilbert.lib.ui.HTMLWindow = Ext.extend(Ext.Window, {
+       html_source: undefined,
+       onRender: function() {
+               if (this.html_source) {
+                       this.bodyCfg = {
+                               tag: 'iframe',
+                               cls: this.bodyCls,
+                       };
+                       Gilbert.lib.ui.HTMLWindow.superclass.onRender.apply(this, arguments);
+                       var iframe = this.body.dom;
+                       var doc = iframe.document;
+                       if (iframe.contentDocument) {
+                               doc = iframe.contentDocument;
+                       } else if (iframe.contentWindow) {
+                               doc = iframe.contentWindow.document;
+                       }
+                       doc.open();
+                       doc.writeln(this.html_source);
+                       doc.close();
+               } else {
+                       Gilbert.lib.ui.HTMLWindow.superclass.onRender.apply(this, arguments);
+               }
+       }
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/arrv.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/arrv.png
new file mode 100644 (file)
index 0000000..66fdce0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/arrv.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Down.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Down.png
new file mode 100644 (file)
index 0000000..d0225fb
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Down.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Normal.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Normal.png
new file mode 100644 (file)
index 0000000..42e9981
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Normal.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Over.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Over.png
new file mode 100644 (file)
index 0000000..a5a716d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Over.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Pressed.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Pressed.png
new file mode 100644 (file)
index 0000000..aa168a3
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/c/Pressed.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/left-over.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/left-over.png
new file mode 100644 (file)
index 0000000..7ef8f67
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/left-over.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/left.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/left.png
new file mode 100644 (file)
index 0000000..b0dc68c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/left.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/menu.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/menu.png
new file mode 100644 (file)
index 0000000..4993084
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/menu.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/right-over.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/right-over.png
new file mode 100644 (file)
index 0000000..2cb9783
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/right-over.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/right.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/right.png
new file mode 100644 (file)
index 0000000..4e68d26
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/right.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/split.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/split.png
new file mode 100644 (file)
index 0000000..a5402b7
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/split.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/splith.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/splith.png
new file mode 100644 (file)
index 0000000..a5402b7
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/splith.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/splitv.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/splitv.png
new file mode 100644 (file)
index 0000000..82970e9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/splitv.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Down.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Down.png
new file mode 100644 (file)
index 0000000..c55c6f0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Down.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Normal.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Normal.png
new file mode 100644 (file)
index 0000000..d4756a4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Normal.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Over.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Over.png
new file mode 100644 (file)
index 0000000..fb22b68
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Over.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Pressed.png b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Pressed.png
new file mode 100644 (file)
index 0000000..153be02
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/btn/whole/Pressed.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/fills/30b.png b/philo/contrib/gilbert/static/gilbert/murano/images/fills/30b.png
new file mode 100644 (file)
index 0000000..f030b53
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/fills/30b.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/fills/30w.png b/philo/contrib/gilbert/static/gilbert/murano/images/fills/30w.png
new file mode 100644 (file)
index 0000000..494c8bb
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/fills/30w.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/fills/70w.png b/philo/contrib/gilbert/static/gilbert/murano/images/fills/70w.png
new file mode 100644 (file)
index 0000000..b9fa366
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/fills/70w.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/fills/80b.png b/philo/contrib/gilbert/static/gilbert/murano/images/fills/80b.png
new file mode 100644 (file)
index 0000000..8a6b4eb
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/fills/80b.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/form/checked.png b/philo/contrib/gilbert/static/gilbert/murano/images/form/checked.png
new file mode 100644 (file)
index 0000000..f22d0ff
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/form/checked.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/form/field.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/form/field.opacity
new file mode 100644 (file)
index 0000000..62576dd
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/form/field.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/form/field.png b/philo/contrib/gilbert/static/gilbert/murano/images/form/field.png
new file mode 100644 (file)
index 0000000..33767b4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/form/field.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/form/radioed.png b/philo/contrib/gilbert/static/gilbert/murano/images/form/radioed.png
new file mode 100644 (file)
index 0000000..1db47e8
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/form/radioed.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/form/unchecked.png b/philo/contrib/gilbert/static/gilbert/murano/images/form/unchecked.png
new file mode 100644 (file)
index 0000000..259deac
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/form/unchecked.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/form/unradioed.png b/philo/contrib/gilbert/static/gilbert/murano/images/form/unradioed.png
new file mode 100644 (file)
index 0000000..6db37df
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/form/unradioed.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.opacity
new file mode 100644 (file)
index 0000000..e0425c7
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.png b/philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.png
new file mode 100644 (file)
index 0000000..af1ec9b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/gloss/gloss.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/gloss/light-gloss.png b/philo/contrib/gilbert/static/gilbert/murano/images/gloss/light-gloss.png
new file mode 100644 (file)
index 0000000..e9444e5
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/gloss/light-gloss.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/gradients/30white-to-trans.png b/philo/contrib/gilbert/static/gilbert/murano/images/gradients/30white-to-trans.png
new file mode 100644 (file)
index 0000000..cbc8ff2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/gradients/30white-to-trans.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/gradients/dark-inner-shadow.png b/philo/contrib/gilbert/static/gilbert/murano/images/gradients/dark-inner-shadow.png
new file mode 100644 (file)
index 0000000..0636daa
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/gradients/dark-inner-shadow.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-bottom.png b/philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-bottom.png
new file mode 100644 (file)
index 0000000..cab866d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-bottom.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-top.png b/philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-top.png
new file mode 100644 (file)
index 0000000..3de7621
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/grid/col-move-top.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/grid/row-editor-btns.png b/philo/contrib/gilbert/static/gilbert/murano/images/grid/row-editor-btns.png
new file mode 100644 (file)
index 0000000..a267203
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/grid/row-editor-btns.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/balloon.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/balloon.png
new file mode 100755 (executable)
index 0000000..dff9e47
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/balloon.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/cross.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/cross.png
new file mode 100755 (executable)
index 0000000..e559d44
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/cross.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-first.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-first.png
new file mode 100644 (file)
index 0000000..62bb6b6
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-first.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-last.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-last.png
new file mode 100644 (file)
index 0000000..a45d9d2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-last.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-next.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-next.png
new file mode 100644 (file)
index 0000000..669df8b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-next.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-prev.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-prev.png
new file mode 100644 (file)
index 0000000..e8f4683
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-prev.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-refresh.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-refresh.png
new file mode 100644 (file)
index 0000000..d18dbf9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/d-refresh.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation.png
new file mode 100644 (file)
index 0000000..c088a26
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation32.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation32.png
new file mode 100755 (executable)
index 0000000..51fde5a
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/exclamation32.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/first.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/first.png
new file mode 100755 (executable)
index 0000000..a6930a1
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/first.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/information.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/information.png
new file mode 100755 (executable)
index 0000000..01184cc
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/information.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/last.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/last.png
new file mode 100755 (executable)
index 0000000..8c63819
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/last.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/next.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/next.png
new file mode 100755 (executable)
index 0000000..195a9d7
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/next.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/no.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/no.png
new file mode 100755 (executable)
index 0000000..1f33a1b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/no.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/prev.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/prev.png
new file mode 100755 (executable)
index 0000000..79558ab
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/prev.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/refresh.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/refresh.png
new file mode 100755 (executable)
index 0000000..1c56055
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/refresh.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/icons/yes.png b/philo/contrib/gilbert/static/gilbert/murano/images/icons/yes.png
new file mode 100755 (executable)
index 0000000..46c0a29
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/icons/yes.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/az.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/az.png
new file mode 100644 (file)
index 0000000..60ecb93
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/az.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/cols.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/cols.png
new file mode 100644 (file)
index 0000000..4393cca
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/cols.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/header.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/header.png
new file mode 100644 (file)
index 0000000..bec2aca
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/header.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/headerdrop.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/headerdrop.png
new file mode 100644 (file)
index 0000000..fe001e8
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/headerdrop.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-asc.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-asc.png
new file mode 100644 (file)
index 0000000..d11b3e2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-asc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-desc.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-desc.png
new file mode 100644 (file)
index 0000000..627a7d9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/listview-sort-desc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-asc.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-asc.png
new file mode 100644 (file)
index 0000000..c77105b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-asc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-desc.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-desc.png
new file mode 100644 (file)
index 0000000..75e171e
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/sort-desc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/listview/za.png b/philo/contrib/gilbert/static/gilbert/murano/images/listview/za.png
new file mode 100644 (file)
index 0000000..0c2333d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/listview/za.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/load.gif b/philo/contrib/gilbert/static/gilbert/murano/images/load.gif
new file mode 100644 (file)
index 0000000..5fe9ccf
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/load.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.opacity
new file mode 100644 (file)
index 0000000..ef80ff9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.png b/philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.png
new file mode 100644 (file)
index 0000000..f79ebbc
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/menu/arrow.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-bottom.png b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-bottom.png
new file mode 100644 (file)
index 0000000..11ae079
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-bottom.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-left.png b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-left.png
new file mode 100644 (file)
index 0000000..1fe6f7e
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-left.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-right.png b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-right.png
new file mode 100644 (file)
index 0000000..d090c0a
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-right.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-top.png b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-top.png
new file mode 100644 (file)
index 0000000..32f0de8
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/mini/mini-top.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/progress.png b/philo/contrib/gilbert/static/gilbert/murano/images/progress.png
new file mode 100644 (file)
index 0000000..d55db12
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/progress.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/e.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/e.png
new file mode 100644 (file)
index 0000000..1d4d6d4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/e.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/n.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/n.png
new file mode 100644 (file)
index 0000000..6ac64c9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/n.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/ne.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/ne.png
new file mode 100644 (file)
index 0000000..c57ab8e
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/ne.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/nw.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/nw.png
new file mode 100644 (file)
index 0000000..52262a7
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/nw.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/s.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/s.png
new file mode 100644 (file)
index 0000000..6ac64c9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/s.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/se.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/se.png
new file mode 100644 (file)
index 0000000..109cf8d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/se.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/sw.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/sw.png
new file mode 100644 (file)
index 0000000..9b91ce1
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/sw.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/resizable/w.png b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/w.png
new file mode 100644 (file)
index 0000000..1d4d6d4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/resizable/w.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/scroller/l.png b/philo/contrib/gilbert/static/gilbert/murano/images/scroller/l.png
new file mode 100644 (file)
index 0000000..5c43d0d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/scroller/l.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/scroller/r.png b/philo/contrib/gilbert/static/gilbert/murano/images/scroller/r.png
new file mode 100644 (file)
index 0000000..c6111ef
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/scroller/r.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/slider/h.png b/philo/contrib/gilbert/static/gilbert/murano/images/slider/h.png
new file mode 100644 (file)
index 0000000..5ce04c0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/slider/h.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbh.png b/philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbh.png
new file mode 100644 (file)
index 0000000..9c11f82
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbh.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbv.png b/philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbv.png
new file mode 100644 (file)
index 0000000..9c7419c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/slider/thumbv.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/slider/v.png b/philo/contrib/gilbert/static/gilbert/murano/images/slider/v.png
new file mode 100644 (file)
index 0000000..72c816c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/slider/v.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/30-b.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/30-b.opacity
new file mode 100644 (file)
index 0000000..869cec5
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/30-b.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/30-w.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/30-w.opacity
new file mode 100644 (file)
index 0000000..c2ce599
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/30-w.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/30w.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/30w.opacity
new file mode 100644 (file)
index 0000000..35b9066
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/30w.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/30white-to-trans.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/30white-to-trans.opacity
new file mode 100644 (file)
index 0000000..a3ceb55
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/30white-to-trans.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/70w.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/70w.opacity
new file mode 100644 (file)
index 0000000..718ca1d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/70w.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/80-b.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/80-b.opacity
new file mode 100644 (file)
index 0000000..d4c7378
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/80-b.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/?.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/?.opacity
new file mode 100644 (file)
index 0000000..9e51720
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/?.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/arrow buttons.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/arrow buttons.opacity
new file mode 100644 (file)
index 0000000..1dc866d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/arrow buttons.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/btn.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/btn.opacity
new file mode 100644 (file)
index 0000000..79387cb
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/btn.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/check.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/check.opacity
new file mode 100644 (file)
index 0000000..9835a54
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/check.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-bottom.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-bottom.opacity
new file mode 100644 (file)
index 0000000..8d5a7e0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-bottom.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-top.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-top.opacity
new file mode 100644 (file)
index 0000000..62b579e
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/col-move-top.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/dark-inner-shadow.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/dark-inner-shadow.opacity
new file mode 100644 (file)
index 0000000..2422c28
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/dark-inner-shadow.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/exclamation.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/exclamation.opacity
new file mode 100644 (file)
index 0000000..09cfd0c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/exclamation.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/grad-height-25.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/grad-height-25.opacity
new file mode 100644 (file)
index 0000000..4a16036
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/grad-height-25.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/h.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/h.opacity
new file mode 100644 (file)
index 0000000..cb738c5
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/h.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/light-gloss.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/light-gloss.opacity
new file mode 100644 (file)
index 0000000..0dd03ec
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/light-gloss.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/listview-sort.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/listview-sort.opacity
new file mode 100644 (file)
index 0000000..ab60087
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/listview-sort.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/listviewheader.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/listviewheader.opacity
new file mode 100644 (file)
index 0000000..853b4d8
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/listviewheader.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/mini.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/mini.opacity
new file mode 100644 (file)
index 0000000..3ba2b37
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/mini.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/progress.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/progress.opacity
new file mode 100644 (file)
index 0000000..81d4904
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/progress.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/resizable corner.psd b/philo/contrib/gilbert/static/gilbert/murano/images/src/resizable corner.psd
new file mode 100644 (file)
index 0000000..822db29
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/resizable corner.psd differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/resizable long.psd b/philo/contrib/gilbert/static/gilbert/murano/images/src/resizable long.psd
new file mode 100644 (file)
index 0000000..074b076
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/resizable long.psd differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/row-editor-btns.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/row-editor-btns.opacity
new file mode 100644 (file)
index 0000000..040f298
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/row-editor-btns.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/scroller.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/scroller.opacity
new file mode 100644 (file)
index 0000000..6f9ee31
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/scroller.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/sort.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/sort.opacity
new file mode 100644 (file)
index 0000000..e33424c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/sort.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/tabs.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/tabs.opacity
new file mode 100644 (file)
index 0000000..974441e
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/tabs.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/tb-sprite.psd b/philo/contrib/gilbert/static/gilbert/murano/images/src/tb-sprite.psd
new file mode 100644 (file)
index 0000000..9f0ec67
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/tb-sprite.psd differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/tb.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/tb.opacity
new file mode 100644 (file)
index 0000000..d307ca0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/tb.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/thumbh.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/thumbh.opacity
new file mode 100644 (file)
index 0000000..6909c40
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/thumbh.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/thumbv.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/thumbv.opacity
new file mode 100644 (file)
index 0000000..64c1a5c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/thumbv.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/tools.psd b/philo/contrib/gilbert/static/gilbert/murano/images/src/tools.psd
new file mode 100644 (file)
index 0000000..a69c09f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/tools.psd differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip-invalid.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip-invalid.opacity
new file mode 100644 (file)
index 0000000..dcfcb81
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip-invalid.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip.opacity
new file mode 100644 (file)
index 0000000..0264045
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/tooltip.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.opacity
new file mode 100644 (file)
index 0000000..2f124fb
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.png b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.png
new file mode 100644 (file)
index 0000000..983fdff
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/blank.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/clear.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/clear.opacity
new file mode 100644 (file)
index 0000000..a51a849
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/clear.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/date.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/date.opacity
new file mode 100644 (file)
index 0000000..ce54557
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/date.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/drop.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/drop.opacity
new file mode 100644 (file)
index 0000000..f7065e1
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/drop.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/search.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/search.opacity
new file mode 100644 (file)
index 0000000..01c7e9b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/triggers/search.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/v.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/v.opacity
new file mode 100644 (file)
index 0000000..81a092d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/v.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/window-dark.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/window-dark.opacity
new file mode 100644 (file)
index 0000000..1d4cde2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/window-dark.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/window.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/window.opacity
new file mode 100644 (file)
index 0000000..644311c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/window.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/src/x.opacity b/philo/contrib/gilbert/static/gilbert/murano/images/src/x.opacity
new file mode 100644 (file)
index 0000000..da05b1f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/src/x.opacity differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Bottom.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Bottom.png
new file mode 100644 (file)
index 0000000..f12c35b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Bottom.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Top.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Top.png
new file mode 100644 (file)
index 0000000..0d9880c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/c/Top.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/close.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/close.png
new file mode 100644 (file)
index 0000000..7139a96
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/close.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Bottom.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Bottom.png
new file mode 100644 (file)
index 0000000..1a6f7c0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Bottom.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Top.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Top.png
new file mode 100644 (file)
index 0000000..9bbe709
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/l/Top.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Bottom.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Bottom.png
new file mode 100644 (file)
index 0000000..0e16744
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Bottom.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Top.png b/philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Top.png
new file mode 100644 (file)
index 0000000..85ea39d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tab/r/Top.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tb-sprite.png b/philo/contrib/gilbert/static/gilbert/murano/images/tb-sprite.png
new file mode 100644 (file)
index 0000000..d4e4c53
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tb-sprite.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tb.png b/philo/contrib/gilbert/static/gilbert/murano/images/tb.png
new file mode 100644 (file)
index 0000000..01a80d9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tb.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tools.png b/philo/contrib/gilbert/static/gilbert/murano/images/tools.png
new file mode 100644 (file)
index 0000000..53540f0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tools.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/arrows.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/arrows.gif
new file mode 100644 (file)
index 0000000..2683463
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/arrows.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-add.png b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-add.png
new file mode 100755 (executable)
index 0000000..49746a1
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-add.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-between.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-between.gif
new file mode 100644 (file)
index 0000000..5c6c09d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-between.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-no.png b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-no.png
new file mode 100755 (executable)
index 0000000..1f33a1b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-no.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-over.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-over.gif
new file mode 100644 (file)
index 0000000..30d1ca7
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-over.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-under.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-under.gif
new file mode 100644 (file)
index 0000000..85f66b1
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-under.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-yes.png b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-yes.png
new file mode 100755 (executable)
index 0000000..46c0a29
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/drop-yes.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus-nl.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus-nl.gif
new file mode 100644 (file)
index 0000000..0f2f2fd
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus-nl.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus.gif
new file mode 100644 (file)
index 0000000..e8995a9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-minus.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus-nl.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus-nl.gif
new file mode 100644 (file)
index 0000000..095afc4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus-nl.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus.gif
new file mode 100644 (file)
index 0000000..6049b9a
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end-plus.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end.gif
new file mode 100644 (file)
index 0000000..f24ddee
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-end.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-line.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-line.gif
new file mode 100644 (file)
index 0000000..75e6da4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-line.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus-nl.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus-nl.gif
new file mode 100644 (file)
index 0000000..0f2f2fd
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus-nl.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus.gif
new file mode 100644 (file)
index 0000000..df410a0
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-minus.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus-nl.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus-nl.gif
new file mode 100644 (file)
index 0000000..095afc4
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus-nl.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus.gif
new file mode 100644 (file)
index 0000000..4fe31a3
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow-plus.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow.gif
new file mode 100644 (file)
index 0000000..b8f4208
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/elbow.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/folder-open.png b/philo/contrib/gilbert/static/gilbert/murano/images/tree/folder-open.png
new file mode 100755 (executable)
index 0000000..dc05885
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/folder-open.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/folder.png b/philo/contrib/gilbert/static/gilbert/murano/images/tree/folder.png
new file mode 100755 (executable)
index 0000000..87ff120
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/folder.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/leaf.png b/philo/contrib/gilbert/static/gilbert/murano/images/tree/leaf.png
new file mode 100755 (executable)
index 0000000..8b17b3c
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/leaf.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/loading.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/loading.gif
new file mode 100644 (file)
index 0000000..5fe9ccf
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/loading.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tree/s.gif b/philo/contrib/gilbert/static/gilbert/murano/images/tree/s.gif
new file mode 100644 (file)
index 0000000..1d11fa9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tree/s.gif differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/trigger/blank.png b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/blank.png
new file mode 100644 (file)
index 0000000..165ba02
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/blank.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/trigger/clear.png b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/clear.png
new file mode 100644 (file)
index 0000000..a7c97bf
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/clear.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/trigger/date.png b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/date.png
new file mode 100644 (file)
index 0000000..6fdaa3f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/date.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/trigger/drop.png b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/drop.png
new file mode 100644 (file)
index 0000000..5a8a8b2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/drop.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/trigger/search.png b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/search.png
new file mode 100644 (file)
index 0000000..f878e02
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/trigger/search.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/bc.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/bc.png
new file mode 100644 (file)
index 0000000..f2022df
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/bc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/bl.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/bl.png
new file mode 100644 (file)
index 0000000..fad7b21
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/bl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/br.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/br.png
new file mode 100644 (file)
index 0000000..217491d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/br.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/c.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/c.png
new file mode 100644 (file)
index 0000000..70f3d1d
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/c.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bc.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bc.png
new file mode 100644 (file)
index 0000000..eb124df
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bl.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bl.png
new file mode 100644 (file)
index 0000000..7c7721f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-bl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-br.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-br.png
new file mode 100644 (file)
index 0000000..19aaad9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-br.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-c.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-c.png
new file mode 100644 (file)
index 0000000..b23e939
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-c.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-ml.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-ml.png
new file mode 100644 (file)
index 0000000..34d3f1a
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-ml.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-mr.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-mr.png
new file mode 100644 (file)
index 0000000..6dbacc3
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-mr.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tc.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tc.png
new file mode 100644 (file)
index 0000000..3b55ecf
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tl.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tl.png
new file mode 100644 (file)
index 0000000..12fa555
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tr.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tr.png
new file mode 100644 (file)
index 0000000..bc6e3bf
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/i-tr.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/ml.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/ml.png
new file mode 100644 (file)
index 0000000..4bebef2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/ml.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/mr.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/mr.png
new file mode 100644 (file)
index 0000000..23aebdd
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/mr.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/tc.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/tc.png
new file mode 100644 (file)
index 0000000..2108682
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/tc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/tl.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/tl.png
new file mode 100644 (file)
index 0000000..0d86302
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/tl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/tt/tr.png b/philo/contrib/gilbert/static/gilbert/murano/images/tt/tr.png
new file mode 100644 (file)
index 0000000..70d0cc9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/tt/tr.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bc.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bc.png
new file mode 100644 (file)
index 0000000..a2bfead
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bl.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bl.png
new file mode 100644 (file)
index 0000000..5a6ce5b
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/bl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/br.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/br.png
new file mode 100644 (file)
index 0000000..bbde5b2
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/br.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/c.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/c.png
new file mode 100644 (file)
index 0000000..8a6b4eb
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/c.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/l.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/l.png
new file mode 100644 (file)
index 0000000..af70f5f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/l.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/r.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/r.png
new file mode 100644 (file)
index 0000000..bf77670
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/r.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tc.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tc.png
new file mode 100644 (file)
index 0000000..8c65fb1
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tl.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tl.png
new file mode 100644 (file)
index 0000000..c253ea5
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tr.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tr.png
new file mode 100644 (file)
index 0000000..b371422
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowdark/tr.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bc.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bc.png
new file mode 100644 (file)
index 0000000..0ce7973
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bl.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bl.png
new file mode 100644 (file)
index 0000000..e2f5bbf
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/bl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/br.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/br.png
new file mode 100644 (file)
index 0000000..9ed3c34
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/br.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/c.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/c.png
new file mode 100644 (file)
index 0000000..bcf4ec9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/c.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/l.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/l.png
new file mode 100644 (file)
index 0000000..e85f76e
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/l.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/r.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/r.png
new file mode 100644 (file)
index 0000000..36cc2a3
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/r.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tc.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tc.png
new file mode 100644 (file)
index 0000000..ea82da9
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tc.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tl.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tl.png
new file mode 100644 (file)
index 0000000..ff1586f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tl.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tr.png b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tr.png
new file mode 100644 (file)
index 0000000..42087c6
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/murano/images/windowlight/tr.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/murano/murano.css b/philo/contrib/gilbert/static/gilbert/murano/murano.css
new file mode 100644 (file)
index 0000000..53c05d9
--- /dev/null
@@ -0,0 +1,1488 @@
+body{
+       background:#666;
+       color: #CCC;
+       font-family: Helvetica, Arial, sans-serif;
+       font-size: 12px;
+}
+
+/* kill the borders */
+.x-panel-tl, .x-toolbar-layout-ct, .x-panel-tbar, .x-toolbar, .x-panel-header, .x-panel-body,
+.x-window-tl,
+.x-window-body,
+.x-window-mc,
+.x-progress-bar,
+.x-tab-scrolling-top,
+.x-tab-panel-body,
+.x-tab-scroller-left, .x-tab-scroller-right,
+.x-tab-strip-spacer,
+.x-tab-panel-footer,
+.x-tab-strip,
+.x-list-header-inner em,
+.x-grid3-cell,.x-grid3-header, .x-grid3-header-offset,
+.x-date-prevday,
+.x-date-nextday,
+.x-tab-panel-header{
+       border: 0 !important;
+}
+
+/* kill stuff */
+.x-shadow{
+       display:none !important;
+}
+.x-tool, .x-date-mp .x-date-mp-ybtn .x-date-mp-prev, .x-date-mp .x-date-mp-ybtn .x-date-mp-next{
+       background-image:url(images/tools.png);
+}
+
+
+/* @group Basic Window */
+
+/* window header */
+       .x-window-tr, .x-panel-tr{
+               padding-right: 16px;
+               background: transparent url(images/windowlight/tr.png) no-repeat right top;
+       }
+       .x-window-tc, .x-panel-tc{
+               background: transparent url(images/windowlight/tc.png) repeat-x right top;
+               overflow:visible;
+       }
+       .x-window-tl, .x-panel-tl{
+               padding-left: 16px;
+               background: transparent url(images/windowlight/tl.png) no-repeat left top;
+               font-family: Optima;
+       }
+       .x-window-header-text, .x-panel-header-text{
+               color: #FFF;
+               text-shadow: 1px 1px 0 #000;
+               font-size: 12px;
+       }
+       .x-window-header, .x-window-tc, .x-panel-tc{
+       }
+       
+       .x-panel-tc > .x-panel-header, .x-window-tc > .x-window-header{
+               margin: 0 -10px;
+       }
+       /* don't go off the edge when a window is maximized. */
+       .x-window-maximized > .x-window-tl > .x-window-tr > .x-window-tc > .x-window-header{
+               margin: 0;
+       }
+       /* hide the rounded corners when the window is maximized. */
+       .x-window-maximized > .x-window-tl, .x-window-maximized > .x-window-tl > .x-window-tr{
+               background: none;
+       }
+       
+       .x-window-mc, .x-panel-mc{
+               background: transparent url(images/windowlight/c.png) repeat right top;
+               padding: 0;
+       }
+       .x-window-ml, .x-panel-ml{
+               padding-left: 2px;
+               background: transparent url(images/windowlight/l.png) repeat-y left bottom;
+       }
+       .x-window-mr, .x-panel-mr{
+               padding-right: 2px;
+               background: transparent url(images/windowlight/r.png) repeat-y right bottom;
+       }
+       .x-window-mc{
+               border: 0;
+               padding: 0;
+       }
+       .x-window-header, .x-panel-header{
+               line-height: 16px;
+       }
+       .x-panel-header{
+               margin: 0 0;
+       }
+       /* Window Footer Stuff */
+       .x-window-bl, .x-panel-bl{
+               padding-left: 16px;
+               min-height: 16px;
+               background: transparent url(images/windowdark/bl.png) no-repeat left bottom;
+       }
+       .x-window-br, .x-panel-br{
+               padding-right: 16px;
+               min-height: 16px;
+               background: transparent url(images/windowdark/br.png) no-repeat right bottom;
+       }
+       .x-window-bc, .x-panel-bc{
+               background: transparent url(images/windowdark/bc.png) repeat-x right bottom;
+               min-height: 16px;
+       }
+       .x-window-bc .x-window-footer, .x-panel-bc .x-panel-footer{
+               margin: 0 -16px;
+       }
+       /* If there is no footer, use the light version */
+       .x-window-bl.x-window-no-footer, .x-panel-bl.x-panel-nofooter{
+               border-top: 0;
+               background-image: url(images/windowlight/bl.png);
+       }
+       .x-window-no-footer .x-window-br, .x-panel-nofooter .x-panel-br{
+               background-image: url(images/windowlight/br.png);
+       }
+       .x-window-no-footer .x-window-bc, .x-panel-nofooter .x-panel-bc{
+               background-image: url(images/windowlight/bc.png) ;
+       }
+
+/* @end */
+
+/* @group Frame Panel inside a window */
+
+.x-window .x-panel-tl,
+.x-window .x-panel-tr,
+.x-window .x-panel-ml,
+.x-window .x-panel-mr,
+.x-window .x-panel-bl,
+.x-window .x-panel-br{
+       padding: 0;
+       background: transparent;
+}
+.x-window .x-panel-bl, .x-window .x-panel-bc, .x-window .x-panel-br{
+       background-position: top left;
+}
+
+/* @end */
+
+
+
+/* @group Basic Panel */
+
+.x-panel-header{
+               background: transparent url(images/windowlight/tc.png) repeat-x left top;
+               font-family: Optima;
+       }
+       .x-panel-body{
+               background: transparent url(images/windowlight/c.png) top left repeat;
+       }
+       .x-panel-mc .x-panel-body{
+               background: none;
+       }
+
+/* @end */
+
+/* @group Minimizers and Split-Panels */
+
+.x-splitbar-proxy{
+       background: transparent url(images/fills/30w.png) left top repeat;
+}
+
+.x-layout-collapsed{
+               background: transparent url(images/windowlight/c.png) top left repeat;
+               border-color: #333;
+       }
+       .x-layout-collapsed-over{
+               background: transparent url(images/windowdark/c.png) top left repeat;
+       }
+       
+.x-layout-mini-west, .x-layout-collapsed .x-layout-mini-east{
+               background: transparent url(images/mini/mini-left.png) top left no-repeat;
+}
+.x-layout-mini-east, .x-layout-collapsed .x-layout-mini-west{
+               background: transparent url(images/mini/mini-right.png) top left no-repeat;
+}
+.x-layout-mini-north, .x-layout-collapsed .x-layout-mini-south{
+               background: transparent url(images/mini/mini-bottom.png) top left no-repeat;
+}
+.x-layout-mini-south, .x-layout-collapsed .x-layout-mini-north{
+               background: transparent url(images/mini/mini-top.png) top left no-repeat;
+}
+
+/* @end */
+
+
+/* @group Toolbars */
+
+.x-toolbar{
+       height: 24px;
+       background: transparent url(images/tb.png) left top repeat-x;
+}
+.x-btn-text{
+       color: #FFF;
+}
+.xtb-text{
+       font-size: 11px;
+}
+       /* text-edit */
+       .x-edit-bold,
+       .x-edit-italic,
+       .x-edit-underline,
+       .x-edit-increasefontsize,
+       .x-edit-decreasefontsize,
+       .x-edit-forecolor,
+       .x-edit-backcolor,
+       .x-edit-justifyleft,
+       .x-edit-justifyright,
+       .x-edit-justifycenter,
+       .x-edit-createlink,
+       .x-edit-insertorderedlist,
+       .x-edit-insertunorderedlist,
+       .x-edit-sourceedit{
+               background-image: url(images/tb-sprite.png) !important;
+       }
+
+/* @end */
+
+/* @group Buttons */
+
+       .x-btn-ml, .x-btn-tl, .x-btn-bl, .x-btn-mr, .x-btn-tr, .x-btn-br{
+               width: 3px;
+       }
+               .x-btn-tl, .x-btn-tr, .x-btn-bl, .x-btn-br{
+               height: 3px;
+       }
+       .x-btn-mc em{
+               margin: 0 2px;
+       }
+
+       /* @group Normal */
+               .x-btn-tl,
+               .x-btn-tr,
+               .x-btn-bl,
+               .x-btn-br,
+               .x-btn-ml,
+               .x-btn-mr{
+                       background: transparent url(images/btn/whole/Normal.png) top left no-repeat;
+               }
+               .x-btn-mc,
+               .x-btn-bc,
+               .x-btn-tc{
+                       background: transparent url(images/btn/c/Normal.png) top left repeat-x;
+               }
+       /* @end */
+
+       /* @group Over */
+               .x-btn-over .x-btn-tl,
+               .x-btn-over .x-btn-tr,
+               .x-btn-over .x-btn-bl,
+               .x-btn-over .x-btn-br,
+               .x-btn-over .x-btn-ml,
+               .x-btn-over .x-btn-mr{
+                       background: transparent url(images/btn/whole/Over.png) top left no-repeat;
+               }
+               .x-btn-over .x-btn-mc,
+               .x-btn-over .x-btn-bc,
+               .x-btn-over .x-btn-tc{
+                       background: transparent url(images/btn/c/Over.png) top left repeat-x;
+               }
+       /* @end */
+
+       /* @group Click */
+               .x-btn-click .x-btn-tl,
+               .x-btn-click .x-btn-tr,
+               .x-btn-click .x-btn-bl,
+               .x-btn-click .x-btn-br,
+               .x-btn-click .x-btn-ml,
+               .x-btn-click .x-btn-mr{
+                       background: transparent url(images/btn/whole/Pressed.png) top left no-repeat;
+               }
+               .x-btn-click .x-btn-mc,
+               .x-btn-click .x-btn-bc,
+               .x-btn-click .x-btn-tc{
+                       background: transparent url(images/btn/c/Pressed.png) top left repeat-x;
+               }
+       /* @end */
+
+       /* @group Active */
+               .x-btn-pressed .x-btn-tl, .x-btn-menu-active .x-btn-tl,
+               .x-btn-pressed .x-btn-tr, .x-btn-menu-active .x-btn-tr,
+               .x-btn-pressed .x-btn-bl, .x-btn-menu-active .x-btn-bl,
+               .x-btn-pressed .x-btn-br, .x-btn-menu-active .x-btn-br,
+               .x-btn-pressed .x-btn-ml, .x-btn-menu-active .x-btn-ml,
+               .x-btn-pressed .x-btn-mr, .x-btn-menu-active .x-btn-mr{
+                       background: transparent url(images/btn/whole/Down.png) top left no-repeat;
+               }
+               .x-btn-pressed .x-btn-mc, .x-btn-menu-active .x-btn-mc,
+               .x-btn-pressed .x-btn-bc, .x-btn-menu-active .x-btn-bc,
+               .x-btn-pressed .x-btn-tc, .x-btn-menu-active .x-btn-tc{
+                       background: transparent url(images/btn/c/Down.png) top left repeat-x;
+               }
+       /* @end */
+.x-btn-mc{
+       padding: 0 10px !important;
+}
+
+.x-btn-tl{ background-position: left top !important; }
+.x-btn-tc{ background-position: center top !important; }
+.x-btn-tr{ background-position: right top !important; }
+.x-btn-ml{ background-position: left center !important; }
+.x-btn-mc{ background-position: center center !important; }
+.x-btn-mr{ background-position: right center !important; }
+.x-btn-bl{ background-position: left bottom !important; }
+.x-btn-bc{ background-position: center bottom !important; }
+.x-btn-br{ background-position: right bottom !important; }
+
+.x-btn-arrow-bottom{
+               background: transparent url(images/btn/arrv.png) center bottom no-repeat !important;
+}
+.x-btn-split-bottom{
+               background: transparent url(images/btn/splitv.png) center bottom no-repeat !important;
+               padding-bottom:18px !important; 
+}
+.x-btn-icon-small-right .x-btn-text{ padding-right: 20px !important; }
+.x-btn-icon-medium-right .x-btn-text{ padding-right: 28px !important; }
+.x-btn-icon-large-right .x-btn-text{ padding-right: 36px !important; }
+
+/*.x-toolbar-cell table{
+               height:22px;
+               margin-top: 1px;
+       }
+       .x-btn-tr, .x-btn-tc, .x-btn-tl, .x-btn-br, .x-btn-bc, .x-btn-bl{
+               display: none;
+       }
+       .x-btn-ml, .x-btn-mc, .x-btn-mr{
+               height: 22px;
+       }
+       .x-btn-ml,
+       .x-toolbar .x-btn-ml{
+               width: 6px;
+               background: transparent url(images/btn/l.png) left top no-repeat;
+       }
+       .x-btn-mc,
+       .x-toolbar .x-btn-mc{
+               background: transparent url(images/btn/c.png) left top repeat;
+       }
+       .x-btn-mr,
+       .x-toolbar .x-btn-mr{
+               width:6px;
+               background: transparent url(images/btn/r.png) right top no-repeat;
+       }
+       .x-btn-text{
+               text-shadow: -1px -1px 0 rgba(0,0,0,.6);
+               color: #EEE;
+       }
+       /* over state */
+       .x-btn-over .x-btn-ml, .x-btn-over .x-btn-mc,
+       .x-toolbar .x-btn-over .x-btn-ml, .x-toolbar .x-btn-over .x-btn-mc{
+               background-position: left -22px;
+       }
+       .x-btn-over .x-btn-mr,
+       .x-toolbar .x-btn-over .x-btn-mr{
+               background-position: right -22px;
+       }
+       .x-btn-over .x-btn-text{
+               color: #FFF;
+       }
+       /* pressed state */
+       .x-toolbar .x-btn-menu-active .x-btn-ml,
+       .x-toolbar .x-btn-menu-active .x-btn-mc,
+       .x-btn-pressed .x-btn-ml,
+       .x-btn-pressed .x-btn-mc,
+       .x-toolbar .x-btn-pressed .x-btn-ml,
+       .x-toolbar .x-btn-pressed .x-btn-mc{
+               background-position: left -66px;
+       }
+       .x-toolbar .x-btn-menu-active .x-btn-mr,
+       .x-toolbar .x-btn-pressed .x-btn-mr,
+       .x-btn-pressed .x-btn-mr{
+               background-position: right -66px;
+       }
+       
+       .x-btn-split{
+               background: transparent url(images/btn/splith.png) right center no-repeat !important;
+       }
+       .x-btn-split button{
+               padding-right: 5px !important;
+       }
+       .x-btn-arrow{
+               background: transparent url(images/btn/menu.png) right center no-repeat !important;
+               padding-right:12px !important;
+       }
+       /* pressed and over state */
+       /* Commented out because the selectors are so specific they're overriding the click state. What to do? I don't want to use !important... Figure out later. */
+       /*.x-toolbar .x-btn-menu-active.x-btn-over .x-btn-ml,
+       .x-toolbar .x-btn-menu-active.x-btn-over .x-btn-mc,
+       .x-btn-pressed.x-btn-over .x-btn-ml,
+       .x-btn-pressed.x-btn-over .x-btn-mc,
+       .x-toolbar .x-btn-pressed.x-btn-over .x-btn-ml,
+       .x-toolbar .x-btn-pressed.x-btn-over .x-btn-mc{
+               background-position: left -88px;
+       }
+       .x-toolbar .x-btn-menu-active.x-btn-over .x-btn-mr,
+       .x-toolbar .x-btn-pressed.x-btn-over .x-btn-mr,
+       .x-btn-pressed.x-btn-over .x-btn-mr{
+               background-position: right -88px;
+       }*/
+       /* click state */
+       /*.x-btn-click .x-btn-ml, .x-btn-click .x-btn-mc,
+       .x-toolbar .x-btn-click .x-btn-ml, .x-toolbar .x-btn-click .x-btn-mc{
+               background-position: left -44px;
+       }
+       .x-btn-click .x-btn-mr,
+       .x-toolbar .x-btn-click .x-btn-mr{
+               background-position: right -44px;
+       }
+       .x-btn-click .x-btn-text, .x-btn-pressed .x-btn-text{
+               color: #FFF;
+       }*/
+
+/* @end */
+       
+
+/* @group Tabs */
+
+.x-tab-panel-header, .x-tab-panel-footer{
+       padding: 0;
+       background: transparent url(images/fills/80b.png)
+}
+.x-tab-scrolling-top{
+               background: transparent url(images/windowdark/c.png)
+       }
+
+       /* default state */
+       
+       .x-tab-strip .x-tab-right{
+               padding: 0 0 0 6px;
+               background-image: url(images/tab/l/Top.png) !important;
+               background-position:left top;
+       }
+       .x-tab-strip .x-tab-left{
+               padding: 0 6px 0 0 !important;
+               background-image: url(images/tab/r/Top.png) !important;
+               background-position:right top;
+       }
+       .x-tab-strip .x-tab-strip-inner{
+               background-image: url(images/tab/c/Top.png) !important;
+               background-position:center top;
+       }
+       .x-tab-strip-bottom .x-tab-right{ background-image: url(images/tab/l/Bottom.png) !important; }
+       .x-tab-strip-bottom .x-tab-left{ background-image: url(images/tab/r/Bottom.png) !important; }
+       .x-tab-strip-bottom .x-tab-strip-inner{ background-image: url(images/tab/c/Bottom.png) !important; }
+       
+       /* hover state */
+       .x-tab-strip-over .x-tab-right{ background-position: left -26px !important; }
+       .x-tab-strip-over .x-tab-left{ background-position: right -26px !important;}
+       .x-tab-strip-over  .x-tab-strip-inner{ background-position: left -26px !important; }
+       .x-tab-right .x-tab-left .x-tab-strip-inner .x-tab-strip-text{
+               color: #CCC;
+               text-shadow: -1px -1px 0 rgba(0,0,0,.2);
+       }
+       
+       /* active state */
+       .x-tab-strip-active .x-tab-right{ background-position: left -52px !important; }
+       .x-tab-strip-active  .x-tab-left{ background-position: right -52px !important; }
+       .x-tab-strip-active  .x-tab-strip-inner{ background-position: center -52px !important; }
+       .x-tab-strip-active  .x-tab-right .x-tab-left .x-tab-strip-inner .x-tab-strip-text{
+               color: #222;
+               text-shadow:1px 1px 0 rgba(255,255,255,.25);
+       }
+       
+       /* spacer */
+       .x-tab-strip-top{
+               border: 0 !important;
+       }
+       .x-tab-strip-spacer{
+               border: 0 !important;
+               background: transparent url(images/fills/30w.png) top left repeat;
+               height: 2px;
+       }
+       
+       /* scroller */
+       .x-tab-scroller-left, .x-tab-scroller-right{
+       }
+       .x-tab-scroller-left{
+               background: transparent url(images/scroller/l.png) 0 0 no-repeat;
+       }
+       .x-tab-scroller-left-over, .x-tab-scroller-right-over{
+               background-position: left -22px !important;
+       }
+       .x-tab-scroller-left-disabled, .x-tab-scroller-right-disabled{
+               background-position: left 0 !important;
+       }
+       .x-tab-scroller-right{
+               background: transparent url(images/scroller/r.png) right 0 no-repeat;
+       }
+       
+       /* closable */
+       
+       .x-tab-strip-closable .x-tab-strip-inner{
+               padding-right: 15px;
+       }
+       .x-tab-strip-close{
+               background: transparent url(images/tab/close.png) 0 0 no-repeat;
+               margin-right: 2px;
+               top: 5px !important;
+               right:4px !important;
+       }
+       .x-tab-strip-active .x-tab-strip-close{
+               background-position: 0 -11px;
+       }
+
+/* @end */
+
+/* @group Forms and Form Fields */
+
+.x-form .x-form-item{
+       margin: 10px 0;
+}
+.x-form .x-form-item .x-form-item{
+       margin: 0;
+}
+
+.x-form-field{
+       font-size: 11px !important;
+}
+.x-toolbar-cell .x-form-field{
+       height: 22px !important;
+}
+.x-form-text, .x-form-textarea{
+       background: #444 url(images/gradients/dark-inner-shadow.png) top left repeat-x;
+       color: #FFF;
+       text-shadow: 1px 1px 0 #000;
+       border-top-color: #666;
+       border-bottom-color: #222;
+       border-left-color: transparent;
+       border-right-color: transparent;
+}
+.x-form-empty-field{
+       color: #999;
+}
+.x-form-invalid{
+       border-top-color: #700;
+       border-bottom-color: #200;
+       border-left-color: transparent;
+       border-right-color: transparent;
+       background-color: #C00;
+       color: #FAA;
+}
+.x-form-invalid-icon{
+       background: transparent url(images/icons/exclamation.png) top left no-repeat;
+}
+
+.x-form-textarea{
+       background: #FFF !important;
+       color: #000;
+       text-shadow: none;
+       border-color: #000 !important;
+}
+.x-form-field-wrap .x-form-trigger{
+               background-image:  url(images/trigger/blank.png);
+               border-bottom-color: #111;
+}
+.x-form-field-wrap .x-form-arrow-trigger{
+               background-image:  url(images/trigger/drop.png);
+}
+.x-form-field-wrap .x-form-date-trigger{
+               background-image:  url(images/trigger/date.png);
+}
+.x-form-field-wrap .x-form-search-trigger{
+               background-image:  url(images/trigger/search.png);
+}
+.x-form-field-wrap .x-form-clear-trigger{
+               background-image:  url(images/trigger/clear.png);
+}
+.x-html-editor-wrap{
+       background: #FFF;
+       border:1px solid #000;
+       color: #000 !important;
+}
+
+.x-fieldset{
+       border:1px solid #888;
+}
+
+/* @end */
+
+/* icons */
+.ext-mb-question.ext-mb-icon{
+               background: transparent url(images/icons/balloon.png) left center no-repeat;
+}
+.x-window-dlg .ext-mb-error{
+               background: transparent url(images/icons/cross.png) left center no-repeat;
+}
+.x-window-dlg .ext-mb-warning{
+               background: transparent url(images/icons/exclamation32.png) left center no-repeat;
+}
+.x-window-dlg .ext-mb-info{
+               background: transparent url(images/icons/information.png) left center no-repeat;
+}
+
+
+/* @group Combo List */
+
+.x-combo-list{
+       border: 1px solid transparent;
+       color: #444;
+       background: #FFF;
+       border-color: #777;
+}
+.x-combo-list-item{
+       border-color: transparent;
+}
+.x-combo-list .x-combo-selected{
+       background: #06C;
+       color: #FFF;
+       border:1px solid transparent !important;
+}
+.x-combo-list-inner{
+       background: #FFF;
+       border-bottom-color: #777 !important;
+}
+
+/* @end */
+
+/* @group Lists and Grids */
+
+.x-grid3-header-offset, .x-grid3-header{
+       padding: 0;
+       margin-bottom: -1px;
+}
+.x-grid3-header-offset table{
+       border-right: 2px groove #DDD;
+}
+.x-list-header, .x-grid3-header{
+       background: #CCC url(images/gloss/light-gloss.png) left top repeat-x;
+}
+.x-pivotgrid .x-grid3-header-offset td, .x-list-header-inner div, .x-grid3-hd, .x-dd-drag-proxy .x-grid3-hd-inner, .sort-asc .x-grid3-hd-inner, .sort-desc .x-grid3-hd-inner{
+       background: transparent url(images/listview/header.png) left top repeat-x;
+       color: #222;
+       text-shadow:1px 1px 0 rgba(255,255,255,.35);
+}
+.x-pivotgrid .x-grid3-header-offset td{
+       border-bottom: 1px solid #888;
+}
+/* a hover state */
+.x-grid3 .x-grid3-hd-over .x-grid3-hd-inner{
+       background: transparent url(images/listview/header.png) left -44px repeat-x;
+}
+.sort-asc, .sort-desc{
+       border-top: 0 !important;
+       border-bottom: 0 !important;
+       border-right: 0 !important;
+}
+.x-list-header-inner .sort-asc, .x-list-header-inner .sort-desc{
+       height: 15px;
+}
+.x-grid3 .sort-asc, .x-grid3 .sort-desc{
+       background: transparent url(images/listview/header.png) 0 -22px repeat-x !important;
+}
+.x-grid3-hd-inner{
+       color: #222;
+}
+.x-grid3 table{
+       font-size: 12px;
+}
+.x-grid3-hd-btn{
+       border-left:2px groove #DDD;
+       border-right:2px groove #DDD;
+       background: transparent url(images/listview/headerdrop.png) 0 0px;
+}
+.x-grid3-hd-btn:hover{
+       background: transparent url(images/listview/headerdrop.png) 0 -44px !important;
+}
+
+.x-grid3-sort-icon{
+       margin-top: -5px !important;
+       height: 5px;
+}
+.sort-asc .x-grid3-sort-icon{
+       background: transparent url(images/listview/sort-asc.png);
+}
+
+.sort-desc .x-grid3-sort-icon{
+       background: transparent url(images/listview/sort-desc.png);
+}
+
+.x-list-header .sort-asc{
+       background: transparent url(images/listview/listview-sort-asc.png) center top !important;
+}
+.x-list-header .sort-desc{
+       background: transparent url(images/listview/listview-sort-desc.png) center top !important;
+}
+
+.x-cols-icon{
+       background-image: url(images/listview/cols.png);
+}
+.xg-hmenu-sort-desc .x-menu-item-icon{
+       background-image: url(images/listview/az.png);
+}
+.xg-hmenu-sort-asc .x-menu-item-icon{
+       background-image: url(images/listview/za.png);
+}
+
+.x-grid3-row-over, .x-list-over{
+       background: transparent url(images/fills/30w.png) top left repeat !important;
+}
+
+.x-grid3-row-alt{
+       background: transparent url(images/fills/30b.png) top left repeat;
+       border: 1px solid #222 !important;
+       border-width: 1px 0 !important;
+}
+
+.x-grid3-scroller{
+       margin-top: 1px;
+}
+
+/* @group Row Editor */
+
+.x-row-editor .x-row-editor-body{
+       background: #838383 url(images/gloss/gloss.png) repeat center center;
+}
+.x-row-editor .x-row-editor-header, .x-row-editor .x-row-editor-footer{
+               height: 0;
+               border: 1px solid #FFF;
+               border-width: 1px 0;
+               background: transparent !important;
+}
+.x-row-editor .x-row-editor-header{
+       border-top-color: rgba(0,0,0,.15);
+       border-bottom-color: #71b3e1;
+}
+.x-row-editor .x-row-editor-footer{
+       border-bottom-color: rgba(0,0,0,.15);
+       border-top-color: #065790 !important;
+}
+
+.x-row-editor .x-row-editor-bwrap .x-plain,
+.x-row-editor .x-row-editor-bwrap .x-plain .x-plain-bwrap,
+.x-row-editor .x-row-editor-bwrap .x-plain .x-plain-bwrap .x-plain-body{
+       background-image: url(images/grid/row-editor-btns.png);
+}
+
+
+/* @end */
+
+/* @group Manip States */
+
+.x-grid3-resize-proxy {
+       background: #777;
+}
+
+.x-grid3-resize-marker {
+       background: #777;
+}
+
+.col-move-top{
+       background-image:url(images/grid/col-move-top.png);
+}
+
+.col-move-bottom{
+       background-image:url(images/grid/col-move-bottom.png);
+}
+
+/* @end */
+
+/* @group Selection States */
+.x-grid3-row, .x-list-wrap dl{
+       border: 1px solid transparent;
+       border-width: 1px 0;
+       }
+       .x-grid3-row .x-grid3-row-table, .x-list-wrap dl dt{
+               border: 1px solid transparent;
+               border-width: 1px 0;
+       }
+.x-grid3-row-selected, .x-list-selected{
+       border-color: #000 !important;
+       }
+       .x-grid3-row-selected .x-grid3-row-table, .x-list-selected dt{
+               background: transparent url(images/gloss/gloss.png) center center repeat;       
+               border-top-color: #71b3e1 !important;
+               border-bottom-color: #065790 !important;
+               }
+               .x-grid3-row-selected .x-grid3-cell, .x-list-selected dt{
+                               color: #FFF;
+                               text-shadow: -1px -1px 0 rgba(0,0,0,.35);
+               }
+
+/* @end */
+
+/* @group Groups */
+
+.x-grid3 .x-grid3-td-numberer{
+       font-weight: bold;
+       color: #AAA;
+}
+.x-grid3 .x-grid-group-hd{
+       border-top: 1px solid #CCC !important;
+       border-bottom: 1px solid #444 !important;
+       background: #666 url(images/gradients/30white-to-trans.png) left top repeat-x;
+       font-family: Helvetica, Arial, sans-serif;
+       padding: 0;
+       color: #FFF;
+       text-shadow:-1px -1px rgba(0,0,0,.25)
+       
+}
+.x-grid-group-collapsed .x-grid-group-hd{
+       border-bottom: 1px solid #222 !important;
+       background: #333 url(images/gradients/30white-to-trans.png) left top repeat-x;
+}
+
+/* @end */
+
+/* @group Pivot Grid */
+       .x-pivotgrid .x-grid3-row-table{
+               border-bottom: 1px solid transparent;
+       }
+       .x-grid3-header-title{
+               background: #666 url(images/gloss/gloss.png) left 50% repeat;
+               color: #FFF;
+               text-shadow:-1px -1px 0 rgba(0,0,0,.75);
+               border-top: 1px solid #999;
+               border-bottom: 1px solid #111;
+               font-size: 14px;
+               font-family: Optima, sans-serif;
+               line-height: 10px;
+       }
+       .x-grid3-row-headers table{
+               border-collapse:separate !important;
+               border-spacing:0;
+       }
+       .x-grid3-row-headers td{
+               height: 19px !important;
+               background: #FFF url(images/gloss/gloss.png) left 50% repeat;
+               border-top: 1px solid #777;
+               border-bottom: 1px solid #444;
+               border-left: 1px solid #777;
+               border-right: 1px solid #444;
+               color: #FFF;
+               text-shadow: -1px -1px 0 rgba(0,0,0,.35)
+       }
+
+/* @end */
+
+
+
+/* @end */
+
+/* @group Menus */
+
+.x-menu{
+       background: transparent url(images/fills/80b.png) top left repeat;
+       border:1px solid #444;
+       }
+       .x-menu-item-text{
+               color: #DDD;
+               text-shadow: -1px -1x 0 #000;
+       }
+       .x-menu-item-active, .x-menu-item-active .x-menu-item{
+               border-color: #3398e1;
+               border-top-color: #71b3e1;
+               border-bottom-color: #065790;
+               }
+               .x-menu-item-active{
+                       background: transparent url(images/gloss/gloss.png) left center repeat-x;
+                       }
+                       .x-menu-item-active .x-menu-item-text{
+                               color: #FFF;
+                       }
+       .x-menu-sep{
+               border-color: #444;
+       }
+       .x-menu-item-arrow{
+               background-image: url(images/menu/arrow.png);
+       }
+       .x-menu-check-item .x-menu-item-icon{
+               background: transparent url(images/form/unchecked.png) left center no-repeat;
+       }
+       .x-menu-item-checked .x-menu-check-item .x-menu-item-icon{
+               background: transparent url(images/form/checked.png) left center no-repeat;
+       }
+       .x-menu-group-item .x-menu-item-icon{
+               background: transparent url(images/form/unradioed.png) left center no-repeat;
+       }
+       .x-menu-item-checked .x-menu-group-item .x-menu-item-icon{
+               background: transparent url(images/form/radioed.png) left center no-repeat;
+       }
+
+/* @end */
+
+/* @group Progress Bar */
+
+.x-progress-wrap{
+       color: #CCC;
+       background: transparent url(images/progress.png) 0 -18px repeat-x;
+       border: 1px solid #000;
+}
+.x-progress-inner{
+       border: 1px solid #888;
+}
+.x-progress-bar{
+       color: #FFF;
+       background: #000 url(images/gloss/gloss.png) center center repeat-x;
+       padding: 1px;
+       height: 15px !important;
+       border-bottom: 1px solid #000 !important;
+}
+
+/* @end */
+
+/* @group Date Picker */
+
+.x-date-picker{
+       border: 1px solid #000;
+       background: transparent url(images/windowdark/c.png) repeat right top;
+}
+.x-date-picker a{
+       color: #06C;
+}
+.x-date-mp{
+       background: #222;
+}
+.x-date-mp a{
+       color: #6AF;
+       text-shadow:-1px -1px 0 rgba(0,0,0,.35)
+}
+.x-date-picker table{
+       font-size:11px;
+}
+.x-date-active{
+       border-color: transparent !important;
+}
+.x-date-selected{
+       border: 1px solid #000 !important;
+}
+.x-date-active a{
+       color: #FFF;
+}
+.x-date-picker .x-date-today .x-date-date{
+       background: transparent url(images/fills/30w.png) repeat;
+       border-color: transparent;
+}
+.x-date-picker .x-date-selected .x-date-date{
+       background: transparent url(images/windowlight/tc.png) repeat-x right center;
+       border-color: #3398e1;
+       border-top-color: #71b3e1;
+       border-bottom-color: #065790;
+}
+.x-date-picker .x-date-selected.x-date-today .x-date-date{
+       background-color: #FFF;
+}
+.x-date-prevday a, .x-date-nextday a{
+       color: #888;
+}
+.x-date-bottom{
+       padding: 3px;
+       border-top-color: #000;
+       background: transparent url(images/tb.png) left top repeat-x;
+}
+.x-date-picker th{
+       background: transparent url(images/tb.png) left top repeat-x;
+       border-bottom-color: #000;
+}
+.x-date-middle, .x-date-left, .x-date-right{
+       background: #000 url(images/gradients/30white-to-trans.png) left top repeat-x;
+       border-top: 1px solid #888;
+}
+.x-date-left a{
+       background: transparent url(images/btn/left.png) left top no-repeat;
+       height: 15px;
+       left: 15px;
+}
+.x-date-right a{
+       background: transparent url(images/btn/right.png) left top no-repeat;
+       height: 15px;
+       left: 15px;
+}
+.x-date-picker .x-date-mp-sel a{
+       background: transparent url(images/windowlight/tc.png) repeat-x right center;
+       border-color: #3398e1;
+       border-top-color: #71b3e1;
+       border-bottom-color: #065790;
+}
+.x-date-picker .x-date-mp-sep{
+       border-color: #444;
+}
+.x-date-mp-btns td{
+       border-color: #444;
+}
+.x-date-mp-btns{
+       background: #000;
+}
+.x-date-mp-btns button{
+       background: transparent url(images/windowlight/tc.png) repeat-x right center;
+       border-color: #888;
+       border-top-color: #CCC;
+       border-bottom-color: #555;
+       color: #FFF;
+       text-shadow:-1px -1px 0 #000;
+}
+.x-date-mp-btns button:hover{
+       border-color: #3398e1;
+       border-top-color: #71b3e1;
+       border-bottom-color: #065790;
+}
+
+/* @end */
+
+/* @group Resizable Handles */
+
+.x-resizable-handle{
+}
+.x-resizable-handle-north{ background-image: url(images/resizable/n.png); }
+.x-resizable-handle-south{ background-image: url(images/resizable/s.png); }
+
+.x-resizable-handle-east{ background-image: url(images/resizable/e.png); }
+.x-resizable-handle-west{ background-image: url(images/resizable/w.png); }
+.x-resizable-handle-northwest{ background-image: url(images/resizable/nw.png); }
+.x-resizable-handle-northeast{ background-image: url(images/resizable/ne.png); }
+.x-resizable-handle-southwest{ background-image: url(images/resizable/sw.png); }
+.x-resizable-handle-southeast{ background-image: url(images/resizable/se.png); }
+
+.x-resizable-pinned{
+       background: #888;
+}
+
+.x-window-handle, .x-window.x-resizable-pinned{
+       background: transparent !important;
+}
+
+/* @end */
+
+
+/* @group Node Tree */
+
+.x-tree .x-panel-body{
+       background: #000;
+}
+.x-tree-node-expanded .x-tree-node-icon{
+       background-image:url(images/tree/folder-open.png);
+}
+
+.x-tree-node-leaf .x-tree-node-icon{
+    background-image:url(images/tree/leaf.png);
+}
+
+.x-tree-node-collapsed .x-tree-node-icon{
+    background-image:url(images/tree/folder.png);
+}
+
+.x-tree-node-loading .x-tree-node-icon{
+    background-image:url(images/tree/loading.gif) !important;
+}
+
+.x-tree-node .x-tree-node-inline-icon {
+    background-image: none;
+}
+
+.x-tree-node-loading a span{
+     font-style: italic;
+     color:#FFF;
+}
+
+.x-tree-lines .x-tree-elbow{
+    background-image:url(images/tree/elbow.gif);
+}
+
+.x-tree-lines .x-tree-elbow-plus{
+    background-image:url(images/tree/elbow-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-minus{
+    background-image:url(images/tree/elbow-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end{
+    background-image:url(images/tree/elbow-end.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-plus{
+    background-image:url(images/tree/elbow-end-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-minus{
+    background-image:url(images/tree/elbow-end-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-line{
+    background-image:url(images/tree/elbow-line.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-plus{
+    background-image:url(images/tree/elbow-plus-nl.gif);
+}
+.x-tree-no-lines .x-tree-elbow-minus{
+    background-image:url(images/tree/elbow-minus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-plus{
+    background-image:url(images/tree/elbow-end-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-minus{
+    background-image:url(images/tree/elbow-end-minus-nl.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-plus{
+    background-image:url(images/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-minus{
+    background-image:url(images/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-plus{
+    background-image:url(images/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-minus{
+    background-image:url(images/tree/arrows.gif);
+}
+
+.x-tree-node{
+    color:#FFF;
+    font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-tree-node a, .x-dd-drag-ghost a{
+    color:#FFF;
+}
+
+.x-tree-node a span, .x-dd-drag-ghost a span{
+    color:#FFF;
+}
+
+.x-tree-node .x-tree-node-disabled a span{
+    color:gray !important;
+}
+
+.x-tree-node div.x-tree-drag-insert-below{
+     border-bottom-color: #3fb5ff;
+}
+
+.x-tree-node div.x-tree-drag-insert-above{
+     border-top-color:#3fb5ff;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below a{
+     border-bottom-color:#3fb5ff;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above a{
+     border-top-color:#3fb5ff;
+}
+
+.x-tree-node .x-tree-drag-append a span{
+     background-color:#444;
+     border-color:#666;
+}
+
+.x-tree-node .x-tree-node-over {
+    background-color: #444;
+}
+
+.x-tree-node .x-tree-selected {
+    background-color: #456;
+}
+
+.x-tree-drop-ok-append .x-dd-drop-icon{
+  background-image: url(images/tree/drop-add.png);
+}
+
+.x-tree-drop-ok-above .x-dd-drop-icon{
+  background-image: url(images/tree/drop-over.gif);
+}
+
+.x-tree-drop-ok-below .x-dd-drop-icon{
+  background-image: url(images/tree/drop-under.gif);
+}
+
+.x-tree-drop-ok-between .x-dd-drop-icon{
+  background-image: url(images/tree/drop-between.gif);
+}
+
+/* @end */
+
+/* @group Toolbar Icons */
+
+.x-toolbar .x-btn-mc em.x-btn-split {
+    background-image:url(../images/default/btn/s-arrow-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split, .x-toolbar .x-btn-click .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split, .x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split
+{
+    background-image:url(../images/default/btn/s-arrow-o.gif);
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split-bottom {
+    background-image:url(../images/default/btn/s-arrow-b-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split-bottom, .x-toolbar .x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split-bottom, .x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split-bottom
+{
+    background-image:url(../images/default/btn/s-arrow-bo.gif);
+}
+
+.x-toolbar .xtb-sep {
+       width: 0;
+       border-left: 1px solid #222;
+       border-right: 1px solid #777;
+}
+.x-tbar-page-first{
+    background-image: url(images/icons/first.png) !important;
+}
+
+.x-tbar-loading{
+    background-image: url(images/icons/refresh.png) !important;
+}
+
+.x-tbar-page-last{
+    background-image: url(images/icons/last.png) !important;
+}
+.x-tbar-page-next{
+    background-image: url(images/icons/next.png) !important;
+}
+
+.x-tbar-page-prev{
+    background-image: url(images/icons/prev.png) !important;
+}
+
+.x-item-disabled .x-tbar-loading{
+    background-image: url(images/icons/d-refresh.png) !important;
+}
+
+.x-item-disabled .x-tbar-page-first{
+    background-image: url(images/icons/d-first.png) !important;
+}
+
+.x-item-disabled .x-tbar-page-last{
+    background-image: url(images/icons/d-last.png) !important;
+}
+
+.x-item-disabled .x-tbar-page-next{
+    background-image: url(images/icons/d-next.png) !important;
+}
+
+.x-item-disabled .x-tbar-page-prev{
+    background-image: url(images/icons/d-prev.png) !important;
+}
+
+.x-paging-info {
+    color:#444;
+}
+
+.x-toolbar-more-icon {
+    background-image: url(../images/default/toolbar/more.gif) !important;
+}
+
+/* @end */
+
+/* @group Tool Tips */
+
+.x-tip{
+       color: #FFE;
+       text-shadow: -1px -1px 0 rgba(0,0,0,.35);
+}
+.x-tip .x-tip-tl{
+       padding-left: 5px;
+       background:transparent url(images/tt/tl.png) top left no-repeat;
+}
+.x-tip .x-tip-tr{
+       padding-right: 5px;
+       background:transparent url(images/tt/tr.png) top right no-repeat;
+}
+.x-tip .x-tip-tc{
+       font-family: Optima, sans-serif;
+       text-transform: uppercase;
+       letter-spacing: .1em;
+       font-size: 10px;
+       padding-bottom: 3px;
+       padding-top: 5px;
+       background:transparent url(images/tt/tc.png) top left repeat-x;
+}
+.x-tip .x-tip-ml{
+       padding-left: 5px;
+       background:transparent url(images/tt/ml.png) top left repeat-y;
+}
+.x-tip .x-tip-mr{
+       padding-right: 5px;
+       background:transparent url(images/tt/mr.png) top right repeat-y;
+}
+.x-tip .x-tip-mc{
+       background:transparent url(images/tt/c.png) top right repeat;
+}
+.x-tip .x-tip-bl{
+       padding-left: 5px;
+       background:transparent url(images/tt/bl.png) bottom left no-repeat;
+}
+.x-tip .x-tip-br{
+       padding-right: 5px;
+       background:transparent url(images/tt/br.png) bottom right no-repeat;
+}
+.x-tip .x-tip-bc{
+       height: 5px;
+       background:transparent url(images/tt/bc.png) bottom left repeat-x;
+}
+
+       /* @group Invalid Tips */
+
+.x-form-invalid-tip{
+               color: #FCC;
+       }
+       .x-form-invalid-tip .x-tip-body{
+               background:transparent url(images/icons/exclamation.png) top left no-repeat;
+       }
+       .x-form-invalid-tip .x-tip-tl{
+               padding-left: 5px;
+               background:transparent url(images/tt/i-tl.png) top left no-repeat;
+       }
+       .x-form-invalid-tip .x-tip-tr{
+               padding-right: 5px;
+               background:transparent url(images/tt/i-tr.png) top right no-repeat;
+       }
+       .x-form-invalid-tip .x-tip-tc{
+               font-family: Optima, sans-serif;
+               text-transform: uppercase;
+               letter-spacing: .1em;
+               font-size: 10px;
+               padding-bottom: 3px;
+               padding-top: 5px;
+               background:transparent url(images/tt/i-tc.png) top left repeat-x;
+       }
+       .x-form-invalid-tip .x-tip-ml{
+               padding-left: 5px;
+               background:transparent url(images/tt/i-ml.png) top left repeat-y;
+       }
+       .x-form-invalid-tip .x-tip-mr{
+               padding-right: 5px;
+               background:transparent url(images/tt/i-mr.png) top right repeat-y;
+       }
+       .x-form-invalid-tip .x-tip-mc{
+               background:transparent url(images/tt/i-c.png) top right repeat;
+       }
+       .x-form-invalid-tip .x-tip-bl{
+               padding-left: 5px;
+               background:transparent url(images/tt/i-bl.png) bottom left no-repeat;
+       }
+       .x-form-invalid-tip .x-tip-br{
+               padding-right: 5px;
+               background:transparent url(images/tt/i-br.png) bottom right no-repeat;
+       }
+       .x-form-invalid-tip .x-tip-bc{
+               height: 5px;
+               background:transparent url(images/tt/i-bc.png) bottom left repeat-x;
+       }
+
+       /* @end */
+
+/* @end */
+
+/* @group Slider */
+
+.x-slider-horz .x-slider-thumb{
+       background-image: url(images/slider/thumbh.png) !important;
+}
+
+.x-slider-vert .x-slider-thumb{
+       background-image: url(images/slider/thumbv.png) !important;
+}
+.x-slider-horz, .x-slider-horz .x-slider-end, .x-slider-horz .x-slider-inner{
+       background-image: url(images/slider/h.png) !important;
+}
+.x-slider-vert, .x-slider-vert .x-slider-end, .x-slider-vert .x-slider-inner{
+       background-image: url(images/slider/v.png) !important;
+}
+
+/* @end */
+
+/* @group Masked Panel */
+
+.ext-el-mask{
+       background: #999;
+}
+.ext-el-mask-msg{
+       background: transparent url(images/gloss/gloss.png) center center repeat;
+       border-bottom-color: #222;
+       border-top-color: #999;
+       border-left-color: #444;
+       border-right-color: #444;
+       border-radius:4px;
+}
+.ext-el-mask-msg div{
+       background: #000;
+       border: 1px solid #222;
+       border-top-color: #222;
+       border-bottom-color: #999;
+       border-left-color: #444;
+       border-right-color: #444;
+       border-radius:3px;
+}
+.x-mask-loading div{
+       background:#000 url(images/load.gif) 4px 4px no-repeat;
+}
+
+/* @end */
+
+/* @group Color Palette */
+
+.x-color-palette{
+       background: #666 url(images/gradients/30white-to-trans.png) 0 0 repeat-x;
+       margin: -3px !important;
+}
+.x-color-palette a{
+       color: #000;
+       border-color: transparent;
+}
+.x-color-palette .x-color-palette-sel em, .x-color-palette a:hover em{
+       border-top-color: #222;
+       border-left-color: #444;
+       border-right-color: #444;
+       border-bottom-color: #666;
+}
+.x-color-palette .x-color-palette-sel, .x-color-palette a:hover{
+       border-top-color: #666 !important;
+       border-left-color: #444 !important;
+       border-right-color: #444 !important;
+       border-bottom-color: #222 !important;
+       background: #000 url(images/gloss/gloss.png) center center repeat-x;
+}
+ .x-color-palette a:hover{
+       border-top-color: #71b3e1 !important;
+       border-left-color: #3398e1 !important;
+       border-right-color: #3398e1 !important;
+       border-bottom-color: #065790 !important;
+       background-color: #065790;
+}
+
+/* @end */
+
+/* @group Drag Proxy */
+
+.x-dd-drag-proxy{
+       border: 1px solid #000;
+       background: #000;
+       border-radius:3px;
+}
+.x-dd-drag-ghost{
+       border-radius:2px;
+       background: #000 url(images/gloss/gloss.png) center center repeat-x;
+       border-top: 1px solid #AAA;
+       border-bottom: 1px solid #222;
+       border-left: 1px solid #444;
+       border-right: 1px solid #444;
+}
+.x-dd-drop-ok .x-dd-drop-icon{
+       background-image: url(images/icons/yes.png);
+}
+.x-dd-drop-nodrop .x-dd-drop-icon{
+       background-image: url(images/icons/no.png);
+}
+
+/* @end */
+
+/* @group Accordion */
+
+.x-accordion-hd{
+       border-top: 1px solid #CCC !important;
+       border-bottom: 1px solid #444 !important;
+       background: #666 url(images/gradients/30white-to-trans.png) left top repeat-x;
+       font-family: Helvetica, Arial, sans-serif;
+       font-size: .9em;
+}
+.x-panel-collapsed .x-accordion-hd{
+       border-bottom: 1px solid #222 !important;
+       background: #333 url(images/gradients/30white-to-trans.png) left top repeat-x;
+}
+.x-accordion-hd .x-panel-header-text{
+       text-shadow: -1px -1px 0 rgba(0,0,0,.35);
+}
+
+/* @end */
+
+/* @group Group Summary */
+
+.x-grid3-summary-row{
+       background: transparent url(images/fills/80b.png) left top repeat;
+}
+
+/* @end */
+
diff --git a/philo/contrib/gilbert/static/gilbert/plugins/auth.js b/philo/contrib/gilbert/static/gilbert/plugins/auth.js
new file mode 100644 (file)
index 0000000..97e1785
--- /dev/null
@@ -0,0 +1,140 @@
+Ext.ns('Gilbert.lib.plugins.auth');
+
+
+Gilbert.lib.plugins.auth.Plugin = Ext.extend(Gilbert.lib.plugins.Plugin, {
+       
+       init: function (application) {
+               Gilbert.lib.plugins.auth.Plugin.superclass.init.call(this, application);
+               
+               var outer = this;
+               
+               Gilbert.api.plugins.auth.whoami(function (whoami) {
+                       application.mainmenu.add({
+                               xtype: 'tbfill',
+                       },{
+                               xtype: 'tbseparator',
+                       },{
+                               xtype: 'button',
+                               iconCls: 'icon-user-silhouette',
+                               text: '<span style="font-weight: bolder;">' + whoami + '</span>',
+                               menu: [{
+                                               text: 'Theme',
+                                               iconCls: 'icon-mask',
+                                               menu: outer.build_theme_menu(),
+                                       },{
+                                               xtype: 'menuseparator',
+                                       },{
+                                       text: 'Change password',
+                                       iconCls: 'icon-key--pencil',
+                                       handler: function(button, event) {
+                                               Gilbert.api.plugins.auth.get_passwd_form(function(formspec) {
+                                                       var formspec = formspec;
+                                                       for (var item_index in formspec.items) {
+                                                               var item = formspec.items[item_index];
+                                                               Ext.apply(item, {
+                                                                       anchor: '100%',
+                                                               });
+                                                       }
+                                                       var change_password_form = new Gilbert.lib.ui.DjangoForm(Ext.applyIf({
+                                                               title: 'Change password',
+                                                               header: false,
+                                                               iconCls: 'icon-key--pencil',
+                                                               bodyStyle: 'padding: 10px;',
+                                                               baseCls: 'x-plain',
+                                                               autoScroll: true,
+                                                               api: {
+                                                                       submit: Gilbert.api.plugins.auth.save_passwd_form,
+                                                               },
+                                                       }, formspec));
+                                                       var change_password_window = application.create_window({
+                                                               layout: 'fit',
+                                                               title: change_password_form.title,
+                                                               iconCls: change_password_form.iconCls,
+                                                               bodyStyle: 'padding: 5px; background: solid;',
+                                                               width: 360,
+                                                               height: 240,
+                                                               maximizable: false,
+                                                               items: [change_password_form],
+                                                               bbar: [
+                                                                       '->',
+                                                                       {
+                                                                               text: 'Change password',
+                                                                               iconCls: 'icon-key--pencil',
+                                                                               handler: function(button, event) {
+                                                                                       change_password_form.getForm().submit({
+                                                                                               success: function(form, action) {
+                                                                                                       Ext.MessageBox.alert('Password changed', 'Your password has been changed.');
+                                                                                                       change_password_window.close();
+                                                                                               },
+                                                                                       });
+                                                                               },
+                                                                       }
+                                                               ],
+                                                       });
+                                                       change_password_window.doLayout();
+                                                       change_password_window.show(button.el);
+                                               });
+                                               
+                                       },
+                               },{
+                                       text: 'Log out',
+                                       iconCls: 'icon-door-open-out',
+                                       handler: function(button, event) {
+                                               Gilbert.api.plugins.auth.logout(function(success) {
+                                                       if (success) {
+                                                               window.onbeforeunload = undefined;
+                                                               document.location.reload();
+                                                       } else {
+                                                               Ext.MessageBox.alert('Log out failed', 'You have <strong>not</strong> been logged out. This could mean that your connection with the server has been severed. Please try again.');
+                                                       }
+                                               })
+                                       }
+                               }],
+                       });
+                       application.do_layout();
+               });
+       },
+       
+       build_theme_menu: function () {
+               var application = this.application;
+               
+               var theme_switcher = function (menuitem) {
+                       var theme_name = menuitem.theme_name;
+                       Gilbert.api.plugins.auth.set_preference('gilbert.theme', theme_name, function () {
+                               application._set_theme(theme_name);
+                               application.do_layout();
+                               application.windows.each(function (win) {
+                                       win.doLayout();
+                               });
+                       });
+               };
+               
+               var menu = [];
+               
+               Ext.each(document.getElementsByClassName('gilbert.theme'), function (theme_element) {
+                       var theme_id = theme_element.id;
+                       var theme_name = theme_id.match(/gilbert.theme.(.*)/)[1];
+                       var current_theme = false;
+                       if (!theme_element.disabled) {
+                               current_theme = true;
+                       }
+                       
+                       
+                       menu.push({
+                               text: theme_name.capfirst(),
+                               checked: current_theme,
+                               group: 'theme',
+                               theme_name: theme_name,
+                               handler: theme_switcher,
+                       });
+               });
+               
+               return menu;
+       },
+       
+});
+
+
+Gilbert.on('ready', function (application) {
+       application.register_plugin('auth', new Gilbert.lib.plugins.auth.Plugin());
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/plugins/models.js b/philo/contrib/gilbert/static/gilbert/plugins/models.js
new file mode 100644 (file)
index 0000000..a5b5eed
--- /dev/null
@@ -0,0 +1,527 @@
+Ext.ns('Gilbert.lib.plugins.models.ui');
+
+
+Ext.override(Gilbert.lib.models.Model, {
+       create_new_form: function (callback, config) {
+               var model = this;
+               var config = config;
+               model.api.get_form({}, function (formspec) {
+                       var formspec = formspec;
+                       for (var item_index in formspec.items) {
+                               var item = formspec.items[item_index];
+                               Ext.apply(item, {
+                                       anchor: '100%',
+                               });
+                       }
+                       var form_panel = new Gilbert.lib.ui.DjangoForm(Ext.applyIf(Ext.applyIf(config||{},{
+                               title: 'New '+model.verbose_name,
+                               header: false,
+                               iconCls: 'icon-plus',
+                               baseCls: 'x-plain',
+                               autoScroll: true,
+                               api: {
+                                       submit: model.api.save_form,
+                               },
+                       }), formspec));
+                       callback(form_panel);
+               });
+       },
+       create_edit_form: function (callback, pk, config) {
+               var model = this;
+               var config = config;
+               model.api.get_form({'pk': pk}, function (formspec) {
+                       var formspec = formspec;
+                       for (var item_index in formspec.items) {
+                               var item = formspec.items[item_index];
+                               Ext.apply(item, {
+                                       anchor: '100%',
+                               });
+                       }
+                       callback(new Gilbert.lib.ui.DjangoForm(Ext.applyIf(Ext.applyIf(config||{},{
+                               title: 'Editing '+model.verbose_name.capfirst()+' ('+pk+')',
+                               header: false,
+                               iconCls: 'icon-pencil',
+                               baseCls: 'x-plain',
+                               autoScroll: true,
+                               api: {
+                                       submit: model.api.save_form,
+                               },
+                               baseParams: {
+                                       pk: pk,
+                               },
+                       }), formspec)));
+               });
+       },
+});
+
+
+Gilbert.lib.plugins.models.ui.ForeignKeyColumn = Ext.extend(Ext.grid.Column, {
+       renderer: function(v) {
+               return v.__unicode__
+       }
+})
+
+
+Ext.grid.Column.types['foreignkeycolumn'] = Gilbert.lib.plugins.models.ui.ForeignKeyColumn
+
+
+Gilbert.lib.plugins.models.ui.ModelPanel = Ext.extend(Ext.Panel, {
+       constructor: function (model, plugin, config) {
+               var model = this.model = model;
+               var plugin = this.plugin = plugin;
+               var application = this.application = plugin.application;
+               var outer = this;
+               
+               var store = this.store = model.create_store({
+                       autoLoad: true,
+                       autoDestroy: true,
+                       autoSave: false,
+                       baseParams: {
+                               start: 0,
+                               limit: 25,
+                       },
+               });
+               
+               var grid = this.grid = new Ext.grid.GridPanel({
+                       ddGroup: model.drag_drop_group,
+                       enableDragDrop: true,
+                       loadMask: true,
+                       store: store,
+                       columns: model.columns,
+                       columnLines: true,
+                       stripeRows: true,
+                       viewConfig: {
+                               forceFit: true,
+                       },
+                       selModel: new Ext.grid.RowSelectionModel(),
+                       bbar: new Ext.PagingToolbar({
+                               pageSize: 25,
+                               store: store,
+                               displayInfo: true,
+                               displayMsg: 'Displaying '+model.verbose_name_plural+' {0} - {1} of {2}',
+                               emptyMsg: 'No '+model.verbose_name_plural+' to display',
+                               items: (function () {
+                                       if (model.searchable) {
+                                               return [
+                                                       {
+                                                               xtype: 'tbseparator',
+                                                       },
+                                                       new Ext.ux.form.SearchField({
+                                                               store: store,
+                                                       }),
+                                               ];
+                                       } else {
+                                               return [];
+                                       }
+                               })(),
+                       }),
+               });
+               
+               var new_action = this.new_action = new Ext.Action({
+                       text: 'New ' + model.verbose_name,
+                       iconCls: 'icon-plus',
+                       handler: function () {
+                               plugin.create_instance_window(model, undefined, function (win) {
+                                       win.on('saved', function () {
+                                               store.reload();
+                                       });
+                                       win.show();
+                               });
+                       },
+               });
+               
+               var edit_action = this.edit_action = new Ext.Action({
+                       disabled: true,
+                       text: 'Edit',
+                       iconCls: 'icon-pencil',
+                       handler: function () {
+                               Ext.each(grid.getSelectionModel().getSelections(), function (record, index) {
+                                       plugin.create_instance_window(model, record.id, function (win) {
+                                               win.on('saved', function () {
+                                                       store.reload();
+                                               });
+                                               win.show();
+                                       });
+                               });
+                       }
+               });
+               
+               var delete_action = this.delete_action = new Ext.Action({
+                       disabled: true,
+                       text: 'Delete',
+                       iconCls: 'icon-minus',
+                       handler: function () {
+                               var records = grid.getSelectionModel().getSelections();
+                               var pks = [];
+                               Ext.each(records, function (record, index) {
+                                       pks.push(record.id);
+                               });
+                               model.api.data_destroy_consequences(pks, function (consequences) {
+                                       var convert_consequences_array = function (consequences) {
+                                               var last_parent = consequences[0];
+                                               Ext.each(consequences, function (consequence, index) {
+                                                       if (index != 0) {
+                                                               if (!Ext.isArray(consequence)) {
+                                                                       last_parent = consequence;
+                                                               } else {
+                                                                       last_parent['children'] = convert_consequences_array(consequence);
+                                                                       delete consequences[index];
+                                                               }
+                                                       }
+                                               });
+                                               new_consequences = [];
+                                               Ext.each(consequences, function (consequence) {
+                                                       if (consequence) {
+                                                               var new_consequence = {};
+                                                               if (!consequence['children']) {
+                                                                       new_consequence['leaf'] = true;
+                                                               } else {
+                                                                       new_consequence['leaf'] = false;
+                                                                       new_consequence['children'] = consequence['children'];
+                                                               }
+                                                               var app_label = consequence['app_label'];
+                                                               var name = consequence['name'];
+                                                               var model = Gilbert.get_model(app_label, name);
+                                                               if (model) {
+                                                                       new_consequence['text'] = consequence['__unicode__'];
+                                                                       new_consequence['iconCls'] = model.iconCls;
+                                                               } else {
+                                                                       new_consequence['text'] = '(' + consequence['name'] + ') ' + consequence['__unicode__'];
+                                                                       new_consequence['iconCls'] = 'icon-block';
+                                                               }
+                                                               new_consequence['disabled'] = true;
+                                                               new_consequences.push(new_consequence);
+                                                       }
+                                               });
+                                               return new_consequences;
+                                       };
+                                       
+                                       var new_consequences = convert_consequences_array(consequences);
+                                       var nested_consequences = false;
+                                       for(var i=0;i<new_consequences.length;i++){
+                                               if (!new_consequences[i]['leaf']) {
+                                                       nested_consequences = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       var tree = this.tree = new Ext.tree.TreePanel({
+                                               loader: new Ext.tree.TreeLoader(),
+                                               enableDD: false,
+                                               animate: false,
+                                               trackMouseOver: false,
+                                               autoScroll: true,
+                                               root: {
+                                                       'disabled': true,
+                                                       'text': 'To be deleted',
+                                                       'iconCls': 'icon-minus',
+                                                       'leaf': false,
+                                                       'children': new_consequences,
+                                               },
+                                               useArrows: true,
+                                               rootVisible: false,
+                                               region: 'center',
+                                       });
+                                       
+                                       var consequences_win = application.create_window({
+                                               layout: 'border',
+                                               width: 300,
+                                               height: 300,
+                                               modal: true,
+                                               title: 'Delete ' + model.verbose_name_plural,
+                                               iconCls: 'icon-minus',
+                                               items: [
+                                                       {
+                                                               region: 'north',
+                                                               xtype: 'panel',
+                                                               html: 'Are you sure you want to delete these ' + model.verbose_name_plural + '?' + (nested_consequences ? ' Nested objects will also be deleted.' : ''),
+                                                               bodyStyle: 'padding: 15px;',
+                                                       },
+                                                       tree,
+                                               ],
+                                               bbar: [
+                                                       {
+                                                               xtype: 'button',
+                                                               text: 'Cancel',
+                                                               handler: function () {
+                                                                       consequences_win.close();
+                                                               },
+                                                       },
+                                                       '->',
+                                                       {
+                                                               xtype: 'button',
+                                                               text: 'Delete',
+                                                               handler: function () {
+                                                                       consequences_win.close();
+                                                                       store.remove(records);
+                                                                       store.save();
+                                                                       store.reload();
+                                                               },
+                                                       },
+                                               ],
+                                       });
+                                       
+                                       consequences_win.show();
+                               });
+                       }
+               });
+               
+               grid.on('cellcontextmenu', function (grid, rowIndex, cellIndex, e) {
+                       e.stopEvent();
+                       selmodel = grid.getSelectionModel();
+                       if (!selmodel.isSelected(rowIndex)) {
+                               selmodel.selectRow(rowIndex, false);
+                       }
+                       var contextmenu = new Ext.menu.Menu({
+                               items: [
+                                       edit_action,
+                                       delete_action,
+                               ],
+                       });
+                       contextmenu.showAt(e.xy);
+               });
+               
+               grid.on('rowdblclick', function(grid, rowIndex, e) {
+                       var record = grid.getStore().getAt(rowIndex)
+                       plugin.create_instance_window(model, record.id, function (win) {
+                               win.on('saved', function () {
+                                       store.reload();
+                               });
+                               win.show();
+                       });
+               });
+               
+               grid.getSelectionModel().on('selectionchange', function (selmodel) {
+                       if (selmodel.hasSelection()) {
+                               edit_action.setDisabled(false);
+                               delete_action.setDisabled(false);
+                       } else {
+                               edit_action.setDisabled(true);
+                               delete_action.setDisabled(true);
+                       }
+               });
+               
+               Gilbert.lib.plugins.models.ui.ModelPanel.superclass.constructor.call(this, Ext.applyIf(config||{}, {
+                       layout: 'fit',
+                       tbar: new Ext.Toolbar({
+                               items: [
+                                       new_action,
+                                       { xtype: 'tbseparator' },
+                                       edit_action,
+                                       delete_action,
+                                       //'->',
+                                       //{
+                                       //      text: 'Advanced',
+                                       //      iconCls: 'icon-gear',
+                                       //      disabled: true,
+                                       //      menu: [],
+                                       //},
+                               ],
+                       }),
+                       items: [grid],
+               }));
+       },
+});
+
+
+Gilbert.lib.plugins.models.Plugin = Ext.extend(Gilbert.lib.plugins.Plugin, {
+       
+       init: function (application) {
+               Gilbert.lib.plugins.models.Plugin.superclass.init.call(this, application);
+               
+               var new_menu = this.new_menu = new Ext.menu.Menu();
+               var manage_menu = this.manage_menu = new Ext.menu.Menu();
+               
+               application.mainmenu.insert(2, {
+                       xtype: 'button',
+                       iconCls: 'icon-plus',
+                       text: 'New',
+                       menu: new_menu,
+               });
+               
+               application.mainmenu.insert(3, {
+                       xtype: 'button',
+                       iconCls: 'icon-databases',
+                       text: 'Manage',
+                       menu: manage_menu,
+               });
+               
+               application.do_layout();
+               
+               Ext.iterate(application.models, function (app_label, models) {
+                       Ext.iterate(models, function (name, model) {
+                               this.handle_new_model(model);
+                       }, this);
+               }, this);
+               
+               application.on('model_registered', function (model) {
+                       this.handle_new_model(model);
+               }, this);
+               
+               this.instance_windows = {}
+       },
+       
+       handle_new_model: function (model) {
+               var outer = this;
+               model.api.has_add_permission(function (has_add_permission) {
+                       if (has_add_permission) {
+                               outer.add_to_new_menu(model);
+                       }
+               });
+               model.api.has_read_permission(function (has_read_permission) {
+                       if (has_read_permission) {
+                               outer.add_to_manage_menu(model);
+                       }
+               });
+       },
+       
+       add_to_new_menu: function (model) {
+               var outer = this;
+               this.new_menu.add({
+                       text: model.verbose_name.capfirst(),
+                       iconCls: model.iconCls,
+                       model: model,
+                       handler: function (button, event) {
+                               outer.create_instance_window(this.model, undefined, function (win) {
+                                       win.show();
+                               });
+                       },
+               });
+       },
+       
+       add_to_manage_menu: function (model) {
+               var outer = this;
+               this.manage_menu.add({
+                       text: model.verbose_name_plural.capfirst(),
+                       iconCls: model.iconCls,
+                       model: model,
+                       handler: function (button, event) {
+                               var win = outer.create_model_management_window(this.model);
+                               win.show(button.el);
+                       },
+               });
+       },
+       
+       create_model_management_window: function (model, config, cls) {
+               var model = model;
+               var panel = new Gilbert.lib.plugins.models.ui.ModelPanel(model, this);
+               var win = this.application.create_window(Ext.applyIf(config||{},{
+                       layout: 'fit',
+                       title: model.verbose_name_plural.capfirst(),
+                       iconCls: model.iconCls,
+                       width: 640,
+                       height: 320,
+                       maximizable: true,
+                       items: [panel],
+               }), cls);
+               return win;
+       },
+       
+       create_instance_window: function (model, pk, callback, config, cls) {
+               if (pk != undefined) {
+                       var win = this.instance_windows[[model.app_label, model.name, pk]];
+                       if (win != undefined){
+                               win.show();
+                               return;
+                       };
+               };
+               var pk = pk;
+               var callback = callback;
+               var application = this.application;
+               var outer = this;
+               
+               var form_callback = function (form) {
+                       var oldform = form;
+                       var win = application.create_window({
+                               layout: 'fit',
+                               title: form.title,
+                               iconCls: form.iconCls,
+                               bodyStyle: 'padding: 5px; background: solid;',
+                               width: 640,
+                               height: 320,
+                               maximizable: true,
+                               items: [form],
+                               bbar: [
+                                       '->',
+                                       {
+                                               xtype: 'button',
+                                               text: 'Save and Close',
+                                               iconCls: 'icon-database-import',
+                                               handler: function (button) {
+                                                       var loading_mask = new Ext.LoadMask(win.body, {
+                                                               msg: 'Saving...',
+                                                               removeMask: true,
+                                                       });
+                                                       loading_mask.show();
+                                                       win.items.items[0].getForm().submit({
+                                                               success: function (form, action) {
+                                                                       loading_mask.hide();
+                                                                       win.fireEvent('saved');
+                                                                       win.close();
+                                                               },
+                                                               failure: function (form, action) {
+                                                                       loading_mask.hide();
+                                                               },
+                                                       });
+                                               }
+                                       },
+                                       {
+                                               xtype: 'button',
+                                               text: 'Save',
+                                               iconCls: 'icon-database-import',
+                                               handler: function (button) {
+                                                       var loading_mask = new Ext.LoadMask(win.body, {
+                                                               msg: 'Saving...',
+                                                               removeMask: true,
+                                                       });
+                                                       loading_mask.show();
+                                                       win.items.items[0].getForm().submit({
+                                                               success: function (form, action) {
+                                                                       win.fireEvent('saved');
+                                                                       var pk = action.result.pk;
+                                                                       model.create_edit_form(function (newform) {
+                                                                               win.remove(oldform);
+                                                                               win.add(newform);
+                                                                               loading_mask.hide();
+                                                                               win.setTitle(newform.title);
+                                                                               win.setIconClass(newform.iconCls);
+                                                                               win.doLayout();
+                                                                       }, pk);
+                                                               },
+                                                               failure: function (form, action) {
+                                                                       loading_mask.hide();
+                                                               },
+                                                       });
+                                               },
+                                       },
+                               ],
+                       });
+                       if (pk != undefined) {
+                               outer.instance_windows[[model.app_label, model.name, pk]] = win
+                               win.on('close', function(){
+                                       delete outer.instance_windows[[model.app_label, model.name, pk]];
+                               });
+                       }
+                       win.addEvents({
+                               'saved': true,
+                       });
+                       callback(win);
+               };
+
+               if (pk) {
+                       model.create_edit_form(form_callback, pk, {
+                               bodyStyle: 'padding: 10px;',
+                       });
+               } else {
+                       model.create_new_form(form_callback, {
+                               bodyStyle: 'padding: 10px;',
+                       });
+               }
+       },
+       
+});
+
+
+Gilbert.on('ready', function (application) {
+       application.register_plugin('models', new Gilbert.lib.plugins.models.Plugin());
+});
diff --git a/philo/contrib/gilbert/static/gilbert/superboxselect/SuperBoxSelect.js b/philo/contrib/gilbert/static/gilbert/superboxselect/SuperBoxSelect.js
new file mode 100755 (executable)
index 0000000..4087666
--- /dev/null
@@ -0,0 +1,1699 @@
+Ext.namespace('Ext.ux.form');\r
+/**\r
+ * <p>SuperBoxSelect is an extension of the ComboBox component that displays selected items as labelled boxes within the form field. As seen on facebook, hotmail and other sites.</p>\r
+ * <p>The SuperBoxSelect component was inspired by the BoxSelect component found here: http://efattal.fr/en/extjs/extuxboxselect/</p>\r
+ * \r
+ * @author <a href="mailto:dan.humphrey@technomedia.co.uk">Dan Humphrey</a>\r
+ * @class Ext.ux.form.SuperBoxSelect\r
+ * @extends Ext.form.ComboBox\r
+ * @constructor\r
+ * @component\r
+ * @version 1.0\r
+ * @license TBA (To be announced)\r
+ * \r
+ */\r
+Ext.ux.form.SuperBoxSelect = function(config) {\r
+    Ext.ux.form.SuperBoxSelect.superclass.constructor.call(this,config);\r
+    this.addEvents(\r
+        /**\r
+         * Fires before an item is added to the component via user interaction. Return false from the callback function to prevent the item from being added.\r
+         * @event beforeadditem\r
+         * @memberOf Ext.ux.form.SuperBoxSelect\r
+         * @param {SuperBoxSelect} this\r
+         * @param {Mixed} value The value of the item to be added\r
+         */\r
+        'beforeadditem',\r
+\r
+        /**\r
+         * Fires after a new item is added to the component.\r
+         * @event additem\r
+         * @memberOf Ext.ux.form.SuperBoxSelect\r
+         * @param {SuperBoxSelect} this\r
+         * @param {Mixed} value The value of the item which was added\r
+         * @param {Record} record The store record which was added\r
+         */\r
+        'additem',\r
+\r
+        /**\r
+         * Fires when the allowAddNewData config is set to true, and a user attempts to add an item that is not in the data store.\r
+         * @event newitem\r
+         * @memberOf Ext.ux.form.SuperBoxSelect\r
+         * @param {SuperBoxSelect} this\r
+         * @param {Mixed} value The new item's value\r
+         */\r
+        'newitem',\r
+\r
+        /**\r
+         * Fires when an item's remove button is clicked. Return false from the callback function to prevent the item from being removed.\r
+         * @event beforeremoveitem\r
+         * @memberOf Ext.ux.form.SuperBoxSelect\r
+         * @param {SuperBoxSelect} this\r
+         * @param {Mixed} value The value of the item to be removed\r
+         */\r
+        'beforeremoveitem',\r
+\r
+        /**\r
+         * Fires after an item has been removed.\r
+         * @event removeitem\r
+         * @memberOf Ext.ux.form.SuperBoxSelect\r
+         * @param {SuperBoxSelect} this\r
+         * @param {Mixed} value The value of the item which was removed\r
+         * @param {Record} record The store record which was removed\r
+         */\r
+        'removeitem',\r
+        /**\r
+         * Fires after the component values have been cleared.\r
+         * @event clear\r
+         * @memberOf Ext.ux.form.SuperBoxSelect\r
+         * @param {SuperBoxSelect} this\r
+         */\r
+        'clear'\r
+    );\r
+    \r
+};\r
+/**\r
+ * @private hide from doc gen\r
+ */\r
+Ext.ux.form.SuperBoxSelect = Ext.extend(Ext.ux.form.SuperBoxSelect,Ext.form.ComboBox,{\r
+    /**\r
+     * @cfg {Boolean} allowAddNewData When set to true, allows items to be added (via the setValueEx and addItem methods) that do not already exist in the data store. Defaults to false.\r
+     */\r
+    allowAddNewData: false,\r
+\r
+    /**\r
+     * @cfg {Boolean} backspaceDeletesLastItem When set to false, the BACKSPACE key will focus the last selected item. When set to true, the last item will be immediately deleted. Defaults to true.\r
+     */\r
+    backspaceDeletesLastItem: true,\r
+\r
+    /**\r
+     * @cfg {String} classField The underlying data field that will be used to supply an additional class to each item.\r
+     */\r
+    classField: null,\r
+\r
+    /**\r
+     * @cfg {String} clearBtnCls An additional class to add to the in-field clear button.\r
+     */\r
+    clearBtnCls: '',\r
+\r
+    /**\r
+     * @cfg {String/XTemplate} displayFieldTpl A template for rendering the displayField in each selected item. Defaults to null.\r
+     */\r
+    displayFieldTpl: null,\r
+\r
+    /**\r
+     * @cfg {String} extraItemCls An additional css class to apply to each item.\r
+     */\r
+    extraItemCls: '',\r
+\r
+    /**\r
+     * @cfg {String/Object/Function} extraItemStyle Additional css style(s) to apply to each item. Should be a valid argument to Ext.Element.applyStyles.\r
+     */\r
+    extraItemStyle: '',\r
+\r
+    /**\r
+     * @cfg {String} expandBtnCls An additional class to add to the in-field expand button.\r
+     */\r
+    expandBtnCls: '',\r
+\r
+    /**\r
+     * @cfg {Boolean} fixFocusOnTabSelect When set to true, the component will not lose focus when a list item is selected with the TAB key. Defaults to true.\r
+     */\r
+    fixFocusOnTabSelect: true,\r
+    \r
+     /**\r
+     * @cfg {Boolean} forceFormValue When set to true, the component will always return a value to the parent form getValues method, and when the parent form is submitted manually. Defaults to false, meaning the component will only be included in the parent form submission (or getValues) if at least 1 item has been selected.  \r
+     */\r
+    forceFormValue: true,\r
+    /**\r
+     * @cfg {Number} itemDelimiterKey The key code which terminates keying in of individual items, and adds the current\r
+     * item to the list. Defaults to the ENTER key.\r
+     */\r
+    itemDelimiterKey: Ext.EventObject.ENTER,    \r
+    /**\r
+     * @cfg {Boolean} navigateItemsWithTab When set to true the tab key will navigate between selected items. Defaults to true.\r
+     */\r
+    navigateItemsWithTab: true,\r
+\r
+    /**\r
+     * @cfg {Boolean} pinList When set to true the select list will be pinned to allow for multiple selections. Defaults to true.\r
+     */\r
+    pinList: true,\r
+\r
+    /**\r
+     * @cfg {Boolean} preventDuplicates When set to true unique item values will be enforced. Defaults to true.\r
+     */\r
+    preventDuplicates: true,\r
+    \r
+    /**\r
+     * @cfg {String} queryValuesDelimiter Used to delimit multiple values queried from the server when mode is remote.\r
+     */\r
+    queryValuesDelimiter: '|',\r
+    \r
+    /**\r
+     * @cfg {String} queryValuesIndicator A request variable that is sent to the server (as true) to indicate that we are querying values rather than display data (as used in autocomplete) when mode is remote.\r
+     */\r
+    queryValuesIndicator: 'valuesqry',\r
+\r
+    /**\r
+     * @cfg {Boolean} removeValuesFromStore When set to true, selected records will be removed from the store. Defaults to true.\r
+     */\r
+    removeValuesFromStore: true,\r
+\r
+    /**\r
+     * @cfg {String} renderFieldBtns When set to true, will render in-field buttons for clearing the component, and displaying the list for selection. Defaults to true.\r
+     */\r
+    renderFieldBtns: true,\r
+\r
+    /**\r
+     * @cfg {Boolean} stackItems When set to true, the items will be stacked 1 per line. Defaults to false which displays the items inline.\r
+     */\r
+    stackItems: false,\r
+\r
+    /**\r
+     * @cfg {String} styleField The underlying data field that will be used to supply additional css styles to each item.\r
+     */\r
+    styleField : null,\r
+    \r
+     /**\r
+     * @cfg {Boolean} supressClearValueRemoveEvents When true, the removeitem event will not be fired for each item when the clearValue method is called, or when the clear button is used. Defaults to false.\r
+     */\r
+    supressClearValueRemoveEvents : false,\r
+    \r
+    /**\r
+     * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable automatic validation (defaults to 'blur').\r
+     */\r
+       validationEvent : 'blur',\r
+       \r
+    /**\r
+     * @cfg {String} valueDelimiter The delimiter to use when joining and splitting value arrays and strings.\r
+     */\r
+    valueDelimiter: ',',\r
+    initComponent:function() {\r
+       Ext.apply(this, {\r
+            items           : new Ext.util.MixedCollection(false),\r
+            usedRecords     : new Ext.util.MixedCollection(false),\r
+            addedRecords       : [],\r
+            remoteLookup       : [],\r
+            hideTrigger     : true,\r
+            grow            : false,\r
+            resizable       : false,\r
+            multiSelectMode : false,\r
+            preRenderValue  : null\r
+        });\r
+        \r
+        if(this.transform){\r
+            this.doTransform();\r
+        }\r
+        if(this.forceFormValue){\r
+               this.items.on({\r
+                  add: this.manageNameAttribute,\r
+                  remove: this.manageNameAttribute,\r
+                  clear: this.manageNameAttribute,\r
+                  scope: this\r
+               });\r
+        }\r
+        \r
+        Ext.ux.form.SuperBoxSelect.superclass.initComponent.call(this);\r
+        if(this.mode === 'remote' && this.store){\r
+               this.store.on('load', this.onStoreLoad, this);\r
+        }\r
+    },\r
+    onRender:function(ct, position) {\r
+       var h = this.hiddenName;\r
+       this.hiddenName = null;\r
+        Ext.ux.form.SuperBoxSelect.superclass.onRender.call(this, ct, position);\r
+        this.hiddenName = h;\r
+        this.manageNameAttribute();\r
+       \r
+        var extraClass = (this.stackItems === true) ? 'x-superboxselect-stacked' : '';\r
+        if(this.renderFieldBtns){\r
+            extraClass += ' x-superboxselect-display-btns';\r
+        }\r
+        this.el.removeClass('x-form-text').addClass('x-superboxselect-input-field');\r
+        \r
+        this.wrapEl = this.el.wrap({\r
+            tag : 'ul'\r
+        });\r
+        \r
+        this.outerWrapEl = this.wrapEl.wrap({\r
+            tag : 'div',\r
+            cls: 'x-form-text x-superboxselect ' + extraClass\r
+        });\r
+       \r
+        this.inputEl = this.el.wrap({\r
+            tag : 'li',\r
+            cls : 'x-superboxselect-input'\r
+        });\r
+        \r
+        if(this.renderFieldBtns){\r
+            this.setupFieldButtons().manageClearBtn();\r
+        }\r
+        \r
+        this.setupFormInterception();\r
+    },\r
+    onStoreLoad : function(store, records, options){\r
+       //accomodating for bug in Ext 3.0.0 where options.params are empty\r
+       var q = options.params[this.queryParam] || store.baseParams[this.queryParam] || "",\r
+               isValuesQuery = options.params[this.queryValuesIndicator] || store.baseParams[this.queryValuesIndicator];\r
+       \r
+       if(this.removeValuesFromStore){\r
+               this.store.each(function(record) {\r
+                               if(this.usedRecords.containsKey(record.get(this.valueField))){\r
+                                       this.store.remove(record);\r
+                               }\r
+                       }, this);\r
+       }\r
+       //queried values\r
+       if(isValuesQuery){\r
+               var params = q.split(this.queryValuesDelimiter);\r
+               Ext.each(params,function(p){\r
+                       this.remoteLookup.remove(p);\r
+                       var rec = this.findRecord(this.valueField,p);\r
+                       if(rec){\r
+                               this.addRecord(rec);\r
+                       }\r
+               },this);\r
+               \r
+               if(this.setOriginal){\r
+                       this.setOriginal = false;\r
+                       this.originalValue = this.getValue();\r
+               }\r
+       }\r
+\r
+       //queried display (autocomplete) & addItem\r
+       if(q !== '' && this.allowAddNewData){\r
+               Ext.each(this.remoteLookup,function(r){\r
+                       if(typeof r == "object" && r[this.displayField] == q){\r
+                               this.remoteLookup.remove(r);\r
+                                       if(records.length && records[0].get(this.displayField) === q) {\r
+                                               this.addRecord(records[0]);\r
+                                               return;\r
+                                       }\r
+                                       var rec = this.createRecord(r);\r
+                                       this.store.add(rec);\r
+                               this.addRecord(rec);\r
+                               this.addedRecords.push(rec); //keep track of records added to store\r
+                               (function(){\r
+                                       if(this.isExpanded()){\r
+                                               this.collapse();\r
+                                       }\r
+                               }).defer(10,this);\r
+                               return;\r
+                       }\r
+               },this);\r
+       }\r
+       \r
+       var toAdd = [];\r
+       if(q === ''){\r
+               Ext.each(this.addedRecords,function(rec){\r
+                       if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){\r
+                                       return;                         \r
+                       }\r
+                       toAdd.push(rec);\r
+                       \r
+               },this);\r
+               \r
+       }else{\r
+               var re = new RegExp(Ext.escapeRe(q) + '.*','i');\r
+               Ext.each(this.addedRecords,function(rec){\r
+                       if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){\r
+                                       return;                         \r
+                       }\r
+                       if(re.test(rec.get(this.displayField))){\r
+                               toAdd.push(rec);\r
+                       }\r
+               },this);\r
+           }\r
+       this.store.add(toAdd);\r
+       this.store.sort(this.displayField, 'ASC');\r
+       \r
+               if(this.store.getCount() === 0 && this.isExpanded()){\r
+                       this.collapse();\r
+               }\r
+               \r
+       },\r
+    doTransform : function() {\r
+       var s = Ext.getDom(this.transform), transformValues = [];\r
+            if(!this.store){\r
+                this.mode = 'local';\r
+                var d = [], opts = s.options;\r
+                for(var i = 0, len = opts.length;i < len; i++){\r
+                    var o = opts[i], oe = Ext.get(o),\r
+                        value = oe.getAttributeNS(null,'value') || '',\r
+                        cls = oe.getAttributeNS(null,'className') || '',\r
+                        style = oe.getAttributeNS(null,'style') || '';\r
+                    if(o.selected) {\r
+                        transformValues.push(value);\r
+                    }\r
+                    d.push([value, o.text, cls, typeof(style) === "string" ? style : style.cssText]);\r
+                }\r
+                this.store = new Ext.data.SimpleStore({\r
+                    'id': 0,\r
+                    fields: ['value', 'text', 'cls', 'style'],\r
+                    data : d\r
+                });\r
+                Ext.apply(this,{\r
+                    valueField: 'value',\r
+                    displayField: 'text',\r
+                    classField: 'cls',\r
+                    styleField: 'style'\r
+                });\r
+            }\r
+           \r
+            if(transformValues.length){\r
+                this.value = transformValues.join(',');\r
+            }\r
+    },\r
+    setupFieldButtons : function(){\r
+        this.buttonWrap = this.outerWrapEl.createChild({\r
+            cls: 'x-superboxselect-btns'\r
+        });\r
+        \r
+        this.buttonClear = this.buttonWrap.createChild({\r
+            tag:'div',\r
+            cls: 'x-superboxselect-btn-clear ' + this.clearBtnCls\r
+        });\r
+        \r
+        this.buttonExpand = this.buttonWrap.createChild({\r
+            tag:'div',\r
+            cls: 'x-superboxselect-btn-expand ' + this.expandBtnCls\r
+        });\r
+        \r
+        this.initButtonEvents();\r
+        \r
+        return this;\r
+    },\r
+    initButtonEvents : function() {\r
+        this.buttonClear.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {\r
+            e.stopEvent();\r
+            if (this.disabled) {\r
+                return;\r
+            }\r
+            this.clearValue();\r
+            this.el.focus();\r
+        }, this);\r
+\r
+        this.buttonExpand.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {\r
+            e.stopEvent();\r
+            if (this.disabled) {\r
+                return;\r
+            }\r
+            if (this.isExpanded()) {\r
+                this.multiSelectMode = false;\r
+            } else if (this.pinList) {\r
+                this.multiSelectMode = true;\r
+            }\r
+            this.onTriggerClick();\r
+        }, this);\r
+    },\r
+    removeButtonEvents : function() {\r
+        this.buttonClear.removeAllListeners();\r
+        this.buttonExpand.removeAllListeners();\r
+        return this;\r
+    },\r
+    clearCurrentFocus : function(){\r
+        if(this.currentFocus){\r
+            this.currentFocus.onLnkBlur();\r
+            this.currentFocus = null;\r
+        }  \r
+        return this;        \r
+    },\r
+    initEvents : function() {\r
+        var el = this.el;\r
+\r
+        el.on({\r
+            click   : this.onClick,\r
+            focus   : this.clearCurrentFocus,\r
+            blur    : this.onBlur,\r
+\r
+            keydown : this.onKeyDownHandler,\r
+            keyup   : this.onKeyUpBuffered,\r
+\r
+            scope   : this\r
+        });\r
+\r
+        this.on({\r
+            collapse: this.onCollapse,\r
+            expand: this.clearCurrentFocus,\r
+            scope: this\r
+        });\r
+\r
+        this.wrapEl.on('click', this.onWrapClick, this);\r
+        this.outerWrapEl.on('click', this.onWrapClick, this);\r
+        \r
+        this.inputEl.focus = function() {\r
+            el.focus();\r
+        };\r
+\r
+        Ext.ux.form.SuperBoxSelect.superclass.initEvents.call(this);\r
+\r
+        Ext.apply(this.keyNav, {\r
+            tab: function(e) {\r
+                if (this.fixFocusOnTabSelect && this.isExpanded()) {\r
+                    e.stopEvent();\r
+                    el.blur();\r
+                    this.onViewClick(false);\r
+                    this.focus(false, 10);\r
+                    return true;\r
+                }\r
+\r
+                this.onViewClick(false);\r
+                if (el.dom.value !== '') {\r
+                    this.setRawValue('');\r
+                }\r
+\r
+                return true;\r
+            },\r
+\r
+            down: function(e) {\r
+                if (!this.isExpanded() && !this.currentFocus) {\r
+                    this.onTriggerClick();\r
+                } else {\r
+                    this.inKeyMode = true;\r
+                    this.selectNext();\r
+                }\r
+            },\r
+\r
+            enter: function(){}\r
+        });\r
+    },\r
+\r
+    onClick: function() {\r
+        this.clearCurrentFocus();\r
+        this.collapse();\r
+        this.autoSize();\r
+    },\r
+\r
+    beforeBlur: Ext.form.ComboBox.superclass.beforeBlur,\r
+\r
+    onFocus: function() {\r
+        this.outerWrapEl.addClass(this.focusClass);\r
+\r
+        Ext.ux.form.SuperBoxSelect.superclass.onFocus.call(this);\r
+    },\r
+\r
+    onBlur: function() {\r
+        this.outerWrapEl.removeClass(this.focusClass);\r
+\r
+        this.clearCurrentFocus();\r
+\r
+        if (this.el.dom.value !== '') {\r
+            this.applyEmptyText();\r
+            this.autoSize();\r
+        }\r
+\r
+        Ext.ux.form.SuperBoxSelect.superclass.onBlur.call(this);\r
+    },\r
+\r
+    onCollapse: function() {\r
+       this.view.clearSelections();\r
+        this.multiSelectMode = false;\r
+    },\r
+\r
+    onWrapClick: function(e) {\r
+        e.stopEvent();\r
+        this.collapse();\r
+        this.el.focus();\r
+        this.clearCurrentFocus();\r
+    },\r
+    markInvalid : function(msg) {\r
+        var elp, t;\r
+\r
+        if (!this.rendered || this.preventMark ) {\r
+            return;\r
+        }\r
+        this.outerWrapEl.addClass(this.invalidClass);\r
+        msg = msg || this.invalidText;\r
+\r
+        switch (this.msgTarget) {\r
+            case 'qtip':\r
+                Ext.apply(this.el.dom, {\r
+                    qtip    : msg,\r
+                    qclass  : 'x-form-invalid-tip'\r
+                });\r
+                Ext.apply(this.wrapEl.dom, {\r
+                    qtip    : msg,\r
+                    qclass  : 'x-form-invalid-tip'\r
+                });\r
+                if (Ext.QuickTips) { // fix for floating editors interacting with DND\r
+                    Ext.QuickTips.enable();\r
+                }\r
+                break;\r
+            case 'title':\r
+                this.el.dom.title = msg;\r
+                this.wrapEl.dom.title = msg;\r
+                this.outerWrapEl.dom.title = msg;\r
+                break;\r
+            case 'under':\r
+                if (!this.errorEl) {\r
+                    elp = this.getErrorCt();\r
+                    if (!elp) { // field has no container el\r
+                        this.el.dom.title = msg;\r
+                        break;\r
+                    }\r
+                    this.errorEl = elp.createChild({cls:'x-form-invalid-msg'});\r
+                    this.errorEl.setWidth(elp.getWidth(true) - 20);\r
+                }\r
+                this.errorEl.update(msg);\r
+                Ext.form.Field.msgFx[this.msgFx].show(this.errorEl, this);\r
+                break;\r
+            case 'side':\r
+                if (!this.errorIcon) {\r
+                    elp = this.getErrorCt();\r
+                    if (!elp) { // field has no container el\r
+                        this.el.dom.title = msg;\r
+                        break;\r
+                    }\r
+                    this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});\r
+                }\r
+                this.alignErrorIcon();\r
+                Ext.apply(this.errorIcon.dom, {\r
+                    qtip    : msg,\r
+                    qclass  : 'x-form-invalid-tip'\r
+                });\r
+                this.errorIcon.show();\r
+                this.on('resize', this.alignErrorIcon, this);\r
+                break;\r
+            default:\r
+                t = Ext.getDom(this.msgTarget);\r
+                t.innerHTML = msg;\r
+                t.style.display = this.msgDisplay;\r
+                break;\r
+        }\r
+        this.fireEvent('invalid', this, msg);\r
+    },\r
+    clearInvalid : function(){\r
+        if(!this.rendered || this.preventMark){ // not rendered\r
+            return;\r
+        }\r
+        this.outerWrapEl.removeClass(this.invalidClass);\r
+        switch(this.msgTarget){\r
+            case 'qtip':\r
+                this.el.dom.qtip = '';\r
+                this.wrapEl.dom.qtip ='';\r
+                break;\r
+            case 'title':\r
+                this.el.dom.title = '';\r
+                this.wrapEl.dom.title = '';\r
+                this.outerWrapEl.dom.title = '';\r
+                break;\r
+            case 'under':\r
+                if(this.errorEl){\r
+                    Ext.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);\r
+                }\r
+                break;\r
+            case 'side':\r
+                if(this.errorIcon){\r
+                    this.errorIcon.dom.qtip = '';\r
+                    this.errorIcon.hide();\r
+                    this.un('resize', this.alignErrorIcon, this);\r
+                }\r
+                break;\r
+            default:\r
+                var t = Ext.getDom(this.msgTarget);\r
+                t.innerHTML = '';\r
+                t.style.display = 'none';\r
+                break;\r
+        }\r
+        this.fireEvent('valid', this);\r
+    },\r
+    alignErrorIcon : function(){\r
+        if(this.wrap){\r
+            this.errorIcon.alignTo(this.wrap, 'tl-tr', [Ext.isIE ? 5 : 2, 3]);\r
+        }\r
+    },\r
+    expand : function(){\r
+        if (this.isExpanded() || !this.hasFocus) {\r
+            return;\r
+        }\r
+        this.list.alignTo(this.outerWrapEl, this.listAlign).show();\r
+        this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac\r
+        Ext.getDoc().on({\r
+            mousewheel: this.collapseIf,\r
+            mousedown: this.collapseIf,\r
+            scope: this\r
+        });\r
+        this.fireEvent('expand', this);\r
+    },\r
+    restrictHeight : function(){\r
+        var inner = this.innerList.dom,\r
+            st = inner.scrollTop, \r
+            list = this.list;\r
+        \r
+        inner.style.height = '';\r
+        \r
+        var pad = list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight,\r
+            h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),\r
+            ha = this.getPosition()[1]-Ext.getBody().getScroll().top,\r
+            hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,\r
+            space = Math.max(ha, hb, this.minHeight || 0)-list.shadowOffset-pad-5;\r
+        \r
+        h = Math.min(h, space, this.maxHeight);\r
+        this.innerList.setHeight(h);\r
+\r
+        list.beginUpdate();\r
+        list.setHeight(h+pad);\r
+        list.alignTo(this.outerWrapEl, this.listAlign);\r
+        list.endUpdate();\r
+        \r
+        if(this.multiSelectMode){\r
+            inner.scrollTop = st;\r
+        }\r
+    },\r
+    \r
+    validateValue: function(val){\r
+        if(this.items.getCount() === 0){\r
+             if(this.allowBlank){\r
+                 this.clearInvalid();\r
+                 return true;\r
+             }else{\r
+                 this.markInvalid(this.blankText);\r
+                 return false;\r
+             }\r
+        }\r
+        \r
+        this.clearInvalid();\r
+        return true;\r
+    },\r
+\r
+    manageNameAttribute :  function(){\r
+       if(this.items.getCount() === 0 && this.forceFormValue){\r
+          this.el.dom.setAttribute('name', this.hiddenName || this.name);\r
+       }else{\r
+               this.el.dom.removeAttribute('name');\r
+       }\r
+    },\r
+    setupFormInterception : function(){\r
+        var form;\r
+        this.findParentBy(function(p){ \r
+            if(p.getForm){\r
+                form = p.getForm();\r
+            }\r
+        });\r
+        if(form){\r
+               \r
+               var formGet = form.getValues;\r
+            form.getValues = function(asString){\r
+                this.el.dom.disabled = true;\r
+                var oldVal = this.el.dom.value;\r
+                this.setRawValue('');\r
+                var vals = formGet.call(form);\r
+                this.el.dom.disabled = false;\r
+                this.setRawValue(oldVal);\r
+                if(this.forceFormValue && this.items.getCount() === 0){\r
+                       vals[this.name] = '';\r
+                }\r
+                return asString ? Ext.urlEncode(vals) : vals ;\r
+            }.createDelegate(this);\r
+        }\r
+    },\r
+    onResize : function(w, h, rw, rh) {\r
+        var reduce = Ext.isIE6 ? 4 : Ext.isIE7 ? 1 : Ext.isIE8 ? 1 : 0;\r
+        if(this.wrapEl){\r
+            this._width = w;\r
+            this.outerWrapEl.setWidth(w - reduce);\r
+            if (this.renderFieldBtns) {\r
+                reduce += (this.buttonWrap.getWidth() + 20);\r
+                this.wrapEl.setWidth(w - reduce);\r
+        }\r
+        }\r
+        Ext.ux.form.SuperBoxSelect.superclass.onResize.call(this, w, h, rw, rh);\r
+        this.autoSize();\r
+    },\r
+    onEnable: function(){\r
+        Ext.ux.form.SuperBoxSelect.superclass.onEnable.call(this);\r
+        this.items.each(function(item){\r
+            item.enable();\r
+        });\r
+        if (this.renderFieldBtns) {\r
+            this.initButtonEvents();\r
+        }\r
+    },\r
+    onDisable: function(){\r
+        Ext.ux.form.SuperBoxSelect.superclass.onDisable.call(this);\r
+        this.items.each(function(item){\r
+            item.disable();\r
+        });\r
+        if(this.renderFieldBtns){\r
+            this.removeButtonEvents();\r
+        }\r
+    },\r
+    /**\r
+     * Clears all values from the component.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name clearValue\r
+     * @param {Boolean} supressRemoveEvent [Optional] When true, the 'removeitem' event will not fire for each item that is removed.    \r
+     */\r
+    clearValue : function(supressRemoveEvent){\r
+        Ext.ux.form.SuperBoxSelect.superclass.clearValue.call(this);\r
+        this.preventMultipleRemoveEvents = supressRemoveEvent || this.supressClearValueRemoveEvents || false;\r
+       this.removeAllItems();\r
+       this.preventMultipleRemoveEvents = false;\r
+        this.fireEvent('clear',this);\r
+        return this;\r
+    },\r
+    onKeyUp : function(e) {\r
+        if (this.editable !== false && (!e.isSpecialKey() || e.getKey() === e.BACKSPACE) && e.getKey() !== this.itemDelimiterKey && (!e.hasModifier() || e.shiftKey)) {\r
+            this.lastKey = e.getKey();\r
+            this.dqTask.delay(this.queryDelay);\r
+        }        \r
+    },\r
+    onKeyDownHandler : function(e,t) {\r
+               \r
+        var toDestroy,nextFocus,idx;\r
+        if ((e.getKey() === e.DELETE || e.getKey() === e.SPACE) && this.currentFocus){\r
+            e.stopEvent();\r
+            toDestroy = this.currentFocus;\r
+            this.on('expand',function(){this.collapse();},this,{single: true});\r
+            idx = this.items.indexOfKey(this.currentFocus.key);\r
+            \r
+            this.clearCurrentFocus();\r
+            \r
+            if(idx < (this.items.getCount() -1)){\r
+                nextFocus = this.items.itemAt(idx+1);\r
+            }\r
+            \r
+            toDestroy.preDestroy(true);\r
+            if(nextFocus){\r
+                (function(){\r
+                    nextFocus.onLnkFocus();\r
+                    this.currentFocus = nextFocus;\r
+                }).defer(200,this);\r
+            }\r
+        \r
+            return true;\r
+        }\r
+        \r
+        var val = this.el.dom.value, it, ctrl = e.ctrlKey;\r
+        if(e.getKey() === this.itemDelimiterKey){\r
+            e.stopEvent();\r
+            if (val !== "") {\r
+                if (ctrl || !this.isExpanded())  {  //ctrl+enter for new items\r
+                       this.view.clearSelections();\r
+                    this.collapse();\r
+                    this.setRawValue('');\r
+                    this.fireEvent('newitem', this, val);\r
+                }\r
+                else {\r
+                       this.onViewClick();\r
+                    //removed from 3.0.1\r
+                    if(this.unsetDelayCheck){\r
+                        this.delayedCheck = true;\r
+                        this.unsetDelayCheck.defer(10, this);\r
+                    }\r
+                }\r
+            }else{\r
+                if(!this.isExpanded()){\r
+                    return;\r
+                }\r
+                this.onViewClick();\r
+                //removed from 3.0.1\r
+                if(this.unsetDelayCheck){\r
+                    this.delayedCheck = true;\r
+                    this.unsetDelayCheck.defer(10, this);\r
+                }\r
+            }\r
+            return true;\r
+        }\r
+        \r
+        if(val !== '') {\r
+            this.autoSize();\r
+            return;\r
+        }\r
+        \r
+        //select first item\r
+        if(e.getKey() === e.HOME){\r
+            e.stopEvent();\r
+            if(this.items.getCount() > 0){\r
+                this.collapse();\r
+                it = this.items.get(0);\r
+                it.el.focus();\r
+                \r
+            }\r
+            return true;\r
+        }\r
+        //backspace remove\r
+        if(e.getKey() === e.BACKSPACE){\r
+            e.stopEvent();\r
+            if(this.currentFocus) {\r
+                toDestroy = this.currentFocus;\r
+                this.on('expand',function(){\r
+                    this.collapse();\r
+                },this,{single: true});\r
+                \r
+                idx = this.items.indexOfKey(toDestroy.key);\r
+                \r
+                this.clearCurrentFocus();\r
+                if(idx < (this.items.getCount() -1)){\r
+                    nextFocus = this.items.itemAt(idx+1);\r
+                }\r
+                \r
+                toDestroy.preDestroy(true);\r
+                \r
+                if(nextFocus){\r
+                    (function(){\r
+                        nextFocus.onLnkFocus();\r
+                        this.currentFocus = nextFocus;\r
+                    }).defer(200,this);\r
+                }\r
+                \r
+                return;\r
+            }else{\r
+                it = this.items.get(this.items.getCount() -1);\r
+                if(it){\r
+                    if(this.backspaceDeletesLastItem){\r
+                        this.on('expand',function(){this.collapse();},this,{single: true});\r
+                        it.preDestroy(true);\r
+                    }else{\r
+                        if(this.navigateItemsWithTab){\r
+                            it.onElClick();\r
+                        }else{\r
+                            this.on('expand',function(){\r
+                                this.collapse();\r
+                                this.currentFocus = it;\r
+                                this.currentFocus.onLnkFocus.defer(20,this.currentFocus);\r
+                            },this,{single: true});\r
+                        }\r
+                    }\r
+                }\r
+                return true;\r
+            }\r
+        }\r
+        \r
+        if(!e.isNavKeyPress()){\r
+            this.multiSelectMode = false;\r
+            this.clearCurrentFocus();\r
+            return;\r
+        }\r
+        //arrow nav\r
+        if(e.getKey() === e.LEFT || (e.getKey() === e.UP && !this.isExpanded())){\r
+            e.stopEvent();\r
+            this.collapse();\r
+            //get last item\r
+            it = this.items.get(this.items.getCount()-1);\r
+            if(this.navigateItemsWithTab){ \r
+                //focus last el\r
+                if(it){\r
+                    it.focus(); \r
+                }\r
+            }else{\r
+                //focus prev item\r
+                if(this.currentFocus){\r
+                    idx = this.items.indexOfKey(this.currentFocus.key);\r
+                    this.clearCurrentFocus();\r
+                    \r
+                    if(idx !== 0){\r
+                        this.currentFocus = this.items.itemAt(idx-1);\r
+                        this.currentFocus.onLnkFocus();\r
+                    }\r
+                }else{\r
+                    this.currentFocus = it;\r
+                    if(it){\r
+                        it.onLnkFocus();\r
+                    }\r
+                }\r
+            }\r
+            return true;\r
+        }\r
+        if(e.getKey() === e.DOWN){\r
+            if(this.currentFocus){\r
+                this.collapse();\r
+                e.stopEvent();\r
+                idx = this.items.indexOfKey(this.currentFocus.key);\r
+                if(idx == (this.items.getCount() -1)){\r
+                    this.clearCurrentFocus.defer(10,this);\r
+                }else{\r
+                    this.clearCurrentFocus();\r
+                    this.currentFocus = this.items.itemAt(idx+1);\r
+                    if(this.currentFocus){\r
+                        this.currentFocus.onLnkFocus();\r
+                    }\r
+                }\r
+                return true;\r
+            }\r
+        }\r
+        if(e.getKey() === e.RIGHT){\r
+            this.collapse();\r
+            it = this.items.itemAt(0);\r
+            if(this.navigateItemsWithTab){ \r
+                //focus first el\r
+                if(it){\r
+                    it.focus(); \r
+                }\r
+            }else{\r
+                if(this.currentFocus){\r
+                    idx = this.items.indexOfKey(this.currentFocus.key);\r
+                    this.clearCurrentFocus();\r
+                    if(idx < (this.items.getCount() -1)){\r
+                        this.currentFocus = this.items.itemAt(idx+1);\r
+                        if(this.currentFocus){\r
+                            this.currentFocus.onLnkFocus();\r
+                        }\r
+                    }\r
+                }else{\r
+                    this.currentFocus = it;\r
+                    if(it){\r
+                        it.onLnkFocus();\r
+                    }\r
+                }\r
+            }\r
+        }\r
+    },\r
+    onKeyUpBuffered : function(e){\r
+        if(!e.isNavKeyPress()){\r
+            this.autoSize();\r
+        }\r
+    },\r
+    reset :  function(){\r
+       this.killItems();\r
+        Ext.ux.form.SuperBoxSelect.superclass.reset.call(this);\r
+        this.addedRecords = [];\r
+        this.autoSize().setRawValue('');\r
+    },\r
+    applyEmptyText : function(){\r
+               this.setRawValue('');\r
+        if(this.items.getCount() > 0){\r
+            this.el.removeClass(this.emptyClass);\r
+            this.setRawValue('');\r
+            return this;\r
+        }\r
+        if(this.rendered && this.emptyText && this.getRawValue().length < 1){\r
+            this.setRawValue(this.emptyText);\r
+            this.el.addClass(this.emptyClass);\r
+        }\r
+        return this;\r
+    },\r
+    /**\r
+     * @private\r
+     * \r
+     * Use clearValue instead\r
+     */\r
+    removeAllItems: function(){\r
+       this.items.each(function(item){\r
+            item.preDestroy(true);\r
+        },this);\r
+        this.manageClearBtn();\r
+        return this;\r
+    },\r
+    killItems : function(){\r
+       this.items.each(function(item){\r
+            item.kill();\r
+        },this);\r
+        this.resetStore();\r
+        this.items.clear();\r
+        this.manageClearBtn();\r
+        return this;\r
+    },\r
+    resetStore: function(){\r
+        this.store.clearFilter();\r
+        if(!this.removeValuesFromStore){\r
+            return this;\r
+        }\r
+        this.usedRecords.each(function(rec){\r
+            this.store.add(rec);\r
+        },this);\r
+        this.usedRecords.clear();\r
+        this.sortStore();\r
+        return this;\r
+    },\r
+    sortStore: function(){\r
+        var ss = this.store.getSortState();\r
+        if(ss && ss.field){\r
+            this.store.sort(ss.field, ss.direction);\r
+        }\r
+        return this;\r
+    },\r
+    getCaption: function(dataObject){\r
+        if(typeof this.displayFieldTpl === 'string') {\r
+            this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl);\r
+        }\r
+        var caption, recordData = dataObject instanceof Ext.data.Record ? dataObject.data : dataObject;\r
+      \r
+        if(this.displayFieldTpl) {\r
+            caption = this.displayFieldTpl.apply(recordData);\r
+        } else if(this.displayField) {\r
+            caption = recordData[this.displayField];\r
+        }\r
+        \r
+        return caption;\r
+    },\r
+    addRecord : function(record) {\r
+        var display = record.data[this.displayField],\r
+            caption = this.getCaption(record),\r
+            val = record.data[this.valueField],\r
+            cls = this.classField ? record.data[this.classField] : '',\r
+            style = this.styleField ? record.data[this.styleField] : '';\r
+\r
+        if (this.removeValuesFromStore) {\r
+            this.usedRecords.add(val, record);\r
+            this.store.remove(record);\r
+        }\r
+        \r
+        this.addItemBox(val, display, caption, cls, style);\r
+        this.fireEvent('additem', this, val, record);\r
+    },\r
+    createRecord : function(recordData){\r
+        if(!this.recordConstructor){\r
+            var recordFields = [\r
+                {name: this.valueField},\r
+                {name: this.displayField}\r
+            ];\r
+            if(this.classField){\r
+                recordFields.push({name: this.classField});\r
+            }\r
+            if(this.styleField){\r
+                recordFields.push({name: this.styleField});\r
+            }\r
+            this.recordConstructor = Ext.data.Record.create(recordFields);\r
+        }\r
+        return new this.recordConstructor(recordData);\r
+    },\r
+    /**\r
+     * Adds an array of items to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name addItem\r
+     * @param {Array} newItemObjects An Array of object literals containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} \r
+     */\r
+    addItems : function(newItemObjects){\r
+       if (Ext.isArray(newItemObjects)) {\r
+                       Ext.each(newItemObjects, function(item) {\r
+                               this.addItem(item);\r
+                       }, this);\r
+               } else {\r
+                       this.addItem(newItemObjects);\r
+               }\r
+    },\r
+    /**\r
+     * Adds a new non-existing item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
+     * This method should be used in place of addItem from within the newitem event handler.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name addNewItem\r
+     * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} \r
+     */\r
+    addNewItem : function(newItemObject){\r
+       this.addItem(newItemObject,true);\r
+    },\r
+    /**\r
+     * Adds an item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name addItem\r
+     * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} \r
+     */\r
+    addItem : function(newItemObject, /*hidden param*/ forcedAdd){\r
+        \r
+        var val = newItemObject[this.valueField];\r
+\r
+        if(this.disabled) {\r
+            return false;\r
+        }\r
+        if(this.preventDuplicates && this.hasValue(val)){\r
+            return;\r
+        }\r
+        \r
+        //use existing record if found\r
+        var record = this.findRecord(this.valueField, val);\r
+        if (record) {\r
+            this.addRecord(record);\r
+            return;\r
+        } else if (!this.allowAddNewData) { // else it's a new item\r
+            return;\r
+        }\r
+        \r
+        if(this.mode === 'remote'){\r
+               this.remoteLookup.push(newItemObject); \r
+               this.doQuery(val,false,false,forcedAdd);\r
+               return;\r
+        }\r
+        \r
+        var rec = this.createRecord(newItemObject);\r
+        this.store.add(rec);\r
+        this.addRecord(rec);\r
+        \r
+        return true;\r
+    },\r
+    addItemBox : function(itemVal,itemDisplay,itemCaption, itemClass, itemStyle) {\r
+        var hConfig, parseStyle = function(s){\r
+            var ret = '';\r
+            if(typeof s == 'function'){\r
+                ret = s.call();\r
+            }else if(typeof s == 'object'){\r
+                for(var p in s){\r
+                    ret+= p +':'+s[p]+';';\r
+                }\r
+            }else if(typeof s == 'string'){\r
+                ret = s + ';';\r
+            }\r
+            return ret;\r
+        }, itemKey = Ext.id(null,'sbx-item'), box = new Ext.ux.form.SuperBoxSelectItem({\r
+            owner: this,\r
+            disabled: this.disabled,\r
+            renderTo: this.wrapEl,\r
+            cls: this.extraItemCls + ' ' + itemClass,\r
+            style: parseStyle(this.extraItemStyle) + ' ' + itemStyle,\r
+            caption: itemCaption,\r
+            display: itemDisplay,\r
+            value:  itemVal,\r
+            key: itemKey,\r
+            listeners: {\r
+                'remove': function(item){\r
+                    if(this.fireEvent('beforeremoveitem',this,item.value) === false){\r
+                        return;\r
+                    }\r
+                    this.items.removeKey(item.key);\r
+                    if(this.removeValuesFromStore){\r
+                        if(this.usedRecords.containsKey(item.value)){\r
+                            this.store.add(this.usedRecords.get(item.value));\r
+                            this.usedRecords.removeKey(item.value);\r
+                            this.sortStore();\r
+                            if(this.view){\r
+                                this.view.render();\r
+                            }\r
+                        }\r
+                    }\r
+                    if(!this.preventMultipleRemoveEvents){\r
+                       this.fireEvent.defer(250,this,['removeitem',this,item.value, this.findInStore(item.value)]);\r
+                    }\r
+                },\r
+                destroy: function(){\r
+                    this.collapse();\r
+                    this.autoSize().manageClearBtn().validateValue();\r
+                },\r
+                scope: this\r
+            }\r
+        });\r
+        box.render();\r
+        \r
+        hConfig = {\r
+            tag :'input', \r
+            type :'hidden', \r
+            value : itemVal,\r
+            name : (this.hiddenName || this.name)\r
+        };\r
+        \r
+        if(this.disabled){\r
+               Ext.apply(hConfig,{\r
+                  disabled : 'disabled'\r
+               })\r
+        }\r
+        box.hidden = this.el.insertSibling(hConfig,'before');\r
+\r
+        this.items.add(itemKey,box);\r
+        this.applyEmptyText().autoSize().manageClearBtn().validateValue();\r
+    },\r
+    manageClearBtn : function() {\r
+        if (!this.renderFieldBtns || !this.rendered) {\r
+            return this;\r
+        }\r
+        var cls = 'x-superboxselect-btn-hide';\r
+        if (this.items.getCount() === 0) {\r
+            this.buttonClear.addClass(cls);\r
+        } else {\r
+            this.buttonClear.removeClass(cls);\r
+        }\r
+        return this;\r
+    },\r
+    findInStore : function(val){\r
+        var index = this.store.find(this.valueField, val);\r
+        if(index > -1){\r
+            return this.store.getAt(index);\r
+        }\r
+        return false;\r
+    },\r
+    /**\r
+     * Returns a String value containing a concatenated list of item values. The list is concatenated with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter}.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name getValue\r
+     * @return {String} a String value containing a concatenated list of item values. \r
+     */\r
+    getValue : function() {\r
+        var ret = [];\r
+        this.items.each(function(item){\r
+            ret.push(item.value);\r
+        });\r
+        return ret.join(this.valueDelimiter);\r
+    },\r
+    /**\r
+     * Returns an Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField}, {@link #Ext.ux.form.SuperBoxSelect-classField} and {@link #Ext.ux.form.SuperBoxSelect-styleField} properties.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name getValueEx\r
+     * @return {Array} an array of item objects. \r
+     */\r
+    getValueEx : function() {\r
+        var ret = [];\r
+        this.items.each(function(item){\r
+            var newItem = {};\r
+            newItem[this.valueField] = item.value;\r
+            newItem[this.displayField] = item.display;\r
+            if(this.classField){\r
+                newItem[this.classField] = item.cls || '';\r
+            }\r
+            if(this.styleField){\r
+                newItem[this.styleField] = item.style || '';\r
+            }\r
+            ret.push(newItem);\r
+        },this);\r
+        return ret;\r
+    },\r
+    // private\r
+    initValue : function(){\r
\r
+        Ext.ux.form.SuperBoxSelect.superclass.initValue.call(this);\r
+        if(this.mode === 'remote') {\r
+               this.setOriginal = true;\r
+        }\r
+    },\r
+    /**\r
+     * Sets the value of the SuperBoxSelect component.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name setValue\r
+     * @param {String|Array} value An array of item values, or a String value containing a delimited list of item values. (The list should be delimited with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter) \r
+     */\r
+    setValue : function(value){\r
+        if(!this.rendered){\r
+            this.value = value;\r
+            return;\r
+        }\r
+            \r
+        this.removeAllItems().resetStore();\r
+        this.remoteLookup = [];\r
+        \r
+        if(Ext.isEmpty(value)){\r
+               return;\r
+        }\r
+        \r
+        var values = value;\r
+        if(!Ext.isArray(value)){\r
+            value = '' + value;\r
+            values = value.split(this.valueDelimiter); \r
+        }\r
+        \r
+        Ext.each(values,function(val){\r
+            var record = this.findRecord(this.valueField, val);\r
+            if(record){\r
+                this.addRecord(record);\r
+            }else if(this.mode === 'remote'){\r
+                               this.remoteLookup.push(val);                    \r
+            }\r
+        },this);\r
+        \r
+        if(this.mode === 'remote'){\r
+               var q = this.remoteLookup.join(this.queryValuesDelimiter); \r
+               this.doQuery(q,false, true); //3rd param to specify a values query\r
+        }\r
+        \r
+    },\r
+    /**\r
+     * Sets the value of the SuperBoxSelect component, adding new items that don't exist in the data store if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name setValue\r
+     * @param {Array} data An Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} properties.  \r
+     */\r
+    setValueEx : function(data){\r
+        this.removeAllItems().resetStore();\r
+        \r
+        if(!Ext.isArray(data)){\r
+            data = [data];\r
+        }\r
+        this.remoteLookup = [];\r
+        \r
+        if(this.allowAddNewData && this.mode === 'remote'){ // no need to query\r
+            Ext.each(data, function(d){\r
+               var r = this.findRecord(this.valueField, d[this.valueField]) || this.createRecord(d);\r
+                this.addRecord(r);\r
+            },this);\r
+            return;\r
+        }\r
+        \r
+        Ext.each(data,function(item){\r
+            this.addItem(item);\r
+        },this);\r
+    },\r
+    /**\r
+     * Returns true if the SuperBoxSelect component has a selected item with a value matching the 'val' parameter.\r
+     * @methodOf Ext.ux.form.SuperBoxSelect\r
+     * @name hasValue\r
+     * @param {Mixed} val The value to test.\r
+     * @return {Boolean} true if the component has the selected value, false otherwise.\r
+     */\r
+    hasValue: function(val){\r
+        var has = false;\r
+        this.items.each(function(item){\r
+            if(item.value == val){\r
+                has = true;\r
+                return false;\r
+            }\r
+        },this);\r
+        return has;\r
+    },\r
+    onSelect : function(record, index) {\r
+       if (this.fireEvent('beforeselect', this, record, index) !== false){\r
+            var val = record.data[this.valueField];\r
+            \r
+            if(this.preventDuplicates && this.hasValue(val)){\r
+                return;\r
+            }\r
+            \r
+            this.setRawValue('');\r
+            this.lastSelectionText = '';\r
+            \r
+            if(this.fireEvent('beforeadditem',this,val) !== false){\r
+                this.addRecord(record);\r
+            }\r
+            if(this.store.getCount() === 0 || !this.multiSelectMode){\r
+                this.collapse();\r
+            }else{\r
+                this.restrictHeight();\r
+            }\r
+       }\r
+    },\r
+    onDestroy : function() {\r
+        this.items.purgeListeners();\r
+        this.killItems();\r
+        if (this.renderFieldBtns) {\r
+            Ext.destroy(\r
+                this.buttonClear,\r
+                this.buttonExpand,\r
+                this.buttonWrap\r
+            );\r
+        }\r
+\r
+        Ext.destroy(\r
+            this.inputEl,\r
+            this.wrapEl,\r
+            this.outerWrapEl\r
+        );\r
+\r
+        Ext.ux.form.SuperBoxSelect.superclass.onDestroy.call(this);\r
+    },\r
+    autoSize : function(){\r
+        if(!this.rendered){\r
+            return this;\r
+        }\r
+        if(!this.metrics){\r
+            this.metrics = Ext.util.TextMetrics.createInstance(this.el);\r
+        }\r
+        var el = this.el,\r
+            v = el.dom.value,\r
+            d = document.createElement('div');\r
+\r
+        if(v === "" && this.emptyText && this.items.getCount() < 1){\r
+            v = this.emptyText;\r
+        }\r
+        d.appendChild(document.createTextNode(v));\r
+        v = d.innerHTML;\r
+        d = null;\r
+        v += "&#160;";\r
+        var w = Math.max(this.metrics.getWidth(v) +  24, 24);\r
+        if(typeof this._width != 'undefined'){\r
+            w = Math.min(this._width, w);\r
+        }\r
+        this.el.setWidth(w);\r
+        \r
+        if(Ext.isIE){\r
+            this.el.dom.style.top='0';\r
+        }\r
+        return this;\r
+    },\r
+    doQuery : function(q, forceAll,valuesQuery, forcedAdd){\r
+        q = Ext.isEmpty(q) ? '' : q;\r
+        var qe = {\r
+            query: q,\r
+            forceAll: forceAll,\r
+            combo: this,\r
+            cancel:false\r
+        };\r
+        if(this.fireEvent('beforequery', qe)===false || qe.cancel){\r
+            return false;\r
+        }\r
+        q = qe.query;\r
+        forceAll = qe.forceAll;\r
+        if(forceAll === true || (q.length >= this.minChars) || valuesQuery && !Ext.isEmpty(q)){\r
+            if(this.lastQuery !== q || forcedAdd){\r
+               this.lastQuery = q;\r
+                if(this.mode == 'local'){\r
+                    this.selectedIndex = -1;\r
+                    if(forceAll){\r
+                        this.store.clearFilter();\r
+                    }else{\r
+                        this.store.filter(this.displayField, q);\r
+                    }\r
+                    this.onLoad();\r
+                }else{\r
+                       \r
+                    this.store.baseParams[this.queryParam] = q;\r
+                    this.store.baseParams[this.queryValuesIndicator] = valuesQuery;\r
+                    this.store.load({\r
+                        params: this.getParams(q)\r
+                    });\r
+                    if(!forcedAdd){\r
+                        this.expand();\r
+                    }\r
+                }\r
+            }else{\r
+                this.selectedIndex = -1;\r
+                this.onLoad();\r
+            }\r
+        }\r
+    }\r
+});\r
+Ext.reg('superboxselect', Ext.ux.form.SuperBoxSelect);\r
+/*\r
+ * @private\r
+ */\r
+Ext.ux.form.SuperBoxSelectItem = function(config){\r
+    Ext.apply(this,config);\r
+    Ext.ux.form.SuperBoxSelectItem.superclass.constructor.call(this); \r
+};\r
+/*\r
+ * @private\r
+ */\r
+Ext.ux.form.SuperBoxSelectItem = Ext.extend(Ext.ux.form.SuperBoxSelectItem,Ext.Component, {\r
+    initComponent : function(){\r
+        Ext.ux.form.SuperBoxSelectItem.superclass.initComponent.call(this); \r
+    },\r
+    onElClick : function(e){\r
+        var o = this.owner;\r
+        o.clearCurrentFocus().collapse();\r
+        if(o.navigateItemsWithTab){\r
+            this.focus();\r
+        }else{\r
+            o.el.dom.focus();\r
+            var that = this;\r
+            (function(){\r
+                this.onLnkFocus();\r
+                o.currentFocus = this;\r
+            }).defer(10,this);\r
+        }\r
+    },\r
+    \r
+    onLnkClick : function(e){\r
+        if(e) {\r
+            e.stopEvent();\r
+        }\r
+        this.preDestroy();\r
+        if(!this.owner.navigateItemsWithTab){\r
+            this.owner.el.focus();\r
+        }\r
+    },\r
+    onLnkFocus : function(){\r
+        this.el.addClass("x-superboxselect-item-focus");\r
+        this.owner.outerWrapEl.addClass("x-form-focus");\r
+    },\r
+    \r
+    onLnkBlur : function(){\r
+        this.el.removeClass("x-superboxselect-item-focus");\r
+        this.owner.outerWrapEl.removeClass("x-form-focus");\r
+    },\r
+    \r
+    enableElListeners : function() {\r
+        this.el.on('click', this.onElClick, this, {stopEvent:true});\r
+       \r
+        this.el.addClassOnOver('x-superboxselect-item x-superboxselect-item-hover');\r
+    },\r
+\r
+    enableLnkListeners : function() {\r
+        this.lnk.on({\r
+            click: this.onLnkClick,\r
+            focus: this.onLnkFocus,\r
+            blur:  this.onLnkBlur,\r
+            scope: this\r
+        });\r
+    },\r
+    \r
+    enableAllListeners : function() {\r
+        this.enableElListeners();\r
+        this.enableLnkListeners();\r
+    },\r
+    disableAllListeners : function() {\r
+        this.el.removeAllListeners();\r
+        this.lnk.un('click', this.onLnkClick, this);\r
+        this.lnk.un('focus', this.onLnkFocus, this);\r
+        this.lnk.un('blur', this.onLnkBlur, this);\r
+    },\r
+    onRender : function(ct, position){\r
+        \r
+        Ext.ux.form.SuperBoxSelectItem.superclass.onRender.call(this, ct, position);\r
+        \r
+        var el = this.el;\r
+        if(el){\r
+            el.remove();\r
+        }\r
+        \r
+        this.el = el = ct.createChild({ tag: 'li' }, ct.last());\r
+        el.addClass('x-superboxselect-item');\r
+        \r
+        var btnEl = this.owner.navigateItemsWithTab ? ( Ext.isSafari ? 'button' : 'a') : 'span';\r
+        var itemKey = this.key;\r
+        \r
+        Ext.apply(el, {\r
+            focus: function(){\r
+                var c = this.down(btnEl +'.x-superboxselect-item-close');\r
+                if(c){\r
+                       c.focus();\r
+                }\r
+            },\r
+            preDestroy: function(){\r
+                this.preDestroy();\r
+            }.createDelegate(this)\r
+        });\r
+        \r
+        this.enableElListeners();\r
+\r
+        el.update(this.caption);\r
+\r
+        var cfg = {\r
+            tag: btnEl,\r
+            'class': 'x-superboxselect-item-close',\r
+            tabIndex : this.owner.navigateItemsWithTab ? '0' : '-1'\r
+        };\r
+        if(btnEl === 'a'){\r
+            cfg.href = '#';\r
+        }\r
+        this.lnk = el.createChild(cfg);\r
+        \r
+        \r
+        if(!this.disabled) {\r
+            this.enableLnkListeners();\r
+        }else {\r
+            this.disableAllListeners();\r
+        }\r
+        \r
+        this.on({\r
+            disable: this.disableAllListeners,\r
+            enable: this.enableAllListeners,\r
+            scope: this\r
+        });\r
+\r
+        this.setupKeyMap();\r
+    },\r
+    setupKeyMap : function(){\r
+        this.keyMap = new Ext.KeyMap(this.lnk, [\r
+            {\r
+                key: [\r
+                    Ext.EventObject.BACKSPACE, \r
+                    Ext.EventObject.DELETE, \r
+                    Ext.EventObject.SPACE\r
+                ],\r
+                fn: this.preDestroy,\r
+                scope: this\r
+            }, {\r
+                key: [\r
+                    Ext.EventObject.RIGHT,\r
+                    Ext.EventObject.DOWN\r
+                ],\r
+                fn: function(){\r
+                    this.moveFocus('right');\r
+                },\r
+                scope: this\r
+            },\r
+            {\r
+                key: [Ext.EventObject.LEFT,Ext.EventObject.UP],\r
+                fn: function(){\r
+                    this.moveFocus('left');\r
+                },\r
+                scope: this\r
+            },\r
+            {\r
+                key: [Ext.EventObject.HOME],\r
+                fn: function(){\r
+                    var l = this.owner.items.get(0).el.focus();\r
+                    if(l){\r
+                        l.el.focus();\r
+                    }\r
+                },\r
+                scope: this\r
+            },\r
+            {\r
+                key: [Ext.EventObject.END],\r
+                fn: function(){\r
+                    this.owner.el.focus();\r
+                },\r
+                scope: this\r
+            },\r
+            {\r
+                key: Ext.EventObject.ENTER,\r
+                fn: function(){\r
+                }\r
+            }\r
+        ]);\r
+        this.keyMap.stopEvent = true;\r
+    },\r
+    moveFocus : function(dir) {\r
+        var el = this.el[dir == 'left' ? 'prev' : 'next']() || this.owner.el;\r
+       \r
+        el.focus.defer(100,el);\r
+    },\r
+\r
+    preDestroy : function(supressEffect) {\r
+       if(this.fireEvent('remove', this) === false){\r
+               return;\r
+           }   \r
+       var actionDestroy = function(){\r
+            if(this.owner.navigateItemsWithTab){\r
+                this.moveFocus('right');\r
+            }\r
+            this.hidden.remove();\r
+            this.hidden = null;\r
+            this.destroy();\r
+        };\r
+        \r
+        if(supressEffect){\r
+            actionDestroy.call(this);\r
+        } else {\r
+            this.el.hide({\r
+                duration: 0.2,\r
+                callback: actionDestroy,\r
+                scope: this\r
+            });\r
+        }\r
+        return this;\r
+    },\r
+    kill : function(){\r
+       this.hidden.remove();\r
+        this.hidden = null;\r
+        this.purgeListeners();\r
+        this.destroy();\r
+    },\r
+    onDisable : function() {\r
+       if(this.hidden){\r
+           this.hidden.dom.setAttribute('disabled', 'disabled');\r
+       }\r
+       this.keyMap.disable();\r
+       Ext.ux.form.SuperBoxSelectItem.superclass.onDisable.call(this);\r
+    },\r
+    onEnable : function() {\r
+       if(this.hidden){\r
+           this.hidden.dom.removeAttribute('disabled');\r
+       }\r
+       this.keyMap.enable();\r
+       Ext.ux.form.SuperBoxSelectItem.superclass.onEnable.call(this);\r
+    },\r
+    onDestroy : function() {\r
+        Ext.destroy(\r
+            this.lnk,\r
+            this.el\r
+        );\r
+        \r
+        Ext.ux.form.SuperBoxSelectItem.superclass.onDestroy.call(this);\r
+    }\r
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/superboxselect/clear.png b/philo/contrib/gilbert/static/gilbert/superboxselect/clear.png
new file mode 100755 (executable)
index 0000000..0052e5f
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/superboxselect/clear.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/superboxselect/close.png b/philo/contrib/gilbert/static/gilbert/superboxselect/close.png
new file mode 100755 (executable)
index 0000000..0245558
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/superboxselect/close.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/superboxselect/expand.png b/philo/contrib/gilbert/static/gilbert/superboxselect/expand.png
new file mode 100755 (executable)
index 0000000..52d7fe6
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/superboxselect/expand.png differ
diff --git a/philo/contrib/gilbert/static/gilbert/superboxselect/superboxselect-gray-extend.css b/philo/contrib/gilbert/static/gilbert/superboxselect/superboxselect-gray-extend.css
new file mode 100755 (executable)
index 0000000..c376da6
--- /dev/null
@@ -0,0 +1,24 @@
+@charset "utf-8";\r
+.x-superboxselect {position:relative; height: auto !important; margin: 0px; overflow: hidden; padding:2px; display:block; outline: none !important;}\r
+.x-superboxselect ul {overflow: hidden; cursor: text;}\r
+.x-superboxselect-display-btns {padding-right: 33px !important;}\r
+.x-superboxselect-btns {position: absolute; right: 1px; top: 0; overflow:hidden; padding:2px;}\r
+.x-superboxselect-btns div {float: left; width: 16px; height: 16px; margin-top: 4px;}\r
+.x-superboxselect-btn-clear {background: url(clear.png) no-repeat scroll left 0px;}\r
+.x-superboxselect-btn-expand {background: url(expand.png) no-repeat scroll left 0px;}\r
+.x-superboxselect-btn-over {background-position: left -16px}\r
+.x-superboxselect-btn-hide {display:none;}\r
+.x-superboxselect li {float: left; margin: 1px 1px 2px 1px; padding: 0;line-height: 18px;}\r
+.x-superboxselect-stacked li {float: none !important;}\r
+.x-superboxselect-input input { border: none; outline: none; margin-top: 4px; margin-bottom: 4px;}\r
+body.ext-ie .x-superboxselect-input input {background: none; border: none; margin-top: 3px;}\r
+.x-superboxselect-item {position: relative; -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; o-border-radius: 6px; khtml-border-radius: 6px; border: 1px solid #d7d7d7; background-color: #e7e7e7; padding: 1px 15px 1px 5px !important; }\r
+body.ext-ie7 .x-superboxselect-item {margin: 2px 1px 2px 1px; line-height: 1.2em; padding: 2px 17px 4px 5px !important;}\r
+body.ext-ie6 .x-superboxselect-item {margin: 2px 1px 2px 1px; line-height: 1.2em; padding: 2px 19px 4px 5px !important;}\r
+.x-superboxselect-item-hover {background: #cdcdcd; border: 1px solid #949494;}\r
+.x-superboxselect-item-focus {border-color: #8b8b8b; background: #8b8b8b; color: #fff;}\r
+.x-superboxselect-item-close {background: url(close.png) no-repeat scroll left 0px; border: none; cursor: default; font-size: 1px; height: 16px;padding:0; position: absolute; right: 0px; top: 2px; width: 13px;display:block;cursor:pointer;}\r
+\r
+.x-superboxselect-item-close:hover, .x-superboxselect-item-close:active  { background-position: left -12px;}\r
+.x-superboxselect-item-focus .x-superboxselect-item-close{ background-position: left -24px}\r
+.x-item-disabled .x-superboxselect-item-close{ background-position: left -36px}
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/superboxselect/superboxselect.css b/philo/contrib/gilbert/static/gilbert/superboxselect/superboxselect.css
new file mode 100755 (executable)
index 0000000..d1cb001
--- /dev/null
@@ -0,0 +1,25 @@
+@charset "utf-8";\r
+.x-superboxselect {position:relative; height: auto !important; margin: 0px; overflow: hidden; padding:2px; display:block; outline: none !important;}\r
+.x-superboxselect input[disabled] {background-color: transparent;};\r
+.x-superboxselect ul {overflow: hidden; cursor: text;}\r
+.x-superboxselect-display-btns {padding-right: 33px !important;}\r
+.x-superboxselect-btns {position: absolute; right: 1px; top: 0; overflow:hidden; padding:2px;}\r
+.x-superboxselect-btns div {float: left; width: 16px; height: 16px; margin-top: 4px;}\r
+.x-superboxselect-btn-clear {background: url(clear.png) no-repeat scroll left 0px;}\r
+.x-superboxselect-btn-expand {background: url(expand.png) no-repeat scroll left 0px;}\r
+.x-superboxselect-btn-over {background-position: left -16px}\r
+.x-superboxselect-btn-hide {display:none;}\r
+.x-superboxselect li {float: left; margin: 1px 1px 2px 1px; padding: 0;line-height: 18px;}\r
+.x-superboxselect-stacked li {float: none !important;}\r
+.x-superboxselect-input input { border: none; outline: none; margin-top: 4px; margin-bottom: 4px;}\r
+body.ext-ie .x-superboxselect-input input {background: none; border: none; margin-top: 3px;}\r
+.x-superboxselect-item {position: relative; -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; o-border-radius: 6px; khtml-border-radius: 6px; border: 1px solid #CAD8F3; background-color: #DEE7F8; padding: 1px 15px 1px 5px !important; }\r
+body.ext-ie7 .x-superboxselect-item {margin: 2px 1px 2px 1px; line-height: 1.2em; padding: 2px 17px 4px 5px !important;}\r
+body.ext-ie6 .x-superboxselect-item {margin: 2px 1px 2px 1px; line-height: 1.2em; padding: 2px 19px 4px 5px !important;}\r
+.x-superboxselect-item-hover {background: #BBCEF1; border: 1px solid #6D95E0;}\r
+.x-superboxselect-item-focus {border-color: #598BEC; background: #598BEC; color: #fff;}\r
+.x-superboxselect-item-close {background: url(close.png) no-repeat scroll left 0px; border: none; cursor: default; font-size: 1px; height: 16px;padding:0; position: absolute; right: 0px; top: 2px; width: 13px;display:block;cursor:pointer;}\r
+\r
+.x-superboxselect-item-close:hover, .x-superboxselect-item-close:active  { background-position: left -12px;}\r
+.x-superboxselect-item-focus .x-superboxselect-item-close{ background-position: left -24px}\r
+.x-item-disabled .x-superboxselect-item-close{ background-position: left -36px}
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/wallpaper.README b/philo/contrib/gilbert/static/gilbert/wallpaper.README
new file mode 100644 (file)
index 0000000..a14fe2e
--- /dev/null
@@ -0,0 +1 @@
+The source of the default wallpaper.jpg is http://webtreats.mysitemyway.com/tileable-classic-nebula-space-patterns/
\ No newline at end of file
diff --git a/philo/contrib/gilbert/static/gilbert/wallpaper.jpg b/philo/contrib/gilbert/static/gilbert/wallpaper.jpg
new file mode 100644 (file)
index 0000000..6188914
Binary files /dev/null and b/philo/contrib/gilbert/static/gilbert/wallpaper.jpg differ
diff --git a/philo/contrib/gilbert/templates/gilbert/api.js b/philo/contrib/gilbert/templates/gilbert/api.js
new file mode 100644 (file)
index 0000000..4281d66
--- /dev/null
@@ -0,0 +1,74 @@
+Ext.Ajax.on('beforerequest', function (connection, options) {
+       if (!(/^http:.*/.test(options.url) || /^https:.*/.test(options.url))) {
+               options.headers = Ext.apply(options.headers||{}, {
+                       'X-CSRFToken': '{{ csrf_token }}',
+               });
+       }
+});
+
+
+Ext.ns('Gilbert.api');
+{% for provider in providers %}Ext.Direct.addProvider({{ provider|safe }});{% endfor %}
+
+
+Gilbert.on('ready', function (application) {{% for app_label, models in model_registry.items %}{% for name, admin in models.items %}
+       application.register_model('{{ app_label }}', '{{ name }}', new Gilbert.lib.models.Model({
+               app_label: '{{ app_label }}',
+               name: '{{ name }}',
+               verbose_name: '{{ admin.model_meta.verbose_name }}',
+               verbose_name_plural: '{{ admin.model_meta.verbose_name_plural }}',
+               searchable: {% if admin.search_fields %}true{% else %}false{% endif %},
+               columns: {{ admin.data_columns_spec_json|safe }},
+               iconCls: 'icon-{{ admin.icon_name }}',
+               api: Gilbert.api.models.{{ app_label }}.{{ name }},
+       }));
+{% endfor %}{% endfor %}});
+
+
+Gilbert.on('ready', function (application) {
+       application.register_plugin('_about_window', {
+               init: function(application) {
+                       var application = this.application = application;
+                       
+                       var plugin = this;
+                       
+                       application.mainmenu.remove(application.mainmenu.items.items[0]);
+                       
+                       application.mainmenu.insert(0, {
+                               xtype: 'button',
+                               text: '<span style="font-weight: bolder; text-transform: uppercase;">{{ gilbert.title|safe }}</span>',
+                               handler: function(button, event) {
+                                       plugin.showAbout(button);
+                               },
+                       });
+               },
+               showAbout: function(sender) {
+                       var application = this.application;
+                       
+                       if (!this.about_window) {
+                               var about_window = this.about_window = application.create_window({
+                                       height: 176,
+                                       width: 284,
+                                       header: false,
+                                       html: '<h1>{{ gilbert.title|safe }}</h1><h2>Version {{ gilbert.version|safe }}</h2><div id="credits">{{ gilbert.credits|safe }}</div>',
+                                       bodyStyle: 'background: none; font-size: larger; line-height: 1.4em; text-align: center;',
+                                       modal: true,
+                                       closeAction: 'hide',
+                                       closable: false,
+                                       resizable: false,
+                                       draggable: false,
+                                       minimizable: false,
+                                       fbar: [{
+                                               text: 'OK',
+                                               handler: function(button, event) {
+                                                       about_window.hide();
+                                               }
+                                       }],
+                                       defaultButton: 0,
+                               });
+                       }
+                       this.about_window.show();
+                       this.about_window.focus();
+               },
+       });
+});
\ No newline at end of file
diff --git a/philo/contrib/gilbert/templates/gilbert/base.html b/philo/contrib/gilbert/templates/gilbert/base.html
new file mode 100644 (file)
index 0000000..4bc7bb1
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>{% block head %}
+       <title>{% block title %}{{ gilbert.title }}{% endblock %}</title>
+       
+       {% block css %}
+       <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}gilbert/extjs/resources/css/ext-all-notheme.css" />
+       <link rel="stylesheet" type="text/css" class="gilbert.theme" id="gilbert.theme.murano" title="murano" href="{{ STATIC_URL }}gilbert/murano/murano.css"{% if request.GET.theme and request.GET.theme != 'murano' %} disabled="disabled"{% endif %} />
+       <link rel="stylesheet" type="text/css" class="gilbert.theme" id="gilbert.theme.access" title="access" href="{{ STATIC_URL }}gilbert/extjs/resources/css/xtheme-access.css"{% if request.GET.theme != 'access' %} disabled="disabled"{% endif %} />
+       <link rel="stylesheet" type="text/css" class="gilbert.theme" id="gilbert.theme.blue" title="blue" href="{{ STATIC_URL }}gilbert/extjs/resources/css/xtheme-blue.css"{% if request.GET.theme != 'blue' %} disabled="disabled"{% endif %} />
+       <link rel="stylesheet" type="text/css" class="gilbert.theme" id="gilbert.theme.gray" title="gray" href="{{ STATIC_URL }}gilbert/extjs/resources/css/xtheme-gray.css"{% if request.GET.theme != 'gray' %} disabled="disabled"{% endif %} />
+       {% endblock %}
+       
+       {% block js %}
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/adapter/ext/ext-base.js"></script>
+       {% if request.GET.debug %}
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/ext-all-debug.js"></script>
+       {% else %}
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/ext-all.js"></script>
+       {% endif %}
+       <script type="text/javascript">
+               Ext.BLANK_IMAGE_URL = '{{ STATIC_URL }}gilbert/extjs/resources/images/default/s.gif';
+       </script>
+       {% endblock %}
+       
+       {% block extrahead %}
+       {% endblock %}
+       
+{% endblock %}</head>
+<body style="background-image: url({{ STATIC_URL }}gilbert/wallpaper.jpg);">{% block body %}{% endblock %}</body>
+</html>
diff --git a/philo/contrib/gilbert/templates/gilbert/icons.css b/philo/contrib/gilbert/templates/gilbert/icons.css
new file mode 100644 (file)
index 0000000..677d7eb
--- /dev/null
@@ -0,0 +1,5 @@
+{% for icon_name in icon_names %}
+.icon-{{ icon_name }} {
+       background: url({{ STATIC_URL }}gilbert/fugue-icons/icons-shadowless/{{ icon_name }}.png) 0 no-repeat !important;
+}
+{% endfor %}
\ No newline at end of file
diff --git a/philo/contrib/gilbert/templates/gilbert/index.html b/philo/contrib/gilbert/templates/gilbert/index.html
new file mode 100644 (file)
index 0000000..b45a2b8
--- /dev/null
@@ -0,0 +1,45 @@
+{% extends 'gilbert/base.html' %}
+
+{% block css %}{{ block.super }}
+       <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}gilbert/extjs/examples/ux/fileuploadfield/css/fileuploadfield.css" />
+       <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}gilbert/superboxselect/superboxselect.css" />
+       <link rel="stylesheet" type="text/css" href="{% url gilbert:icons %}" />
+       
+       {% for router in gilbert.model_routers.values %}{% for admin in router.models.values %}{% for css_url in admin.index_css_urls %}
+       <link rel="stylesheet" type="text/css" href="{{ css_url }}" />
+       {% endfor %}{% endfor %}{% endfor %}
+       
+       {% for plugin in plugins %}{% for css_url in plugin.index_css_urls %}
+       <link rel="stylesheet" type="text/css" href="{{ css_url }}" />
+       {% endfor %}{% endfor %}
+{% endblock %}
+       
+{% block js %}{{ block.super }}
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/examples/ux/FieldLabeler.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/examples/ux/fileuploadfield/FileUploadField.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/superboxselect/SuperBoxSelect.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/examples/ux/Reorderer.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/examples/ux/ToolbarReorderer.js"></script>
+       
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/lib/app.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/lib/models.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/lib/plugins.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/lib/ui/forms.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/lib/ui/windows.js"></script>
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/gilbert.js"></script>
+       
+       <script type="text/javascript" src="{% url gilbert:api %}"></script>
+       
+       {% for router in gilbert.model_routers.values %}{% for admin in router.models.values %}{% for js_url in admin.index_js_urls %}
+       <script type="text/javascript" src="{{ js_url }}"></script>
+       {% endfor %}{% endfor %}{% endfor %}
+       
+       {% for plugin in plugins %}{% for js_url in plugin.index_js_urls %}
+       <script type="text/javascript" src="{{ js_url }}"></script>
+       {% endfor %}{% endfor %}
+{% endblock %}
+       
+{% block extrahead %}
+       {% for router in gilbert.model_routers.values %}{% for admin in router.models.values %}{{ admin.index_extrahead }}{% endfor %}{% endfor %}
+       {% for plugin in plugins %}{{ plugin.index_extrahead }}{% endfor %}
+{% endblock %}
\ No newline at end of file
diff --git a/philo/contrib/gilbert/templates/gilbert/login.html b/philo/contrib/gilbert/templates/gilbert/login.html
new file mode 100644 (file)
index 0000000..0041ff7
--- /dev/null
@@ -0,0 +1,101 @@
+{% extends 'gilbert/base.html' %}
+
+{% block css %}{{ block.super }}
+       <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}gilbert/extjs/examples/ux/statusbar/css/statusbar.css" />
+       <style type="text/css">
+               .icon-door-open-in {
+                       background: url({{ STATIC_URL }}gilbert/fugue-icons/icons-shadowless/door-open-in.png) no-repeat !important;
+               }
+               .x-statusbar .x-status-error {
+                       cursor: help;
+                       background-image: url({{ STATIC_URL }}gilbert/fugue-icons/icons-shadowless/exclamation-red.png) !important;
+               }
+       </style>
+{% endblock %}
+
+{% block js %}{{ block.super }}
+       <script type="text/javascript" src="{{ STATIC_URL }}gilbert/extjs/examples/ux/statusbar/StatusBar.js"></script>
+       <script type="text/javascript" charset="utf-8">
+               Ext.onReady(function () {
+                       
+                       var login_form = new Ext.FormPanel({
+                               border: false,{% if request.GET.theme %}
+                               bodyStyle: 'padding: 10px',{% else %}
+                               bodyStyle: 'padding: 0px 10px 0px;',{% endif %}
+                               items: [
+                                       {
+                                               xtype: 'hidden',
+                                               name: 'csrfmiddlewaretoken',
+                                               value: '{{ csrf_token }}',
+                                       },
+                                       {
+                                               xtype: 'textfield',
+                                               anchor: '100%',
+                                               fieldLabel: 'Username',
+                                               name: 'username',
+                                       },
+                                       {
+                                               xtype: 'textfield',
+                                               inputType: 'password',
+                                               anchor: '100%',
+                                               fieldLabel: 'Password',
+                                               name: 'password',
+                                       }
+                               ],
+                               url: '{{ form_url }}',
+                               standardSubmit: true,
+                               keys: [
+                                       {
+                                               key: [Ext.EventObject.ENTER],
+                                               handler: function () {
+                                                       var form = login_form.getForm();
+                                                       form.submit();
+                                               },
+                                       },
+                               ],
+                       });
+                       
+                       var login_status_bar = window.status_bar = new Ext.ux.StatusBar({
+                               items: [
+                                       {
+                                               xtype: 'button',
+                                               text: 'Log in',
+                                               iconCls: 'icon-door-open-in',
+                                               handler: function () {
+                                                       var form = login_form.getForm();
+                                                       form.submit();
+                                               },
+                                       },
+                               ],
+                       });
+                       var login_window = new Ext.Window({
+                               header: false,
+                               closable: false,
+                               resizable: false,
+                               draggable: false,
+                               frame: true,
+                               autoHeight: true,
+                               width: 320,
+                               items: login_form,
+                               bbar: login_status_bar,
+                       });
+                       
+                       login_window.show();
+                       {% if error_message %}
+                       login_status_bar.setStatus({
+                               iconCls: 'x-status-error',
+                               text: '{{ error_message_short }}',
+                       });
+                       
+                       new Ext.ToolTip({
+                               target: login_status_bar.statusEl.el,
+                               anchor: 'top',
+                               title: '{{ error_message_short }}',
+                               html: '{{ error_message }}',
+                       });
+                       
+                       Ext.QuickTips.init();
+                       {% endif %}
+               });
+       </script>
+{% endblock %}
\ No newline at end of file
diff --git a/philo/contrib/penfield/gilbert.py b/philo/contrib/penfield/gilbert.py
new file mode 100644 (file)
index 0000000..e6e8251
--- /dev/null
@@ -0,0 +1,21 @@
+from .models import Blog, BlogEntry, BlogView, NewsletterArticle, NewsletterIssue, Newsletter, NewsletterView
+from philo.contrib.gilbert import site
+from philo.contrib.gilbert.plugins.models import ModelAdmin
+
+
+class BlogAdmin(ModelAdmin):
+       search_fields = ('title',)
+
+
+class BlogEntryAdmin(ModelAdmin):
+       search_fields = ('title', 'content',)
+       data_columns = ('title', 'author', 'blog', 'date',)
+
+
+site.register_model(Blog, BlogAdmin, icon_name='blog')
+site.register_model(BlogEntry, BlogEntryAdmin, icon_name='document-snippet')
+site.register_model(BlogView, icon_name='application-blog')
+site.register_model(NewsletterArticle, icon_name='document-snippet')
+site.register_model(NewsletterIssue, icon_name='newspaper')
+site.register_model(Newsletter, icon_name='newspapers')
+site.register_model(NewsletterView, icon_name='application')
\ No newline at end of file
similarity index 96%
rename from contrib/shipherd/templatetags/shipherd.py
rename to philo/contrib/shipherd/templatetags/shipherd.py
index b05ff0f..4723246 100644 (file)
@@ -60,10 +60,6 @@ class LazyNavigationRecurser(object):
                        context['item'] = item
                        context['children'] = self.__class__(self.template_nodes, item.get_children(), context, request)
                        
-                       # Django 1.2.X compatibility - a lazy recurser will not be called if accessed as a template variable.
-                       if django_version < (1,3):
-                               context['children'] = context['children']()
-                       
                        # Then render the nodelist bit by bit.
                        for node in self.template_nodes:
                                bits.append(node.render(context))
similarity index 100%
rename from exceptions.py
rename to philo/exceptions.py
similarity index 100%
rename from forms/__init__.py
rename to philo/forms/__init__.py
similarity index 100%
rename from forms/entities.py
rename to philo/forms/entities.py
similarity index 100%
rename from forms/fields.py
rename to philo/forms/fields.py
diff --git a/philo/gilbert.py b/philo/gilbert.py
new file mode 100644 (file)
index 0000000..f59a0a2
--- /dev/null
@@ -0,0 +1,11 @@
+from philo.models import Tag, Collection, Node, Redirect, File, Template, Page
+from philo.contrib.gilbert import site
+
+
+site.register_model(Tag, icon_name='tag-label', data_columns=('name', 'slug'), data_editable_columns=('name', 'slug'))
+site.register_model(Collection, icon_name='box')
+site.register_model(Node, icon_name='node')
+site.register_model(Redirect, icon_name='arrow-switch')
+site.register_model(File, icon_name='document-binary')
+site.register_model(Page, icon_name='document-globe')
+site.register_model(Template, icon_name='document-template')
\ No newline at end of file
diff --git a/philo/hacks.py b/philo/hacks.py
new file mode 100644 (file)
index 0000000..038f6f5
--- /dev/null
@@ -0,0 +1,69 @@
+class Category(type):
+       """
+       Adds attributes to an existing class.
+       
+       """
+       
+       replace_attrs = False
+       dunder_attrs = False
+       never_attrs = ('__module__', '__metaclass__')
+       
+       def __new__(cls, name, bases, attrs):
+               if len(bases) != 1:
+                       raise AttributeError('%s: "%s" cannot add methods to more than one class.' % (cls.__name__, name))
+               
+               base = bases[0]
+               
+               for attr, value in attrs.iteritems():
+                       if attr in cls.never_attrs:
+                               continue
+                       if not cls.dunder_attrs and attr.startswith('__'):
+                               continue
+                       if not cls.replace_attrs and hasattr(base, attr):
+                               continue
+                       setattr(base, attr, value)
+               
+               return base
+
+
+class MonkeyPatch(type):
+       """
+       Similar to Category, except it will replace attributes.
+       
+       """
+       
+       replace_attrs = True
+       dunder_attrs = Category.dunder_attrs
+       never_attrs = Category.never_attrs
+       
+       unpatches = {}
+       
+       @classmethod
+       def unpatched(cls, klass, name):
+               try:
+                       return cls.unpatches[klass][name]
+               except:
+                       return getattr(klass, name)
+       
+       def __new__(cls, name, bases, attrs):
+               if len(bases) != 1:
+                       raise AttributeError('%s: "%s" cannot patch more than one class.' % (cls.__name__, name))
+               
+               base = bases[0]
+               
+               for attr, value in attrs.iteritems():
+                       if attr in cls.never_attrs:
+                               continue
+                       if not cls.dunder_attrs and attr.startswith('__'):
+                               continue
+                       if hasattr(base, attr):
+                               if not cls.replace_attrs:
+                                       continue
+                               else:
+                                       if base not in cls.unpatches:
+                                               cls.unpatches[base] = {}
+                                       cls.unpatches[base][attr] = getattr(base, attr)
+                       
+                       setattr(base, attr, value)
+               
+               return base
\ No newline at end of file
diff --git a/philo/loaders/__init__.py b/philo/loaders/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
similarity index 100%
rename from middleware.py
rename to philo/middleware.py
similarity index 100%
rename from models/__init__.py
rename to philo/models/__init__.py
similarity index 73%
rename from models/base.py
rename to philo/models/base.py
index af1e880..cf420c7 100644 (file)
@@ -38,10 +38,12 @@ class Titled(models.Model):
                abstract = True
 
 
+#: An instance of :class:`ContentTypeRegistryLimiter` which is used to track the content types which can be related to by ForeignKeyValues and ManyToManyValues.
 value_content_type_limiter = ContentTypeRegistryLimiter()
 
 
 def register_value_model(model):
+       """Registers a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue` through the :data:`value_content_type_limiter`."""
        value_content_type_limiter.register_class(model)
 
 
@@ -49,21 +51,37 @@ register_value_model(Tag)
 
 
 def unregister_value_model(model):
+       """Registers a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue` through the :data:`value_content_type_limiter`."""
        value_content_type_limiter.unregister_class(model)
 
 
 class AttributeValue(models.Model):
+       """
+       This is an abstract base class for models that can be used as values for :class:`Attribute`\ s.
+       
+       AttributeValue subclasses are expected to supply access to a clean version of their value through an attribute called "value".
+       
+       """
+       
+       #: :class:`GenericRelation` to :class:`Attribute`
        attribute_set = generic.GenericRelation('Attribute', content_type_field='value_content_type', object_id_field='value_object_id')
        
        def set_value(self, value):
+               """Given a ``value``, sets the appropriate fields so that it can be correctly stored in the database."""
                raise NotImplementedError
        
        def value_formfields(self, **kwargs):
-               """Define any formfields that would be used to construct an instance of this value."""
+               """
+               Returns any formfields that would be used to construct an instance of this value.
+               
+               :returns: A dictionary mapping field names to formfields.
+               
+               """
+               
                raise NotImplementedError
        
        def construct_instance(self, **kwargs):
-               """Apply cleaned data from the formfields generated by valid_formfields to oneself."""
+               """Applies cleaned data from the formfields generated by valid_formfields to oneself."""
                raise NotImplementedError
        
        def __unicode__(self):
@@ -73,10 +91,12 @@ class AttributeValue(models.Model):
                abstract = True
 
 
+#: An instance of :class:`ContentTypeSubclassLimiter` which is used to track the content types which are considered valid value models for an :class:`Attribute`.
 attribute_value_limiter = ContentTypeSubclassLimiter(AttributeValue)
 
 
 class JSONValue(AttributeValue):
+       """Stores a python object as a json string."""
        value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null', db_index=True)
        
        def __unicode__(self):
@@ -99,6 +119,7 @@ class JSONValue(AttributeValue):
 
 
 class ForeignKeyValue(AttributeValue):
+       """Stores a generic relationship to an instance of any value content type (as defined by the :data:`value_content_type_limiter`)."""
        content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
        object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
        value = generic.GenericForeignKey()
@@ -136,6 +157,7 @@ class ForeignKeyValue(AttributeValue):
 
 
 class ManyToManyValue(AttributeValue):
+       """Stores a generic relationship to many instances of any value content type (as defined by the :data:`value_content_type_limiter`)."""
        content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
        values = models.ManyToManyField(ForeignKeyValue, blank=True, null=True)
        
@@ -215,14 +237,20 @@ class ManyToManyValue(AttributeValue):
 
 
 class Attribute(models.Model):
+       """Represents an arbitrary key/value pair on an arbitrary :class:`Model` where the key consists of word characters and the value is a subclass of :class:`AttributeValue`."""
        entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
        entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID', db_index=True)
+       
+       #: :class:`GenericForeignKey` to anything (generally an instance of an Entity subclass).
        entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
        
        value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True)
        value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
+       
+       #: :class:`GenericForeignKey` to an instance of a subclass of :class:`AttributeValue` as determined by the :data:`attribute_value_limiter`.
        value = generic.GenericForeignKey('value_content_type', 'value_object_id')
        
+       #: :class:`CharField` containing a key (up to 255 characters) consisting of alphanumeric characters and underscores.
        key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.", db_index=True)
        
        def __unicode__(self):
@@ -279,12 +307,26 @@ class EntityBase(models.base.ModelBase):
 
 
 class Entity(models.Model):
+       """An abstract class that simplifies access to related attributes. Most models provided by Philo subclass Entity."""
        __metaclass__ = EntityBase
        
        attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
        
        @property
        def attributes(self):
+               """
+               Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly.
+
+               Example::
+
+                       >>> attr = entity.attribute_set.get(key='spam')
+                       >>> attr.value.value
+                       u'eggs'
+                       >>> entity.attributes['spam']
+                       u'eggs'
+               
+               """
+               
                return QuerySetMapper(self.attribute_set.all())
        
        class Meta:
@@ -296,19 +338,21 @@ class TreeManager(models.Manager):
        
        def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='slug'):
                """
-               Returns the object with the path, unless absolute_result is set to False, in which
-               case it returns a tuple containing the deepest object found along the path, and the
-               remainder of the path after that object as a string (or None if there is no remaining
-               path). Raises a DoesNotExist exception if no object is found with the given path.
-               
-               If the path you're searching for is known to exist, it is always faster to use
-               absolute_result=True - unless the path depth is over ~40, in which case the high cost
-               of the absolute query makes a binary search (i.e. non-absolute) faster.
+               If ``absolute_result`` is ``True``, returns the object at ``path`` (starting at ``root``) or raises a :exception:`DoesNotExist` exception. Otherwise, returns a tuple containing the deepest object found along ``path`` (or ``root`` if no deeper object is found) and the remainder of the path after that object as a string (or None if there is no remaining path).
+               
+               .. note:: If you are looking for something with an exact path, it is faster to use absolute_result=True, unless the path depth is over ~40, in which case the high cost of the absolute query may make a binary search (i.e. non-absolute) faster.
+               
+               .. note:: SQLite allows max of 64 tables in one join. That means the binary search will only work on paths with a max depth of 127 and the absolute fetch will only work to a max depth of (surprise!) 63. Larger depths could be handled, but since the common use case will not have a tree structure that deep, they are not.
+               
+               :param path: The path of the object
+               :param root: The object which will be considered the root of the search
+               :param absolute_result: Whether to return an absolute result or do a binary search
+               :param pathsep: The path separator used in ``path``
+               :param field: The field on the model which should be queried for ``path`` segment matching.
+               :returns: An instance if absolute_result is True or (instance, remaining_path) otherwise.
+               
                """
-               # Note: SQLite allows max of 64 tables in one join. That means the binary search will
-               # only work on paths with a max depth of 127 and the absolute fetch will only work
-               # to a max depth of (surprise!) 63. Although this could be handled, chances are your
-               # tree structure won't be that deep.
+               
                segments = path.split(pathsep)
                
                # Clean out blank segments. Handles multiple consecutive pathseps.
@@ -407,6 +451,14 @@ class TreeModel(MPTTModel):
        slug = models.SlugField(max_length=255)
        
        def get_path(self, root=None, pathsep='/', field='slug'):
+               """
+               :param root: Only return the path since this object.
+               :param pathsep: The path separator to use when constructing an instance's path
+               :param field: The field to pull path information from for each ancestor.
+               :returns: A string representation of an object's path.
+               
+               """
+               
                if root == self:
                        return ''
                
@@ -438,10 +490,27 @@ class TreeEntityBase(MPTTModelBase, EntityBase):
 
 
 class TreeEntity(Entity, TreeModel):
+       """An abstract subclass of Entity which represents a tree relationship."""
+       
        __metaclass__ = TreeEntityBase
        
        @property
        def attributes(self):
+               """
+               Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly. If an attribute with a given key is not related to the :class:`Entity`, then the object will check the parent's attributes.
+
+               Example::
+
+                       >>> attr = entity.attribute_set.get(key='spam')
+                       DoesNotExist: Attribute matching query does not exist.
+                       >>> attr = entity.parent.attribute_set.get(key='spam')
+                       >>> attr.value.value
+                       u'eggs'
+                       >>> entity.attributes['spam']
+                       u'eggs'
+               
+               """
+               
                if self.parent:
                        return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes)
                return super(TreeEntity, self).attributes
similarity index 96%
rename from models/fields/__init__.py
rename to philo/models/fields/__init__.py
index d900e31..1f9603e 100644 (file)
@@ -109,8 +109,7 @@ class SlugMultipleChoiceField(models.Field):
                                del kwargs[k]
                
                defaults.update(kwargs)
-               # Django 1.2 does not supply MultipleChoiceField
-               form_class = getattr(forms, 'TypedMultipleChoiceField', forms.MultipleChoiceField)
+               form_class = forms.TypedMultipleChoiceField
                return form_class(**defaults)
        
        def validate(self, value, model_instance):
similarity index 100%
rename from models/nodes.py
rename to philo/models/nodes.py
similarity index 100%
rename from models/pages.py
rename to philo/models/pages.py
similarity index 100%
rename from signals.py
rename to philo/signals.py
@@ -1,4 +1,4 @@
-{% load i18n adminmedia %}
+{% load i18n adminmedia grp_tags %}
 
 <!-- group -->
 <div class="group tabular{% if inline_admin_formset.opts.classes %} {{ inline_admin_formset.opts.classes|join:" " }}{% endif %}"
 
 <script type="text/javascript">
 (function($) {
-    $(document).ready(function($) {
-        
-        $("#{{ inline_admin_formset.formset.prefix }}-group").grp_inline({
-            prefix: "{{ inline_admin_formset.formset.prefix }}",
-            onBeforeAdded: function(inline) {},
-            onAfterAdded: function(form) {
-                grappelli.reinitDateTimeFields(form);
-                grappelli.updateSelectFilter(form);
-                form.find("input.vForeignKeyRawIdAdminField").grp_related_fk({lookup_url:"{% url grp_related_lookup %}"});
-                form.find("input.vManyToManyRawIdAdminField").grp_related_m2m({lookup_url:"{% url grp_m2m_lookup %}"});
-                form.find("input[name*='object_id'][name$='id']").grp_related_generic({lookup_url:"{% url grp_related_lookup %}"});
-            },
-        });
-        
-        {% if inline_admin_formset.opts.sortable_field_name %}
-        $("#{{ inline_admin_formset.formset.prefix }}-group > div.table").sortable({
-            handle: "a.drag-handler",
-            items: "div.dynamic-form",
-            axis: "y",
-            appendTo: 'body',
-            forceHelperSize: true,
-            containment: '#{{ inline_admin_formset.formset.prefix }}-group > div.table',
-            tolerance: 'pointer',
-        });
-        $("#{{ opts.module_name }}_form").bind("submit", function(){
-            var sortable_field_name = "{{ inline_admin_formset.opts.sortable_field_name }}";
-            var i = 0;
-            $("#{{ inline_admin_formset.formset.prefix }}-group").find("div.dynamic-form").each(function(){
-                var fields = $(this).find("div.td :input[value]");
-                if (fields.serialize()) {
-                    $(this).find("input[name$='"+sortable_field_name+"']").val(i);
-                    i++;
-                }
-            });
-        });
-        {% endif %}
-        
-    });
+       $(document).ready(function($) {
+               
+               var prefix = "{{ inline_admin_formset.formset.prefix }}";
+               var related_lookup_fields_fk = {% get_related_lookup_fields_fk inline_admin_formset.opts %};
+               var related_lookup_fields_m2m = {% get_related_lookup_fields_m2m inline_admin_formset.opts %};
+               var related_lookup_fields_generic = {% get_related_lookup_fields_generic inline_admin_formset.opts %};
+               $.each(related_lookup_fields_fk, function() {
+                       $("#{{ inline_admin_formset.formset.prefix }}-group > div.table")
+                       .find("input[name^='" + prefix + "'][name$='" + this + "']")
+                       .grp_related_fk({lookup_url:"{% url grp_related_lookup %}"});
+               });
+               $.each(related_lookup_fields_m2m, function() {
+                       $("#{{ inline_admin_formset.formset.prefix }}-group > div.table")
+                       .find("input[name^='" + prefix + "'][name$='" + this + "']")
+                       .grp_related_m2m({lookup_url:"{% url grp_m2m_lookup %}"});
+               });
+               $.each(related_lookup_fields_generic, function() {
+                       var content_type = this[0],
+                               object_id = this[1];
+                       $("#{{ inline_admin_formset.formset.prefix }}-group > div.table")
+                       .find("input[name^='" + prefix + "'][name$='" + this[1] + "']")
+                       .each(function() {
+                               var i = $(this).attr("id").match(/-\d+-/);
+                               if (i) {
+                                       var ct_id = "#id_" + prefix + i[0] + content_type,
+                                               obj_id = "#id_" + prefix + i[0] + object_id;
+                                       $(this).grp_related_generic({content_type:ct_id, object_id:obj_id, lookup_url:"{% url grp_related_lookup %}"});
+                               }
+                       });
+               });
+               
+               $("#{{ inline_admin_formset.formset.prefix }}-group").grp_inline({
+                       prefix: "{{ inline_admin_formset.formset.prefix }}",
+                       onBeforeAdded: function(inline) {},
+                       onAfterAdded: function(form) {
+                               grappelli.reinitDateTimeFields(form);
+                               grappelli.updateSelectFilter(form);
+                               $.each(related_lookup_fields_fk, function() {
+                                       form.find("input[name^='" + prefix + "'][name$='" + this + "']")
+                                       .grp_related_fk({lookup_url:"{% url grp_related_lookup %}"});
+                               });
+                               $.each(related_lookup_fields_m2m, function() {
+                                       form.find("input[name^='" + prefix + "'][name$='" + this + "']")
+                                       .grp_related_m2m({lookup_url:"{% url grp_m2m_lookup %}"});
+                               });
+                               $.each(related_lookup_fields_generic, function() {
+                                       var content_type = this[0],
+                                               object_id = this[1];
+                                       form.find("input[name^='" + prefix + "'][name$='" + this[1] + "']")
+                                       .each(function() {
+                                               var i = $(this).attr("id").match(/-\d+-/);
+                                               if (i) {
+                                                       var ct_id = "#id_" + prefix + i[0] + content_type,
+                                                               obj_id = "#id_" + prefix + i[0] + object_id;
+                                                       $(this).grp_related_generic({content_type:ct_id, object_id:obj_id, lookup_url:"{% url grp_related_lookup %}"});
+                                               }
+                                       });
+                               });
+                       },
+               });
+               
+               {% if inline_admin_formset.opts.sortable_field_name %}
+               $("#{{ inline_admin_formset.formset.prefix }}-group > div.table").sortable({
+                       handle: "a.drag-handler",
+                       items: "div.dynamic-form",
+                       axis: "y",
+                       appendTo: 'body',
+                       forceHelperSize: true,
+                       containment: '#{{ inline_admin_formset.formset.prefix }}-group > div.table',
+                       tolerance: 'pointer',
+               });
+               $("#{{ opts.module_name }}_form").bind("submit", function(){
+                       var sortable_field_name = "{{ inline_admin_formset.opts.sortable_field_name }}";
+                       var i = 0;
+                       $("#{{ inline_admin_formset.formset.prefix }}-group").find("div.dynamic-form").each(function(){
+                               var fields = $(this).find("div.td :input[value]");
+                               if (fields.serialize()) {
+                                       $(this).find("input[name$='"+sortable_field_name+"']").val(i);
+                                       i++;
+                               }
+                       });
+               });
+               {% endif %}
+               
+       });
 })(django.jQuery);
 </script>
                        {% endfor %}{% endspaceless %}
                {% endfor %}
                {% for form in inline_admin_formset.formset.forms %}
-                       <div class="row cells-{{ form.fields.keys|length }}{% if not form.fields.keys|length_is:"2" %} cells{% endif %}{% if form.errors %} errors{% endif %} {% for field in form %}{{ field.field.name }} {% endfor %}{% comment %} {% if forloop.last %} empty-form{% endif %}{% endcomment %}">
+                       <div class="row cells-{{ form.fields|length }} cells{% if form.errors %} errors{% endif %}{% for field in form %} {{ field.field.name }}{% endfor %}">
                                {{ form.non_field_errors }}
-                               <div{% if not form.fields.keys|length_is:"2" %} class="cell"{% endif %}>
-                                       <div class="column span-4"><label class='required' for="{{ form.content.auto_id }}{{ form.content_id.auto_id }}">{{ form.verbose_name|capfirst }}:</label></div>
+                               <div>
                                {% for field in form %}
                                        {% if not field.is_hidden %}
+                                       {% comment %}This will be true for one field: the content/content reference{% endcomment %}
+                                       <div class="column span-4"><label class='required' for="{{ form.content.auto_id }}{{ form.content_id.auto_id }}">{{ form.verbose_name|capfirst }}:</label></div>
                                        <div class="column span-flexible">
                                                {{ field }}
                                                {{ field.errors }}
diff --git a/philo/templates/admin/philo/page/add_form.html b/philo/templates/admin/philo/page/add_form.html
new file mode 100644 (file)
index 0000000..b2a6358
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends "admin/change_form.html" %}
+{% load i18n %}
+
+{% block form_top %}
+       {% if not is_popup %}
+               <p>{% trans "First, choose a template. After saving, you'll be able to provide additional content for containers." %}</p>
+       {% else %}
+               <p>{% trans "Choose a template" %}</p>
+       {% endif %}
+{% endblock %}
+
+{% block after_field_sets %}
+<script type="text/javascript">document.getElementById("id_name").focus();</script>
+{% endblock %}
\ No newline at end of file
diff --git a/philo/templatetags/__init__.py b/philo/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
similarity index 94%
rename from tests.py
rename to philo/tests.py
index 96ac7b6..a0e0184 100644 (file)
--- a/tests.py
@@ -1,13 +1,17 @@
-from django.test import TestCase
+import sys
+import traceback
+
 from django import template
 from django.conf import settings
 from django.db import connection
 from django.template import loader
 from django.template.loaders import cached
+from django.test import TestCase
+from django.test.utils import setup_test_template_loader
+
+from philo.contrib.penfield.models import Blog, BlogView, BlogEntry
 from philo.exceptions import AncestorDoesNotExist
 from philo.models import Node, Page, Template
-from philo.contrib.penfield.models import Blog, BlogView, BlogEntry
-import sys, traceback
 
 
 class TemplateTestCase(TestCase):
@@ -17,19 +21,15 @@ class TemplateTestCase(TestCase):
                "Tests to make sure that embed behaves with complex includes and extends"
                template_tests = self.get_template_tests()
                
-               # Register our custom template loader. Shamelessly cribbed from django core regressiontests.
-               def test_template_loader(template_name, template_dirs=None):
-                       "A custom template loader that loads the unit-test templates."
-                       try:
-                               return (template_tests[template_name][0] , "test:%s" % template_name)
-                       except KeyError:
-                               raise template.TemplateDoesNotExist, template_name
-               
-               cache_loader = cached.Loader(('test_template_loader',))
-               cache_loader._cached_loaders = (test_template_loader,)
+               # Register our custom template loader. Shamelessly cribbed from django/tests/regressiontests/templates/tests.py:384.
+               cache_loader = setup_test_template_loader(
+                       dict([(name, t[0]) for name, t in template_tests.iteritems()]),
+                       use_cached_loader=True,
+               )
                
-               old_template_loaders = loader.template_source_loaders
-               loader.template_source_loaders = [cache_loader]
+               failures = []
+               tests = template_tests.items()
+               tests.sort()
                
                # Turn TEMPLATE_DEBUG off, because tests assume that.
                old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
@@ -38,9 +38,6 @@ class TemplateTestCase(TestCase):
                old_invalid = settings.TEMPLATE_STRING_IF_INVALID
                expected_invalid_str = 'INVALID'
                
-               failures = []
-               tests = template_tests.items()
-               tests.sort()
                # Run tests
                for name, vals in tests:
                        xx, context, result = vals
similarity index 100%
rename from urls.py
rename to philo/urls.py
similarity index 100%
rename from utils.py
rename to philo/utils.py
similarity index 98%
rename from validators.py
rename to philo/validators.py
index 5ae9409..c8e5dc9 100644 (file)
@@ -118,7 +118,7 @@ class TemplateValidationParser(Parser):
 
 
 def linebreak_iter(template_source):
-       # Cribbed from django/views/debug.py
+       # Cribbed from django/views/debug.py:18
        yield 0
        p = template_source.find('\n')
        while p >= 0:
similarity index 100%
rename from views.py
rename to philo/views.py
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..3c18b16
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+import os
+
+
+# Shamelessly cribbed from django's setup.py file.
+def fullsplit(path, result=None):
+       """
+       Split a pathname into components (the opposite of os.path.join) in a
+       platform-neutral way.
+       """
+       if result is None:
+               result = []
+       head, tail = os.path.split(path)
+       if head == '':
+               return [tail] + result
+       if head == path:
+               return result
+       return fullsplit(head, [tail] + result)
+
+# Compile the list of packages available, because distutils doesn't have
+# an easy way to do this. Shamelessly cribbed from django's setup.py file.
+packages, data_files = [], []
+root_dir = os.path.dirname(__file__)
+if root_dir != '':
+    os.chdir(root_dir)
+philo_dir = 'philo'
+
+for dirpath, dirnames, filenames in os.walk(philo_dir):
+       # Ignore dirnames that start with '.'
+       for i, dirname in enumerate(dirnames):
+               if dirname.startswith('.'): del dirnames[i]
+       if '__init__.py' in filenames:
+               packages.append('.'.join(fullsplit(dirpath)))
+       elif filenames:
+               data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]])
+
+
+version = __import__('philo').VERSION
+
+setup(
+       name = 'Philo',
+       version = '%s.%s' % (version[0], version[1]),
+       packages = packages,
+       data_files = data_files,
+)
\ No newline at end of file
diff --git a/templates/admin/philo/page/add_form.html b/templates/admin/philo/page/add_form.html
deleted file mode 100644 (file)
index 67f6ec4..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-{% extends "admin/change_form.html" %}
-{% load i18n %}
-
-{% block extrahead %}{{ block.super }}
-<!-- This will break if anything ever changes and may not work in all browsers. Sad face. -->
-<script type='text/javascript'>
-(function($){
-       $(function(){
-               $('#page_form input[type=submit]').click(function(e){
-                       if (e.target.name == '_addanother') {
-                               hidden = document.getElementById('page_form')._continue[0]
-                               hidden.parentNode.removeChild(hidden)
-                       }
-               })
-       })
-}(django.jQuery));
-</script>
-{% endblock %}
-
-{% block form_top %}
-       <p>{% trans "First, choose a template. After saving, you'll be able to provide additional content for containers." %}</p>
-       <input type="hidden" name="_continue" value="1" />
-{% endblock %}
-
-{% block content %}
-{% with 0 as save_on_top %}
-{{ block.super }}
-{% endwith %}
-{% endblock %}
\ No newline at end of file