From 16594369d820066373b4bac861e958a169364c62 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Wed, 23 Oct 2013 23:43:37 +0200 Subject: [PATCH] pushing preview for import --- .gitignore | 3 + closure_compiled_binary.js | 189 + .../bootstrap/bootstrap-tooltip-custom.css | 8 + preview/css/bootstrap/bootstrap.css | 112 + preview/css/bootstrap/readme.txt | 1 + preview/css/forms.css | 69 + preview/css/preview-film-section.css | 185 + preview/css/reset.css | 36 + preview/css/settings.css | 167 + preview/css/style.css | 294 + preview/css/tools.css | 229 + .../light_canvas_background.png | Bin 0 -> 223 bytes .../lowcont_dark_canvas_background.png | Bin 0 -> 431 bytes .../lowcont_medium_canvas_background.png | Bin 0 -> 278 bytes .../medium_canvas_background.png | Bin 0 -> 418 bytes preview/img/cloud_export.png | Bin 0 -> 594 bytes preview/img/dragndrop.png | Bin 0 -> 532 bytes preview/img/duplicate.png | Bin 0 -> 634 bytes preview/img/gallery.png | Bin 0 -> 305 bytes preview/img/garbage.png | Bin 0 -> 4857 bytes preview/img/gear.png | Bin 0 -> 789 bytes preview/img/icons/circle.png | Bin 0 -> 761 bytes preview/img/icons/color-palette.png | Bin 0 -> 209 bytes preview/img/icons/dropper.png | Bin 0 -> 543 bytes preview/img/icons/eraser.png | Bin 0 -> 656 bytes preview/img/icons/hand.png | Bin 0 -> 672 bytes preview/img/icons/mirror-pen.png | Bin 0 -> 557 bytes preview/img/icons/paint-bucket.png | Bin 0 -> 707 bytes preview/img/icons/pen.png | Bin 0 -> 450 bytes preview/img/icons/rectangle.png | Bin 0 -> 660 bytes preview/img/icons/select.png | Bin 0 -> 1145 bytes preview/img/icons/stroke.png | Bin 0 -> 450 bytes preview/img/icons/vertical-mirror-pen.png | Bin 0 -> 603 bytes preview/img/icons/wand.png | Bin 0 -> 570 bytes preview/img/import-icon.png | Bin 0 -> 720 bytes preview/img/layers.svg | 62 + preview/img/plus.png | Bin 0 -> 271 bytes preview/img/save.png | Bin 0 -> 395 bytes preview/img/tools/circle-dark.png | Bin 0 -> 3356 bytes preview/img/tools/circle.png | Bin 0 -> 3194 bytes preview/img/tools/eraser-dark.png | Bin 0 -> 909 bytes preview/img/tools/eraser.png | Bin 0 -> 1010 bytes preview/img/tools/eyedropper-dark.png | Bin 0 -> 3918 bytes preview/img/tools/eyedropper.png | Bin 0 -> 3918 bytes preview/img/tools/hand-dark.png | Bin 0 -> 1519 bytes preview/img/tools/hand.png | Bin 0 -> 1065 bytes preview/img/tools/magicwand-dark.png | Bin 0 -> 7703 bytes preview/img/tools/magicwand.png | Bin 0 -> 1982 bytes preview/img/tools/mirror.png | Bin 0 -> 1594 bytes preview/img/tools/paintbucket-dark.png | Bin 0 -> 4104 bytes preview/img/tools/paintbucket.png | Bin 0 -> 1189 bytes preview/img/tools/pen-dark.png | Bin 0 -> 1679 bytes preview/img/tools/pen.png | Bin 0 -> 1679 bytes preview/img/tools/rectangle-dark.png | Bin 0 -> 253 bytes preview/img/tools/rectangle.png | Bin 0 -> 245 bytes .../img/tools/rectangle_selection-dark.png | Bin 0 -> 376 bytes preview/img/tools/rectangle_selection.png | Bin 0 -> 372 bytes preview/img/tools/stroke.png | Bin 0 -> 1222 bytes preview/index.html | 61 + preview/js/Constants.js | 52 + preview/js/Events.js | 67 + preview/js/app.js | 262 + .../controller/AnimatedPreviewController.js | 71 + preview/js/controller/DrawingController.js | 353 + preview/js/controller/LayersListController.js | 59 + .../js/controller/NotificationController.js | 39 + preview/js/controller/PaletteController.js | 83 + preview/js/controller/PiskelController.js | 194 + .../js/controller/PreviewFilmController.js | 220 + preview/js/controller/ToolController.js | 107 + .../settings/ApplicationSettingsController.js | 38 + .../settings/GifExportController.js | 129 + .../controller/settings/ImportController.js | 168 + .../controller/settings/SettingsController.js | 76 + preview/js/drawingtools/BaseTool.js | 75 + preview/js/drawingtools/Circle.js | 85 + preview/js/drawingtools/ColorPicker.js | 29 + preview/js/drawingtools/Eraser.js | 23 + preview/js/drawingtools/Move.js | 54 + preview/js/drawingtools/PaintBucket.js | 36 + preview/js/drawingtools/Rectangle.js | 59 + preview/js/drawingtools/SimplePen.js | 49 + preview/js/drawingtools/Stroke.js | 80 + preview/js/drawingtools/VerticalMirrorPen.js | 46 + .../drawingtools/selectiontools/BaseSelect.js | 160 + .../selectiontools/RectangleSelect.js | 52 + .../selectiontools/ShapeSelect.js | 36 + preview/js/lib/bootstrap/bootstrap.js | 275 + preview/js/lib/bootstrap/readme.txt | 1 + preview/js/lib/gif/gif.js | 3 + preview/js/lib/gif/gif.worker.js | 12 + preview/js/lib/iframeLoader.js | 52 + preview/js/lib/jquery-1.8.0.js | 9227 +++++++++++++++++ preview/js/lib/jquery-ui-1.10.3.custom.js | 2252 ++++ preview/js/lib/jsColor_1_4_0/arrow.gif | Bin 0 -> 66 bytes preview/js/lib/jsColor_1_4_0/cross.gif | Bin 0 -> 83 bytes preview/js/lib/jsColor_1_4_0/demo.html | 12 + preview/js/lib/jsColor_1_4_0/hs.png | Bin 0 -> 2684 bytes preview/js/lib/jsColor_1_4_0/hv.png | Bin 0 -> 2865 bytes preview/js/lib/jsColor_1_4_0/jscolor.js | 927 ++ preview/js/lib/pubsub.js | 25 + preview/js/model/Frame.js | 139 + preview/js/model/Layer.js | 82 + preview/js/model/Piskel.js | 81 + preview/js/rendering/CanvasRenderer.js | 47 + preview/js/rendering/DrawingLoop.js | 61 + preview/js/rendering/FrameRenderer.js | 145 + preview/js/rendering/SpritesheetRenderer.js | 44 + preview/js/selection/BaseSelection.js | 34 + preview/js/selection/RectangularSelection.js | 9 + preview/js/selection/SelectionManager.js | 124 + preview/js/selection/ShapeSelection.js | 9 + preview/js/service/HistoryService.js | 28 + preview/js/service/ImageUploadService.js | 28 + preview/js/service/KeyboardEventService.js | 64 + preview/js/service/LocalStorageService.js | 88 + preview/js/utils/CanvasUtils.js | 22 + preview/js/utils/FileUtils.js | 13 + preview/js/utils/FrameUtils.js | 81 + preview/js/utils/ImageResizer.js | 30 + preview/js/utils/PixelUtils.js | 177 + preview/js/utils/Serializer.js | 87 + preview/js/utils/Template.js | 30 + preview/js/utils/UserSettings.js | 76 + preview/js/utils/core.js | 51 + preview/piskel-boot.js | 42 + preview/piskel-closure-externs.js | 17 + preview/piskel-script-list.js | 85 + preview/templates/drawing-tools.html | 18 + preview/templates/frames-list.html | 7 + preview/templates/layers-list.html | 13 + preview/templates/preview.html | 9 + preview/templates/settings.html | 45 + preview/templates/settings/application.html | 26 + preview/templates/settings/export-gif.html | 18 + preview/templates/settings/import.html | 31 + 136 files changed, 18765 insertions(+) create mode 100644 closure_compiled_binary.js create mode 100644 preview/css/bootstrap/bootstrap-tooltip-custom.css create mode 100644 preview/css/bootstrap/bootstrap.css create mode 100644 preview/css/bootstrap/readme.txt create mode 100644 preview/css/forms.css create mode 100644 preview/css/preview-film-section.css create mode 100644 preview/css/reset.css create mode 100644 preview/css/settings.css create mode 100644 preview/css/style.css create mode 100644 preview/css/tools.css create mode 100644 preview/img/canvas_background/light_canvas_background.png create mode 100644 preview/img/canvas_background/lowcont_dark_canvas_background.png create mode 100644 preview/img/canvas_background/lowcont_medium_canvas_background.png create mode 100644 preview/img/canvas_background/medium_canvas_background.png create mode 100644 preview/img/cloud_export.png create mode 100644 preview/img/dragndrop.png create mode 100644 preview/img/duplicate.png create mode 100644 preview/img/gallery.png create mode 100644 preview/img/garbage.png create mode 100644 preview/img/gear.png create mode 100644 preview/img/icons/circle.png create mode 100644 preview/img/icons/color-palette.png create mode 100644 preview/img/icons/dropper.png create mode 100644 preview/img/icons/eraser.png create mode 100644 preview/img/icons/hand.png create mode 100644 preview/img/icons/mirror-pen.png create mode 100644 preview/img/icons/paint-bucket.png create mode 100644 preview/img/icons/pen.png create mode 100644 preview/img/icons/rectangle.png create mode 100644 preview/img/icons/select.png create mode 100644 preview/img/icons/stroke.png create mode 100644 preview/img/icons/vertical-mirror-pen.png create mode 100644 preview/img/icons/wand.png create mode 100644 preview/img/import-icon.png create mode 100644 preview/img/layers.svg create mode 100644 preview/img/plus.png create mode 100644 preview/img/save.png create mode 100644 preview/img/tools/circle-dark.png create mode 100644 preview/img/tools/circle.png create mode 100644 preview/img/tools/eraser-dark.png create mode 100644 preview/img/tools/eraser.png create mode 100644 preview/img/tools/eyedropper-dark.png create mode 100644 preview/img/tools/eyedropper.png create mode 100644 preview/img/tools/hand-dark.png create mode 100644 preview/img/tools/hand.png create mode 100644 preview/img/tools/magicwand-dark.png create mode 100644 preview/img/tools/magicwand.png create mode 100644 preview/img/tools/mirror.png create mode 100644 preview/img/tools/paintbucket-dark.png create mode 100644 preview/img/tools/paintbucket.png create mode 100644 preview/img/tools/pen-dark.png create mode 100644 preview/img/tools/pen.png create mode 100644 preview/img/tools/rectangle-dark.png create mode 100644 preview/img/tools/rectangle.png create mode 100644 preview/img/tools/rectangle_selection-dark.png create mode 100644 preview/img/tools/rectangle_selection.png create mode 100644 preview/img/tools/stroke.png create mode 100644 preview/index.html create mode 100644 preview/js/Constants.js create mode 100644 preview/js/Events.js create mode 100644 preview/js/app.js create mode 100644 preview/js/controller/AnimatedPreviewController.js create mode 100644 preview/js/controller/DrawingController.js create mode 100644 preview/js/controller/LayersListController.js create mode 100644 preview/js/controller/NotificationController.js create mode 100644 preview/js/controller/PaletteController.js create mode 100644 preview/js/controller/PiskelController.js create mode 100644 preview/js/controller/PreviewFilmController.js create mode 100644 preview/js/controller/ToolController.js create mode 100644 preview/js/controller/settings/ApplicationSettingsController.js create mode 100644 preview/js/controller/settings/GifExportController.js create mode 100644 preview/js/controller/settings/ImportController.js create mode 100644 preview/js/controller/settings/SettingsController.js create mode 100644 preview/js/drawingtools/BaseTool.js create mode 100644 preview/js/drawingtools/Circle.js create mode 100644 preview/js/drawingtools/ColorPicker.js create mode 100644 preview/js/drawingtools/Eraser.js create mode 100644 preview/js/drawingtools/Move.js create mode 100644 preview/js/drawingtools/PaintBucket.js create mode 100644 preview/js/drawingtools/Rectangle.js create mode 100644 preview/js/drawingtools/SimplePen.js create mode 100644 preview/js/drawingtools/Stroke.js create mode 100644 preview/js/drawingtools/VerticalMirrorPen.js create mode 100644 preview/js/drawingtools/selectiontools/BaseSelect.js create mode 100644 preview/js/drawingtools/selectiontools/RectangleSelect.js create mode 100644 preview/js/drawingtools/selectiontools/ShapeSelect.js create mode 100644 preview/js/lib/bootstrap/bootstrap.js create mode 100644 preview/js/lib/bootstrap/readme.txt create mode 100644 preview/js/lib/gif/gif.js create mode 100644 preview/js/lib/gif/gif.worker.js create mode 100644 preview/js/lib/iframeLoader.js create mode 100644 preview/js/lib/jquery-1.8.0.js create mode 100644 preview/js/lib/jquery-ui-1.10.3.custom.js create mode 100644 preview/js/lib/jsColor_1_4_0/arrow.gif create mode 100644 preview/js/lib/jsColor_1_4_0/cross.gif create mode 100644 preview/js/lib/jsColor_1_4_0/demo.html create mode 100644 preview/js/lib/jsColor_1_4_0/hs.png create mode 100644 preview/js/lib/jsColor_1_4_0/hv.png create mode 100644 preview/js/lib/jsColor_1_4_0/jscolor.js create mode 100644 preview/js/lib/pubsub.js create mode 100644 preview/js/model/Frame.js create mode 100644 preview/js/model/Layer.js create mode 100644 preview/js/model/Piskel.js create mode 100644 preview/js/rendering/CanvasRenderer.js create mode 100644 preview/js/rendering/DrawingLoop.js create mode 100644 preview/js/rendering/FrameRenderer.js create mode 100644 preview/js/rendering/SpritesheetRenderer.js create mode 100644 preview/js/selection/BaseSelection.js create mode 100644 preview/js/selection/RectangularSelection.js create mode 100644 preview/js/selection/SelectionManager.js create mode 100644 preview/js/selection/ShapeSelection.js create mode 100644 preview/js/service/HistoryService.js create mode 100644 preview/js/service/ImageUploadService.js create mode 100644 preview/js/service/KeyboardEventService.js create mode 100644 preview/js/service/LocalStorageService.js create mode 100644 preview/js/utils/CanvasUtils.js create mode 100644 preview/js/utils/FileUtils.js create mode 100644 preview/js/utils/FrameUtils.js create mode 100644 preview/js/utils/ImageResizer.js create mode 100644 preview/js/utils/PixelUtils.js create mode 100644 preview/js/utils/Serializer.js create mode 100644 preview/js/utils/Template.js create mode 100644 preview/js/utils/UserSettings.js create mode 100644 preview/js/utils/core.js create mode 100644 preview/piskel-boot.js create mode 100644 preview/piskel-closure-externs.js create mode 100644 preview/piskel-script-list.js create mode 100644 preview/templates/drawing-tools.html create mode 100644 preview/templates/frames-list.html create mode 100644 preview/templates/layers-list.html create mode 100644 preview/templates/preview.html create mode 100644 preview/templates/settings.html create mode 100644 preview/templates/settings/application.html create mode 100644 preview/templates/settings/export-gif.html create mode 100644 preview/templates/settings/import.html diff --git a/.gitignore b/.gitignore index b54edf37..ac72e6ee 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # nodejs local installs node_modules +#build artifacts +build + # sublime text stuff (the -project should actually be shared, but then we'd have to share the same disk location) *.sublime-project *.sublime-workspace diff --git a/closure_compiled_binary.js b/closure_compiled_binary.js new file mode 100644 index 00000000..382b2791 --- /dev/null +++ b/closure_compiled_binary.js @@ -0,0 +1,189 @@ +var Constants={DEFAULT:{HEIGHT:32,WIDTH:32,FPS:12},MODEL_VERSION:1,MAX_HEIGHT:128,MAX_WIDTH:128,PREVIEW_FILM_SIZE:120,DEFAULT_PEN_COLOR:"#000000",TRANSPARENT_COLOR:"TRANSPARENT",SELECTION_TRANSPARENT_COLOR:"rgba(255, 255, 255, 0.6)",TOOL_TARGET_HIGHLIGHT_COLOR:"rgba(255, 255, 255, 0.2)",STATIC:{URL:{SAVE:"http://3.piskel-app.appspot.com/store",GET:"http://3.piskel-app.appspot.com/get"}},APPENGINE:{URL:{SAVE:"save"}},IMAGE_SERVICE_UPLOAD_URL:"http://screenletstore.appspot.com/__/upload",IMAGE_SERVICE_GET_URL:"http://screenletstore.appspot.com/img/", +GRID_STROKE_WIDTH:1,LEFT_BUTTON:"left_button_1",RIGHT_BUTTON:"right_button_2"};var Events={TOOL_SELECTED:"TOOL_SELECTED",TOOL_RELEASED:"TOOL_RELEASED",PRIMARY_COLOR_SELECTED:"PRIMARY_COLOR_SELECTED",PRIMARY_COLOR_UPDATED:"PRIMARY_COLOR_UPDATED",SECONDARY_COLOR_SELECTED:"SECONDARY_COLOR_SELECTED",SECONDARY_COLOR_UPDATED:"SECONDARY_COLOR_UPDATED",LOCALSTORAGE_REQUEST:"LOCALSTORAGE_REQUEST",CANVAS_RIGHT_CLICKED:"CANVAS_RIGHT_CLICKED",REFRESH:"REFRESH",REDRAW_PREVIEWFILM:"REDRAW_PREVIEWFILM",USER_SETTINGS_CHANGED:"USER_SETTINGS_CHANGED",CLOSE_SETTINGS_DRAWER:"CLOSE_SETTINGS_DRAWER", +PISKEL_RESET:"PISKEL_RESET",FRAME_SIZE_CHANGED:"FRAME_SIZE_CHANGED",CURRENT_FRAME_SET:"CURRENT_FRAME_SET",SELECTION_CREATED:"SELECTION_CREATED",SELECTION_MOVE_REQUEST:"SELECTION_MOVE_REQUEST",SELECTION_DISMISSED:"SELECTION_DISMISSED",SHOW_NOTIFICATION:"SHOW_NOTIFICATION",HIDE_NOTIFICATION:"HIDE_NOTIFICATION",UNDO:"UNDO",REDO:"REDO",CUT:"CUT",COPY:"COPY",PASTE:"PASTE"};(function(){$.namespace("pskl").app={init:function(){var a=this.readSizeFromURL_(),d=new pskl.model.Piskel(a.width,a.height),b=new pskl.model.Layer("Layer 1"),a=new pskl.model.Frame(a.width,a.height);b.addFrame(a);d.addLayer(b);this.piskelController=new pskl.controller.PiskelController(d);this.drawingController=new pskl.controller.DrawingController(this.piskelController,$("#drawing-canvas-container"));this.drawingController.init();this.animationController=new pskl.controller.AnimatedPreviewController(this.piskelController, +$("#preview-canvas-container"));this.animationController.init();this.previewsController=new pskl.controller.PreviewFilmController(this.piskelController,$("#preview-list"));this.previewsController.init();this.layersListController=new pskl.controller.LayersListController(this.piskelController);this.layersListController.init();this.settingsController=new pskl.controller.settings.SettingsController(this.piskelController);this.settingsController.init();this.selectionManager=new pskl.selection.SelectionManager(this.piskelController); +this.selectionManager.init();this.historyService=new pskl.service.HistoryService(this.piskelController);this.historyService.init();this.keyboardEventService=new pskl.service.KeyboardEventService;this.keyboardEventService.init();this.notificationController=new pskl.controller.NotificationController;this.notificationController.init();this.localStorageService=new pskl.service.LocalStorageService(this.piskelController);this.localStorageService.init();this.imageUploadService=new pskl.service.ImageUploadService; +this.imageUploadService.init();this.toolController=new pskl.controller.ToolController;this.toolController.init();this.paletteController=new pskl.controller.PaletteController;this.paletteController.init();d=new pskl.rendering.DrawingLoop;d.addCallback(this.render,this);d.start();this.initBootstrapTooltips_();(this.isStaticVersion=!pskl.appEngineToken_)?this.finishInitStatic_():this.finishInitAppEngine_()},finishInitStatic_:function(){var a=this.readFramesheetIdFromURL_();a?($.publish(Events.SHOW_NOTIFICATION, +[{content:"Loading animation with id : ["+a+"]"}]),this.loadFramesheetFromService(a)):this.localStorageService.displayRestoreNotification()},finishInitAppEngine_:function(){if(pskl.framesheetData_&&pskl.framesheetData_.content){var a=pskl.utils.Serializer.createPiskel(pskl.framesheetData_.content);pskl.app.piskelController.setPiskel(a);pskl.app.animationController.setFPS(pskl.framesheetData_.fps)}},initBootstrapTooltips_:function(){$("body").tooltip({selector:"[rel=tooltip]"})},render:function(a){this.drawingController.render(a); +this.animationController.render(a);this.previewsController.render(a)},readSizeFromURL_:function(){var a,d=this.readUrlParameter_("size").split("x");!d||2!=d.length||isNaN(d[0])||isNaN(d[1])?a={height:Constants.DEFAULT.HEIGHT,width:Constants.DEFAULT.WIDTH}:(a=parseInt(d[0],10),d=parseInt(d[1],10),a={height:Math.min(d,Constants.MAX_HEIGHT),width:Math.min(a,Constants.MAX_WIDTH)});return a},readFramesheetIdFromURL_:function(){return this.readUrlParameter_("frameId")},readUrlParameter_:function(a){var d, +b,c=window.location.search.substring(1).split("&");for(d=0;dx"; +document.body.appendChild(c);$(c).find(".close").click($.proxy(this.removeMessage_,this));b.behavior&&b.behavior(c)};a.NotificationController.prototype.removeMessage_=function(a){a=$("#user-message");a.length&&a.remove()}})();(function(){var a=$.namespace("pskl.controller");a.PaletteController=function(){};a.PaletteController.prototype.init=function(){$(".palette-color[data-color=TRANSPARENT]").mouseup($.proxy(this.onPaletteColorClick_,this));$.subscribe(Events.PRIMARY_COLOR_UPDATED,$.proxy(function(a,d){this.updateColorPicker_(d,$("#color-picker"))},this));$.subscribe(Events.SECONDARY_COLOR_UPDATED,$.proxy(function(a,d){this.updateColorPicker_(d,$("#secondary-color-picker"))},this));var a=$("#color-picker");a.val(Constants.DEFAULT_PEN_COLOR); +a.change({isPrimary:!0},$.proxy(this.onPickerChange_,this));a=$("#secondary-color-picker");a.val(Constants.TRANSPARENT_COLOR);a.change({isPrimary:!1},$.proxy(this.onPickerChange_,this));window.jscolor.install()};a.PaletteController.prototype.onPickerChange_=function(a,b){var c=$(a.target);a.data.isPrimary?$.publish(Events.PRIMARY_COLOR_SELECTED,[c.val()]):$.publish(Events.SECONDARY_COLOR_SELECTED,[c.val()])};a.PaletteController.prototype.onPaletteColorClick_=function(a){var b=$(a.target).data("color"), +c=3==a.which;1==a.which?$.publish(Events.PRIMARY_COLOR_SELECTED,[b]):c&&$.publish(Events.SECONDARY_COLOR_SELECTED,[b])};a.PaletteController.prototype.updateColorPicker_=function(a,b){a==Constants.TRANSPARENT_COLOR?(b[0].color.fromString("#fff"),b.val(Constants.TRANSPARENT_COLOR)):b[0].color.fromString(a)}})();(function(){var a=$.namespace("pskl.controller");a.PiskelController=function(a){if(a)this.setPiskel(a);else throw"A piskel instance is mandatory for instanciating PiskelController";};a.PiskelController.prototype.setPiskel=function(a){this.piskel=a;this.currentFrameIndex=this.currentLayerIndex=0;this.layerIdCounter=1;$.publish(Events.PISKEL_RESET);$.publish(Events.FRAME_SIZE_CHANGED)};a.PiskelController.prototype.getHeight=function(){return this.piskel.getHeight()};a.PiskelController.prototype.getWidth= +function(){return this.piskel.getWidth()};a.PiskelController.prototype.getFPS=function(){return pskl.app.animationController.getFPS()};a.PiskelController.prototype.getLayers=function(){return this.piskel.getLayers()};a.PiskelController.prototype.getCurrentLayer=function(){return this.piskel.getLayerAt(this.currentLayerIndex)};a.PiskelController.prototype.getCurrentFrame=function(){return this.getCurrentLayer().getFrameAt(this.currentFrameIndex)};a.PiskelController.prototype.getFrameAt=function(a){var b= +this.getLayers().map(function(c){return c.getFrameAt(a)});return pskl.utils.FrameUtils.merge(b)};a.PiskelController.prototype.hasFrameAt=function(a){return!!this.getCurrentLayer().getFrameAt(a)};a.PiskelController.prototype.getFrameByIndex=a.PiskelController.prototype.getMergedFrameAt;a.PiskelController.prototype.addEmptyFrame=function(){this.getLayers().forEach(function(a){a.addFrame(this.createEmptyFrame_())}.bind(this))};a.PiskelController.prototype.createEmptyFrame_=function(){var a=this.piskel.getWidth(), +b=this.piskel.getHeight();return new pskl.model.Frame(a,b)};a.PiskelController.prototype.removeFrameAt=function(a){this.getLayers().forEach(function(b){b.removeFrameAt(a)});this.currentFrameIndex>=a&&this.setCurrentFrameIndex(this.currentFrameIndex-1);$.publish(Events.PISKEL_RESET)};a.PiskelController.prototype.duplicateFrameAt=function(a){this.getLayers().forEach(function(b){b.duplicateFrameAt(a)})};a.PiskelController.prototype.moveFrame=function(a,b){this.getLayers().forEach(function(c){c.moveFrame(a, +b)})};a.PiskelController.prototype.getFrameCount=function(){return this.piskel.getLayerAt(0).length()};a.PiskelController.prototype.setCurrentFrameIndex=function(a){this.currentFrameIndex=a;$.publish(Events.PISKEL_RESET)};a.PiskelController.prototype.setCurrentLayerIndex=function(a){this.currentLayerIndex=a;$.publish(Events.PISKEL_RESET)};a.PiskelController.prototype.selectLayer=function(a){a=this.getLayers().indexOf(a);-1!=a&&this.setCurrentLayerIndex(a)};a.PiskelController.prototype.selectLayerByName= +function(a){this.hasLayerForName_(a)&&(a=this.piskel.getLayersByName(a)[0],this.selectLayer(a))};a.PiskelController.prototype.generateLayerName_=function(){for(var a="Layer "+this.layerIdCounter;this.hasLayerForName_(a);)this.layerIdCounter++,a="Layer "+this.layerIdCounter;return a};a.PiskelController.prototype.createLayer=function(a){a||(a=this.generateLayerName_());if(this.hasLayerForName_(a))throw"Layer name should be unique";a=new pskl.model.Layer(a);for(var b=0;be&&(a=!0),f-c-b>e&&(g=!0));b=$("#preview-list-wrapper");b.toggleClass("top-overflow-visible",a);b.toggleClass("bottom-overflow-visible",g)};a.PreviewFilmController.prototype.createPreviews_=function(){this.container.html("");$(".tooltip").remove();for(var a=this.piskelController.getFrameCount(),b=0;bAdd new frame

";this.container.append(b);$(b).click(this.addFrame.bind(this));1';$("#tools-container").html(b)}})();(function(){var a=$.namespace("pskl.controller.settings");a.ApplicationSettingsController=function(){};a.ApplicationSettingsController.prototype.init=function(){var a=pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND);$("#background-picker-wrapper").find(".background-picker[data-background-class="+a+"]").addClass("selected");a=pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID);$("#show-grid").prop("checked",a);$("#show-grid").change($.proxy(function(a){a=$("#show-grid").prop("checked");pskl.UserSettings.set(pskl.UserSettings.SHOW_GRID, +a)},this));$("#background-picker-wrapper").click(function(a){a=$(a.target).closest(".background-picker");if(a.length){var d=a.data("background-class");pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND,d);$(".background-picker").removeClass("selected");a.addClass("selected")}})}})();(function(){var a=$.namespace("pskl.controller.settings");a.GifExportController=function(a){this.piskelController=a};a.GifExportController.RESOLUTIONS=[{dpi:1},{dpi:5},{dpi:10,"default":!0},{dpi:20}];a.GifExportController.prototype.init=function(){this.radioTemplate_=pskl.utils.Template.get("gif-export-radio-template");this.previewContainerEl=document.querySelectorAll(".gif-export-preview")[0];this.radioGroupEl=document.querySelectorAll(".gif-export-radio-group")[0];this.uploadForm=$("[name=gif-export-upload-form]"); +this.uploadForm.submit(this.onUploadFormSubmit_.bind(this));this.createRadioElements_()};a.GifExportController.prototype.onUploadFormSubmit_=function(a){a.originalEvent.preventDefault();a=this.getSelectedDpi_();var b=this.piskelController.getFPS();this.renderAsImageDataAnimatedGIF(a,b,this.onGifRenderingCompleted_.bind(this))};a.GifExportController.prototype.onGifRenderingCompleted_=function(a){this.updatePreview_(a);this.previewContainerEl.classList.add("preview-upload-ongoing");pskl.app.imageUploadService.upload(a, +this.onImageUploadCompleted_.bind(this))};a.GifExportController.prototype.onImageUploadCompleted_=function(a){this.updatePreview_(a);this.previewContainerEl.classList.remove("preview-upload-ongoing")};a.GifExportController.prototype.updatePreview_=function(a){this.previewContainerEl.innerHTML="
"};a.GifExportController.prototype.getSelectedDpi_=function(){var a=this.uploadForm.get(0).querySelectorAll("[name=gif-dpi]"),a=Array.prototype.slice.call(a, +0).filter(function(a){return!!a.checked});if(1==a.length)return a[0].value;throw"Unexpected error when retrieving selected dpi";};a.GifExportController.prototype.createRadioElements_=function(){for(var d=a.GifExportController.RESOLUTIONS,b=0;b-h&&(m-=h,a+=k);n").outerWidth(1).jquery||a.each(["Width","Height"],function(c,b){function e(c,b,d,g){a.each(f,function(){b-=parseFloat(a.css(c,"padding"+this))||0;d&&(b-=parseFloat(a.css(c,"border"+this+"Width"))||0);g&&(b-=parseFloat(a.css(c,"margin"+ +this))||0)});return b}var f="Width"===b?["Left","Right"]:["Top","Bottom"],m=b.toLowerCase(),n={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+b]=function(c){return c===d?n["inner"+b].call(this):this.each(function(){a(this).css(m,e(this,c)+"px")})};a.fn["outer"+b]=function(c,d){return"number"!==typeof c?n["outer"+b].call(this,c):this.each(function(){a(this).css(m,e(this,c,!0,d)+"px")})}});a.fn.addBack||(a.fn.addBack=function(a){return this.add(null== +a?this.prevObject:this.prevObject.filter(a))});a("").data("a-b","a").removeData("a-b").data("a-b")&&(a.fn.removeData=function(c){return function(b){return arguments.length?c.call(this,a.camelCase(b)):c.call(this)}}(a.fn.removeData));a.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());a.support.selectstart="onselectstart"in document.createElement("div");a.fn.extend({disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", +function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});a.extend(a.ui,{plugin:{add:function(c,b,d){var e;c=a.ui[c].prototype;for(e in d)c.plugins[e]=c.plugins[e]||[],c.plugins[e].push([b,d[e]])},call:function(a,c,b){var d=a.plugins[c];if(d&&a.element[0].parentNode&&11!==a.element[0].parentNode.nodeType)for(c=0;c",options:{disabled:!1,create:null},_createWidget:function(c,d){d=a(d||this.defaultElement||this)[0];this.element=a(d);this.uuid=b++;this.eventNamespace="."+this.widgetName+this.uuid;this.options=a.widget.extend({}, +this.options,this._getCreateOptions(),c);this.bindings=a();this.hoverable=a();this.focusable=a();d!==this&&(a.data(d,this.widgetFullName,this),this._on(!0,this.element,{remove:function(a){a.target===d&&this.destroy()}}),this.document=a(d.style?d.ownerDocument:d.document||d),this.window=a(this.document[0].defaultView||this.document[0].parentWindow));this._create();this._trigger("create",null,this._getCreateEventData());this._init()},_getCreateOptions:a.noop,_getCreateEventData:a.noop,_create:a.noop, +_init:a.noop,destroy:function(){this._destroy();this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(a.camelCase(this.widgetFullName));this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled");this.bindings.unbind(this.eventNamespace);this.hoverable.removeClass("ui-state-hover");this.focusable.removeClass("ui-state-focus")},_destroy:a.noop,widget:function(){return this.element}, +option:function(c,b){var f=c,k,l,m;if(0===arguments.length)return a.widget.extend({},this.options);if("string"===typeof c)if(f={},k=c.split("."),c=k.shift(),k.length){l=f[c]=a.widget.extend({},this.options[c]);for(m=0;mdocument.documentMode)&&!c.button)return this._mouseUp(c);if(this._mouseStarted)return this._mouseDrag(c),c.preventDefault();this._mouseDistanceMet(c)&&this._mouseDelayMet(c)&&((this._mouseStarted=!1!==this._mouseStart(this._mouseDownEvent,c))?this._mouseDrag(c):this._mouseUp(c)); +return!this._mouseStarted},_mouseUp:function(c){a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);this._mouseStarted&&(this._mouseStarted=!1,c.target===this._mouseDownEvent.target&&a.data(c.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(c));return!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance}, +_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery); +(function(a,d){function b(a){return/left|right/.test(a.css("float"))||/inline|table-cell/.test(a.css("display"))}a.widget("ui.sortable",a.ui.mouse,{version:"1.10.3",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect", +zIndex:1E3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var a=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh();this.floating=this.items.length?"x"===a.axis||b(this.items[0].item):!1;this.offset=this.element.offset();this._mouseInit();this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled");this._mouseDestroy(); +for(var a=this.items.length-1;0<=a;a--)this.items[a].item.removeData(this.widgetName+"-item");return this},_setOption:function(c,b){"disabled"===c?(this.options[c]=b,this.widget().toggleClass("ui-sortable-disabled",!!b)):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(c,b){var d=null,g=!1,h=this;if(this.reverting||this.options.disabled||"static"===this.options.type)return!1;this._refreshItems(c);a(c.target).parents().each(function(){if(a.data(this,h.widgetName+"-item")=== +h)return d=a(this),!1});a.data(c.target,h.widgetName+"-item")===h&&(d=a(c.target));if(!d||this.options.handle&&!b&&(a(this.options.handle,d).find("*").addBack().each(function(){this===c.target&&(g=!0)}),!g))return!1;this.currentItem=d;this._removeCurrentsFromItems();return!0},_mouseStart:function(c,b,d){var g;b=this.options;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(c);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent(); +this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:c.pageX-this.offset.left,top:c.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");this.originalPosition=this._generatePosition(c);this.originalPageX=c.pageX;this.originalPageY=c.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt); +this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!==this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment();b.cursor&&"auto"!==b.cursor&&(g=this.document.find("body"),this.storedCursor=g.css("cursor"),g.css("cursor",b.cursor),this.storedStylesheet=a("").appendTo(g));b.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")), +this.helper.css("opacity",b.opacity));b.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",b.zIndex));this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset());this._trigger("start",c,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(d=this.containers.length-1;0<=d;d--)this.containers[d]._trigger("activate",c,this._uiHash(this));a.ui.ddmanager&& +(a.ui.ddmanager.current=this);a.ui.ddmanager&&!b.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,c);this.dragging=!0;this.helper.addClass("ui-sortable-helper");this._mouseDrag(c);return!0},_mouseDrag:function(c){var b,d,g,h;b=this.options;d=!1;this.position=this._generatePosition(c);this.positionAbs=this._convertPositionTo("absolute");this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+ +this.scrollParent[0].offsetHeight-c.pageYk&&b+pm&&g+qa[this.floating?"width":"height"]?q:ka.left&&this.positionAbs.left+this.offset.click.lefta.top&&this.positionAbs.top+this.offset.click.topa.top+a.height/2&&this.positionAbs.top+this.offset.click.topa.left+a.width/2&&this.positionAbs.left+this.offset.click.left",b.document[0]).addClass(d||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");"tr"===e?b.currentItem.children().each(function(){a(" ",b.document[0]).attr("colspan",a(this).attr("colspan")||1).appendTo(h)}):"img"===e&&h.attr("src",b.currentItem.attr("src"));d||h.css("visibility","hidden");return h},update:function(a,h){if(!d||e.forcePlaceholderSize)h.height()||h.height(b.currentItem.innerHeight()- +parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10)),h.width()||h.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}});b.placeholder=a(e.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);e.placeholder.update(b,b.placeholder)},_contactContainers:function(c){var d,e,g,h,k,l,m,n,q,p=e=null;for(d=this.containers.length-1;0<=d;d--)a.contains(this.currentItem[0], +this.containers[d].element[0])||(this._intersectsWith(this.containers[d].containerCache)?e&&a.contains(this.containers[d].element[0],e.element[0])||(e=this.containers[d],p=d):this.containers[d].containerCache.over&&(this.containers[d]._trigger("out",c,this._uiHash(this)),this.containers[d].containerCache.over=0));if(e)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",c,this._uiHash(this)),this.containers[p].containerCache.over=1);else{d=1E4; +g=null;h=(q=e.floating||b(this.currentItem))?"left":"top";k=q?"width":"height";l=this.positionAbs[h]+this.offset.click[h];for(e=this.items.length-1;0<=e;e--)a.contains(this.containers[p].element[0],this.items[e].item[0])&&this.items[e].item[0]!==this.currentItem[0]&&(!q||this.positionAbs.top+this.offset.click.top>this.items[e].top&&this.positionAbs.top+this.offset.click.topMath.abs(m+this.items[e][k]-l)&& +(n=!0,m+=this.items[e][k]),Math.abs(m-l)this.containment[2]&&(e=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(d=this.containment[3]+this.offset.click.top)),g.grid&&(d=this.originalPageY+Math.round((d- +this.originalPageY)/g.grid[1])*g.grid[1],d=this.containment?d-this.offset.click.top>=this.containment[1]&&d-this.offset.click.top<=this.containment[3]?d:d-this.offset.click.top>=this.containment[1]?d-g.grid[1]:d+g.grid[1]:d,e=this.originalPageX+Math.round((e-this.originalPageX)/g.grid[0])*g.grid[0],e=this.containment?e-this.offset.click.left>=this.containment[0]&&e-this.offset.click.left<=this.containment[2]?e:e-this.offset.click.left>=this.containment[0]?e-g.grid[0]:e+g.grid[0]:e));return{top:d- +this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():k?0:h.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():k?0:h.scrollLeft())}},_rearrange:function(a,b,d,g){d?d[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?b.item[0]:b.item[0].nextSibling);var h=this.counter= +this.counter?++this.counter:1;this._delay(function(){h===this.counter&&this.refreshPositions(!g)})},_clear:function(a,b){this.reverting=!1;var d,g=[];!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem);this._noFinalSort=null;if(this.helper[0]===this.currentItem[0]){for(d in this._storedCSS)if("auto"===this._storedCSS[d]||"static"===this._storedCSS[d])this._storedCSS[d]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show(); +this.fromOutside&&!b&&g.push(function(a){this._trigger("receive",a,this._uiHash(this.fromOutside))});!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||b||g.push(function(a){this._trigger("update",a,this._uiHash())});this===this.currentContainer||b||(g.push(function(a){this._trigger("remove",a,this._uiHash())}),g.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this, +this.currentContainer)),g.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.currentContainer)));for(d=this.containers.length-1;0<=d;d--)b||g.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[d])),this.containers[d].containerCache.over&&(g.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[d])),this.containers[d].containerCache.over=0);this.storedCursor&& +(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove());this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex);this.dragging=!1;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",a,this._uiHash());for(d=0;d undefined.";if(isNaN(b.dpi))throw"Bad FrameRenderer initialization. not well defined.";this.container=a;this.dpi=b.dpi;this.supportGridRendering=b.supportGridRendering;this.classes=c||[];this.classes.push("canvas");this.canvas=null;this.enableGrid(pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)); +this.canvasConfigDirty=!0;this.updateBackgroundClass_(pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND));$.subscribe(Events.USER_SETTINGS_CHANGED,$.proxy(this.onUserSettingsChange_,this))};a.FrameRenderer.prototype.setDPI=function(a){this.dpi=a;this.canvasConfigDirty=!0};a.FrameRenderer.prototype.onUserSettingsChange_=function(a,b,c){b==pskl.UserSettings.SHOW_GRID?this.enableGrid(c):b==pskl.UserSettings.CANVAS_BACKGROUND&&this.updateBackgroundClass_(c)};a.FrameRenderer.prototype.updateBackgroundClass_= +function(a){var b=this.container.data("current-background-class");b&&this.container.removeClass(b);this.container.addClass(a);this.container.data("current-background-class",a)};a.FrameRenderer.prototype.enableGrid=function(a){this.gridStrokeWidth=a&&this.supportGridRendering?Constants.GRID_STROKE_WIDTH:0;this.canvasConfigDirty=!0};a.FrameRenderer.prototype.render=function(a){if(a){this.clear();for(var b=this.getCanvas_(a).getContext("2d"),c=0,f=a.getWidth();c";this.piskelController=a;this.localStorageThrottler_=null};a.LocalStorageService.prototype.init=function(a){$.subscribe(Events.LOCALSTORAGE_REQUEST,$.proxy(this.persistToLocalStorageRequest_,this))};a.LocalStorageService.prototype.persistToLocalStorageRequest_=function(){null!==this.localStorageThrottler_&&window.clearTimeout(this.localStorageThrottler_); +this.localStorageThrottler_=window.setTimeout($.proxy(function(){this.persistToLocalStorage_();this.localStorageThrottler_=null},this),1E3)};a.LocalStorageService.prototype.persistToLocalStorage_=function(){console.log("[LocalStorage service]: Snapshot stored");window.localStorage.snapShot=this.piskelController.serialize()};a.LocalStorageService.prototype.restoreFromLocalStorage_=function(){this.piskelController.deserialize(window.localStorage.snapShot);this.piskelController.setCurrentFrameIndex(0)}; +a.LocalStorageService.prototype.cleanLocalStorage_=function(){console.log("[LocalStorage service]: Snapshot removed");delete window.localStorage.snapShot};a.LocalStorageService.prototype.displayRestoreNotification=function(){window.localStorage&&window.localStorage.snapShot&&$.publish(Events.SHOW_NOTIFICATION,[{content:"Non saved version found. reload or discard", +behavior:$.proxy(function(a){a=$(a);a.click($.proxy(function(a){a=$(a.target);a.hasClass("localstorage-restore")?this.restoreFromLocalStorage_():a.hasClass("localstorage-discard")&&this.cleanLocalStorage_();$.publish(Events.HIDE_NOTIFICATION)},this))},this)}])}})();(function(){$.namespace("pskl").CanvasUtils={createCanvas:function(a,d,b){var c=document.createElement("canvas");c.setAttribute("width",a);c.setAttribute("height",d);"string"==typeof b&&(b=[b]);if(Array.isArray(b))for(a=0;ad[e+3]?"TRANSPARENT":this.rgbToHex(g,h,k)}}return b},rgbToHex:function(a,d,b){return"#"+this.componentToHex(a)+this.componentToHex(d)+this.componentToHex(b)},componentToHex:function(a){a=a.toString(16);return 1==a.length?"0"+a:a}}})();(function(){$.namespace("pskl.utils").ImageResizer={resize:function(a,d,b,c){var f=pskl.CanvasUtils.createCanvas(d,b),e=f.getContext("2d");e.save();c||this.disableSmoothingOnContext(e);e.translate(f.width/2,f.height/2);e.scale(d/a.width,b/a.height);e.drawImage(a,-a.width/2,-a.height/2);e.restore();return f},disableSmoothingOnContext:function(a){a.imageSmoothingEnabled=!1;a.mozImageSmoothingEnabled=!1;a.oImageSmoothingEnabled=!1;a.webkitImageSmoothingEnabled=!1;a.msImageSmoothingEnabled=!1}}})();(function(){$.namespace("pskl").PixelUtils={getRectanglePixels:function(a,d,b,c){a=this.getOrderedRectangleCoordinates(a,d,b,c);d=[];for(b=a.x0;b<=a.x1;b++)for(c=a.y0;c<=a.y1;c++)d.push({col:b,row:c});return d},getBoundRectanglePixels:function(a,d,b,c){a=this.getOrderedRectangleCoordinates(a,d,b,c);d=[];for(b=a.x0;b<=a.x1;b++)d.push({col:b,row:a.y0}),d.push({col:b,row:a.y1});for(b=a.y0;b<=a.y1;b++)d.push({col:a.x0,row:b}),d.push({col:a.x1,row:b});return d},getOrderedRectangleCoordinates:function(a, +d,b,c){return{x0:Math.min(a,b),y0:Math.min(d,c),x1:Math.max(a,b),y1:Math.max(d,c)}},getSimilarConnectedPixelsFromFrame:function(a,d,b){a=a.clone();return this.paintSimilarConnectedPixelsFromFrame(a,d,b,"sdfsdfsdf")},paintSimilarConnectedPixelsFromFrame:function(a,d,b,c){var f=[],e=[],g=[-1,0,1,0],h=[0,1,0,-1],k;try{k=a.getPixel(d,b)}catch(l){}if(k!=c){e.push({col:d,row:b});d=0;for(b=a.getWidth()*a.getHeight();0n;n++){var q=m.col+h[n],p=m.row+g[n];try{a.containsPixel(q,p)&&a.getPixel(q,p)==k&&e.push({col:q,row:p})}catch(r){}}if(d>10*b){console.log("loop breaker called");break}}return f}},calculateDPIForContainer:function(a,d,b){return this.calculateDPI(a.height(),a.width(),d,b)},calculateDPI:function(a,d,b,c){return Math.min(Math.floor(a/b),Math.floor(d/c))}}})();(function(){$.namespace("pskl.utils").Serializer={serializePiskel:function(a){var d=a.getLayers().map(function(a){return pskl.utils.Serializer.serializeLayer(a)});return JSON.stringify({modelVersion:Constants.MODEL_VERSION,piskel:{height:a.getHeight(),width:a.getWidth(),layers:d}})},serializeLayer:function(a){var d=a.getFrames().map(function(a){return a.serialize()});return JSON.stringify({name:a.getName(),frames:d})},deserializePiskel:function(a){a=JSON.parse(a);return this.createPiskelFromData(a)}, +createPiskel:function(a){var d=null;a.modelVersion==Constants.MODEL_VERSION?(a=a.piskel,d=new pskl.model.Piskel(a.width,a.height),a.layers.forEach(function(a){a=pskl.utils.Serializer.deserializeLayer(a);d.addLayer(a)})):d=pskl.utils.Serializer.backwardDeserializer_(a);return d},deserializeLayer:function(a){a=JSON.parse(a);var d=new pskl.model.Layer(a.name);a.frames.forEach(function(a){a=pskl.utils.Serializer.deserializeFrame(a);d.addFrame(a)});return d},deserializeFrame:function(a){a=JSON.parse(a); +return pskl.model.Frame.fromPixelGrid(a)},backwardDeserializer_:function(a){var d=new pskl.model.Layer("Layer 1");a.forEach(function(a){d.addFrame(pskl.model.Frame.fromPixelGrid(a))});a=d.getFrameAt(0).getWidth();var b=d.getFrameAt(0).getHeight();a=new pskl.model.Piskel(a,b);a.addLayer(d);return a}}})();(function(){$.namespace("pskl.utils").Template={get:function(a){var d=document.getElementById(a);if(d)return d.innerHTML;console.error("Could not find template for id :",a)},createFromHTML:function(a){var d=document.createElement("div");d.innerHTML=a;return d.children[0]},replace:function(a,d){for(var b in d)d.hasOwnProperty(b)&&(a=a.replace(RegExp("\\{\\{"+b+"\\}\\}","g"),d[b]));return a}}})();(function(){$.namespace("pskl").UserSettings={SHOW_GRID:"SHOW_GRID",CANVAS_BACKGROUND:"CANVAS_BACKGROUND",KEY_TO_DEFAULT_VALUE_MAP_:{SHOW_GRID:!1,CANVAS_BACKGROUND:"medium-canvas-background"},cache_:{},get:function(a){this.checkKeyValidity_(a);a in this.cache_||(this.cache_[a]=this.readFromLocalStorage_(a)||this.readFromDefaults_(a));return this.cache_[a]},set:function(a,d){this.checkKeyValidity_(a);this.cache_[a]=d;this.writeToLocalStorage_(a,d);$.publish(Events.USER_SETTINGS_CHANGED,[a,d])},readFromLocalStorage_:function(a){a= +window.localStorage[a];"undefined"!=typeof a&&(a=JSON.parse(a));return a},writeToLocalStorage_:function(a,d){window.localStorage[a]=JSON.stringify(d)},readFromDefaults_:function(a){return this.KEY_TO_DEFAULT_VALUE_MAP_[a]},checkKeyValidity_:function(a){a in this.KEY_TO_DEFAULT_VALUE_MAP_||console.log("UserSettings key <"+a+"> not find in supported keys.")}}})();jQuery.namespace=function(){var a=arguments,d=null,b,c,f;for(b=0;b!2~2zADD;%DaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheoCO|{#S9GGf*{OTpAtV2C@59p8c`CQpH@LLaXkt;Z;)lxw?LA2CZm^e#9CF;*vBYB zi&Z~laP}y_|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8pdfpRr>`sfT{a#bMw!Krs+EC4YMCJsCC>S|xv6<2KrRD=b5Uwy zNotBhd1gt5g1e`0fI@zrLZX6iV)E1{@zD$nj9i{Bjv*Dd-rhLK$!sX#9H{=}(Y-tM z+IDKPYZMfdrq}+@>Um(y(=3<0_Pa&Gxymm#-|juW{eSI1l!hOPWhAO?>xdciF99C9hebiqiL8{`&H7&g8tk6Tq65DBpeg z>q{MqmXg{k``b&R^+5(WTD|lr-(|OVe(t5U;vk;F+>#~MvGebpH+y-F8^oL7XLV`r zI{)|SvrAsLf_NV4vM>FvFaN&Tch^@Z5O2xkzLMpyFaN%&cDG6q$OBsJz6+}}zOg^I We0gJwNMa}`+&o?VT-G@yGywp7?!83- literal 0 HcmV?d00001 diff --git a/preview/img/canvas_background/lowcont_medium_canvas_background.png b/preview/img/canvas_background/lowcont_medium_canvas_background.png new file mode 100644 index 0000000000000000000000000000000000000000..3a21f204cf1d9fc367a330c547828c0d2f568d33 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^`ao>J!3HE7mn%O4Qfx`y?k)`fL2$v|<&%LToCO|{ z#S9GG!XV7ZFl&wkP>{XE)7O>#E*l>=hsle}+vWj<)G|XNN}Tg^b5rw5fLsO!=c3fa zlGGH1^30M91$R&10EPTKg+vA4#N??@;-i5I3O!vMLo)8Yz2?Z(V8Fw2K;zQ?|N8bP zUE>p?HMtJGF=crsa42tPQrk-TL#pD=R80YUAd&1Htv@UqQSd$9FO`-*tMybz!kF$a$Ww KelF{r5}E)mB5Yy+ literal 0 HcmV?d00001 diff --git a/preview/img/canvas_background/medium_canvas_background.png b/preview/img/canvas_background/medium_canvas_background.png new file mode 100644 index 0000000000000000000000000000000000000000..2b279524aa7a93da3e00c1b4889cef54f965fe78 GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^u|S;2!3HF^oLnmkq}Y|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8pdfpRr>`sfT{d1(9%cjn?~Fhpwak!+66gHf+|;}hAeVu`xhOTU zBsE2$JhLQ2!QIn0Kp{U*AyL6MF?s5f_-LSlzn(6RAr-gY-U#GsHV|;V7_n<>j#XsL zE@pX+GLiFtb&U!*oM-0tY`o>S_xs-Yx94n@{rm6CcK>;QGk;ZISm-QX@}lYAnr&OJ z?Rxb;|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8pdfpRr>`sfT{d1`bIyOU%S|xv6<2KrRD=b5Uwy zNotBhd1gt5g1e`0fI@zrLZX6iV)E1{@zD$njB7nz978hhy}fzfoh4D?z{mGnW@cZX zVV|HWxbgZr?Rq&a$YOMD-)Ab`1K8Ph=%TC%)gKzaFY^OEzNb~!p4i~3I;%1L`!d(mra;!R z%~`tQ5v=bT6{B64&df4QQ_1aMw_$2OkP_nJn!X;Y?kCds+eF-5##woi^CGL?rIwf{ES5qOubYI6 zh*$i3elm6MBA2^c_i%ilr1Ft9r}Kp4H1EaSYZx{wmVdXn$oB5hnvZ?|E(UKvgdA6( bN7+Ya7N>gI{Z{ifgQC{c)z4*}Q$iB}#!K+q literal 0 HcmV?d00001 diff --git a/preview/img/dragndrop.png b/preview/img/dragndrop.png new file mode 100644 index 0000000000000000000000000000000000000000..b1657dfb6bc3cf30e5993e9710e88181586476cb GIT binary patch literal 532 zcmV+v0_**WP)O)B`7-NVngUwkF z9B|0iclJrYFflVywgQRU)Afdk#0O{Q1Arrdiiqj}H|;+EaT2GczF2T zOlk$50qnKcdjKDI&u0J^?VE2R`dxhsStj7SN>2gCPhSgVfD8~Cpes@hFWC7o^R6p& z&1A8&RbpKpxMU#--!a)?l@caxvm$^IViaIZn5_&jfG`5sRAmGJJ%|Co3`txr0Wh~I z>PZOz2LM$<8T1ICF0>a&44_vBs>A@Qft>F4)~ChEB#9>{aadtpiVxx-qOa{`p-t3* zO;qAdXdwwTDgal7)!q2SDrS-o~

i6}2F80r;{Xxg8QG%^d9(l%5b>WATOfj*)CX0gN~t#h?*J;LR2>u`Rr>>F WT?4L{X$A3OA>zMQFCW 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 A>i_@% literal 0 HcmV?d00001 diff --git a/preview/img/gear.png b/preview/img/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..c09e06fa581b4656b7b9fa878e27aba95e8d8b53 GIT binary patch literal 789 zcmV+w1M2*VP)WFU8GbZ8()Nlj27Z*CxAAWdOomE>Pr0007TNkl4ZF$lId*rI!bBfDJ(`FVtE{U$T16{#`Ze;FN{64j4@^Xm4y*% zbjCucjp{=w{R2h_rIb)YZ7(NW*YDo@yZ4tY9X;eB=bqp1_w_sHe9!mZT%*z8I@%o9 z)NX*KwOhU$7-Jp*Z-J7!4uLl!(wiwT;4@GR{4c5R&a6{ebo5R3#*8sV;1f^Fnco2G41~!3aR$%1_|B7Q47h=GdeTu&`MXKuf0BdSZP1<&V zjm*Hb`|7}HM92u(0xH05{Z-UAidb_1G)3gfmtg0$(8JaT>LPL+^7nwAG3F!i6w9-E((y68KsTTOAsvO`SY1y~ChICQG3CB+W(Om}sm zUG+jGOPDU!MC2r4?J{s=N$0wVeDLM5wyK11-R$eZaiI1spoT78?ZW(d;LM^DmVhH; z%zL0S<{@KDS#{dWh{FFJGp?M4;v(=ph0Jlau8y*z>^n)O>9(wFSqQ9Xhdk*TMA_yNG6oIv%OmtxDX~pIyx@o%R zrrCBkcene!w{vV)bkR!(KAiLYe$VHe?>XP|B`5ApGAM+Qa(xOEVop(%Ii@6F@6#;v zx7Y!0aOm+raE+jb^)1{ho$V98%LjskZa}yJvS?N4kf*8mgy*$=J); zW!p+B7Y4s6fV6W{wFHo%{tol;(erJCU~8$Y6!&K0SfT+{7<)bM{qQvr0DFhXmuhKm zu^3uMhua2=l8hVq@~g_SLNOuwtfaDHB%X=FI>gGdj1B3$JLSBNFP_YOBaQ|C$vl_@&Fnd`H5P0am`q&!@ zfCEM3M4DgO*j@|Ah3M)=F7+#0*v^ZTQb{I=@!8lSa*)eIq?x04U$G+tjt;0_hHOGI zBW~@~OiIs;qoFWF9fb=$O%C8pfIWjHOamOv=E|n@c98*c8l4qeLV)Z&+Z!+lgk0yhmKjPv*wW7qpy95of1#Dp&zj|RU4#UH^poBr{lT9!J-b%2AF?Jiq|bLcQ< zFqz=EYW3-jv(}r>KhezkLN++LB!u372frjBh2yLSz-}KXVfTO3T^u_}bjEXvn0~wA r8U0+^#)-@VayXzw{g(eLzXccocR_wGMq{CL00000NkvXXu0mjfIPhXY literal 0 HcmV?d00001 diff --git a/preview/img/icons/color-palette.png b/preview/img/icons/color-palette.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6e85212b85b7ba0918570c2ebede3047596237 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5iLZjC;k_h{kHThqfC`(hFmxSO zSd?`bB$R({-*5T0gITA{|Fu6pn0eqpx>}JTL$UWimE@yNe}L98c)I$ztaD0e0sx43 BPNV<; literal 0 HcmV?d00001 diff --git a/preview/img/icons/dropper.png b/preview/img/icons/dropper.png new file mode 100644 index 0000000000000000000000000000000000000000..2a161faeb79a247dbd80ad204e01108ea328ecc1 GIT binary patch literal 543 zcmV+)0^t3LP)A3!~jw>Ki$8d;SW1I!v_WiHn30@PX?`T+jM%F?tO8AtA>5R;@Y>jE&9zku-w<6S~u(nw@fs zs|pPj1T0Q2xwQWHnl)!b!@^8~HV6HOc#q*fvJ0?j-gEiY9U&gJm)kp|U%wC#nDBko zssf;r8mtKk1pfT_qx|X9C&z-aTDaz-Xy=navHgGl{=EaVaS6_pc=_^W26lFK+m61e zMKH}-!PZBBm179+>8M4}$~m-n}~nwD~SkdEwTrTh^;K zY|r-d^PP72?C}r4us90LANO%-;sZuE2eJV`+ullx^7R0j2S83nP7laH7$~j)q}hS^ hBUFr$((DWnU;xEL=rr*FUHkw5002ovPDHLkV1mZ;^XLEo literal 0 HcmV?d00001 diff --git a/preview/img/icons/eraser.png b/preview/img/icons/eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d4b0201b9fc3f6f6da0c9b888822eafaac8414 GIT binary patch literal 656 zcmV;B0&o3^P)1RCwB?lh2D1VHn4scXm_|RPb*o z2t2F@!9+q64R!|)wREXlm!UK;JjfC*NEabIL{LnZWuc;5?7HNoAE&4}dY4uA7Bo7F zznz)4=Y4a;*7ejgygcu`?|k0x_kG@X2q6UQh{xlQN~OTzaKP2V8xU}e%?d!m6d(ye z3FglRz)uq>7}!JxMj{c2#bV%cx!{txZUIg1etP?iiB)&*+w1;dlVT>5*#z>nEr66% zJt}oI4Xp@}uDvUL+_|^KjRAZYU~@3|*6#6Pse>@3Xx_4*)9JwN+WP|&K*DcskZd-~ z9XOrNWfnzo+p?_ilWP%0?{>K|vriUTKR*Y*-w(Bi`_I=LpwjkqIt}M?ImJkYfTAi> zW)4_iYc~J(DKFxda1owNCgFTOugqpMFijKOcazISelK93&j%-u_Fv%Wsjv!<$K!CJ zP*BhThwbLNa}$1@o#7f5hpUgDzQkiZK_B0QRpnx_s9^pFX03_J)?R|cr@4bySiob% zzVc(VmCNO_G8&E6j-uUeV+{rHdcCms;kEpL&?ceei#;zg?JO>firZMi qS0)6!_zO_`=C*PVMkCl5`N9Qfv&d*`0-eD}<~0i_h~T|9uK{Sh1iG{f~V95!&s7MKs|n60}C zkzn&HrC{iR*Lc_X(N1o~Q9{P^)rrCDkFPQFm~b3@LTf9wAr;d5u1t=3K0-=LFt^4d zwDcAs-6P=mfUo{R4@Tk%wS15xkMH2}yZ%^*p!kkKv?qH_1hLBwjSXDRw(e3yW`y{hXv0)gfQS~Uxm{K`_{tBKt0531)Juuzx~o6D;hLlf6q@Q5c7xb91>@gRz2|N(4=YMnrVF2udl^ z4x%6q4(cZO2S__eCuh+iD2m-WI7o*I0ZSF6Lm{byS3wCVe#4Dg8=BDIXUsir!C>~kdT`hLf`_q+&ay zVmkmR4)rdtK?c?n+(y4)U+El}7r4qj#7_5V9SMq@23hd=S7dr0y}lmzOO<kqx>Y`N5G#KZuk(+dT&<1^7t!h^v@Ly0|+T4pN73& zpIu^ywlOnjQ8G=` z6$k%*1~6*N*xiU={`!K`?WSiklDB0lKsa*A+vO>IMiTpUo3WxhWm0sk9Q!R`<)*Es7cZe`iGfa00000NkvXXu0mjfwg>H5 literal 0 HcmV?d00001 diff --git a/preview/img/icons/paint-bucket.png b/preview/img/icons/paint-bucket.png new file mode 100644 index 0000000000000000000000000000000000000000..f82a8865a7bc0326fd56f27a869326be4a7d688b GIT binary patch literal 707 zcmV;!0zCbRP)d6{}iR@dma4U zGwNI63={10?wsg6GrMIz9=9gZKjA|+f3>GAK+D|GJH82pDTdw`PIL|}-E9ldO1tCE zh!Y}h7`H{Mc=~qbQ(FLcAgsD6T*IKskM2jy_-qNlZnsM>1c*c;%G7om9HAYZ4@|gr ze-YdHGL{@3SlraFMS}W1yBeCDwgj)70ffV0WjdWkyAqr__<>5Lf>Nmjzuyn#`-SF| zCD@#CrZM!vJ3OHFTxO9ZIYyfsjRp#Z0 zMwk*D4u?7#jpFF&2#1GEG`Byu|C}hU^-i!wvCE+^rj9RT$?eTa}EEYkR zMK{!HwbGGEfk1HjC$2x=82slTsZ=WU`uaN4eVfe&i9|y3`F!?;Cy|z1E>{~228hLC plE>q<_Zv^tYSsXk%l)sJ)*onV;!t9*YM1~3002ovPDHLkV1n&hO1S_4 literal 0 HcmV?d00001 diff --git a/preview/img/icons/pen.png b/preview/img/icons/pen.png new file mode 100644 index 0000000000000000000000000000000000000000..0bfecd50ee9f5bc5828f0c0745aa3e0effcbe250 GIT binary patch literal 450 zcmV;z0X_bSP)Rq1}l<=psl5*5Xz9i;M}s*NP=ugs7Q#8Z;Dyx|}!`#}xw_C3!B-yaPC&0j)XcpuX@rNfq|q}N(wJOjA& z>u+z?dfJEuLePrqzy!)73pvLjxk4d6XNZt?hm_iYES{i}J5y3l?}PPNYDBR7oPc~6 zL^d)Bi4Q2L3pnp!nFxN9c2E+=@XAl&+;2m6a~kZj1r3Mz3C=hmUG<{+vWR@t4q?fJ zhFc(ozZD#Mx`^Q~g1v=K6!QnfuqyD4>U4EjF0eamL}Jx| z%&`kR-H+3GBYr*Qx}frLU4`%n9(`uSomzw)t%%NagXkA*R5Mbv9VLDp1wMo$cOMa~ s3Wm%r7^bwK$2$}-<~D8p`#1iScU4^XCLAA~0ssI207*qoM6N<$g3sK(Qvd(} literal 0 HcmV?d00001 diff --git a/preview/img/icons/rectangle.png b/preview/img/icons/rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..d28dc6b1a4420e67d2ad201e77fa6ffdff47b0f0 GIT binary patch literal 660 zcmV;F0&D$=P)-HHgciWXH=58>$m+)aUP_=fNurq~c9Jfr+{g4OA6VysNJCVr!>1 zyO(ukr-@eVVQOd_eeEr`d;&*7#Zd36d)>ub=%lXvJGs$=On&Pn97vP9zKEPnrBRKfD#EKdHd|_ZeufOm#a)m@NAF&m3;5bVE$Q(zLZS1JKPk-x02D-XgTe_RX z#5m7GmzbE!!E}bj^$Yyl#_Q*HW-gz-5F#kVUOZxW=<7_hAg}KDjh>#B%CwB&uQ)_iwz;@6pmo1X^%*Tz443SFZSDVxCn00004Tx04R}-Ro!pfR1`mnZ(O7nKcKOW4i$^9Ra0BJ8yc;~21%2p=|UR0&DbiW z$#rfTQ`a`O(`{9s_5yDV_yd5l2Of}kLK+Oj_Ok5(v`JGz71bo9J#^YYXp{DWs&KBa zQ@dTpxRI}aIp=pi@6k0t$5)!;m`NF6-tt{FpOKHBn3g+MAqmexC-gw4rh87hTrL7G z#)U`L!(So6-Zux@>;H3gR;i~0B%VTSS3P|m@o9jRsXML@Al^p#@G0Lx-0?i(9WEw_ zSYddU<1E8793KxjQ|c&UmW!mTC>k>?{om1c9S zUx<6_jj_!T&^M{wWM#>IBbOSf*xP<^F{$j$aOQ5Y{cT zROCL1M7^NKKL z&(yA}mSw#iM0^;IB{ZO5!wl{^Sg-*ysE~&Yz8!E;Qv(A`lu*=Clo*MpVGd>OdF6n^ zam1Jntk;<}MrqIC5$=Q>n{*R}?8oOIDUw5En2dl--Xw34!z7E+5pr-OgyQ-soSab)C%saskMla`aQLVzg0+MZf20tJU&K{hZoBrUc+U4e9&3o zw|KmGEe4#xz17wBu{f`SS_4i66?j31EjY7n{zGfhONK~c+td!TS#B}JoR}5UAd7p& z5phTyXSkK0xCeD3xaYP^o&J~#Xp9xFb0C;HHml5fA<%h1eR|qw7wxF+oNL9T1Aits?sKNIwvGaN)^WO$I^cUV)HzL_| z1K?{9p!>B*)`xfEv!4N6IG{J&h49W#Bz^(#YWw%`e_a{8n{G9m5AeR~_yl0%<7V@p zRq1}l<=psl5*5Xz9i;M}s*NP=ugs7Q#8Z;Dyx|}!`#}xw_C3!B-yaPC&0j)XcpuX@rNfq|q}N(wJOjA& z>u+z?dfJEuLePrqzy!)73pvLjxk4d6XNZt?hm_iYES{i}J5y3l?}PPNYDBR7oPc~6 zL^d)Bi4Q2L3pnp!nFxN9c2E+=@XAl&+;2m6a~kZj1r3Mz3C=hmUG<{+vWR@t4q?fJ zhFc(ozZD#Mx`^Q~g1v=K6!QnfuqyD4>U4EjF0eamL}Jx| z%&`kR-H+3GBYr*Qx}frLU4`%n9(`uSomzw)t%%NagXkA*R5Mbv9VLDp1wMo$cOMa~ s3Wm%r7^bwK$2$}-<~D8p`#1iScU4^XCLAA~0ssI207*qoM6N<$g3sK(Qvd(} literal 0 HcmV?d00001 diff --git a/preview/img/icons/vertical-mirror-pen.png b/preview/img/icons/vertical-mirror-pen.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2157d808d6e2257a792a2dd3ad1e9ddefb5c43 GIT binary patch literal 603 zcmV-h0;K(kP)jk3mRNaU93L|Nr(jY_l2UJ2)^m2h&zGb`XMg zDG%YGIu%`lP6k02aaJTkr<6_=i5PSVBzH!ryt+X@B3p!g#Ey)Ei#$Rp3GGR`)+Vx0=LtWG3FRvd~t;w1x~2bX{xDD zIyb1s54n$O0|^-U1#m;@CB_&k^FMyd_vlEt9k%rb9^W!}U2_VC)MM}EvLW53y5VQh(i7O!z~?9K{_&3T z`VQID09AU7GE8h5L9D!+N6XDAR^&(F{`e}3!=iw#}5u#Tg^3nctWfs1Hmk-X; z@nk1*BVEXSn1Q$P6pH#9I{Ky=04#pn0Out=QOHWa{k|wd@iVZLapdxv4FI{kwqq&d z2*s1|`=Tghr43%vpBBDiZb{#{d~3)%`l!=Pghh8- pYX~H4&6M=-*DnmnOBDeTVHZOpxosdUl1cyo002ovPDHLkV1ipX1AhPj literal 0 HcmV?d00001 diff --git a/preview/img/icons/wand.png b/preview/img/icons/wand.png new file mode 100644 index 0000000000000000000000000000000000000000..44ccbf812879c42cb1f9587d865bcfc337ce6361 GIT binary patch literal 570 zcmV-A0>%A_P)hUn2-Navsqo?5A8 zh}}V^=%7@_%C;=H{tGpIj5CMu*>5J=i;m^t2QTySd)}9aAppozC}++wDz`eOViU-dbRRoz=JsVlZk>N%^azhi%=xTCt9`LQjtqNFW~e|R=r9= z`@I3J^#z@aD5yBuq2DLQO#|4uFW6R5kzPZ+h&6Af&5}POarL&lA~3t5R1i7uh*ffDw@qEs=HBW ze?CI~MkvG6H-MF7r{Yv4kw_q&PNP^XqFgTHXlH@RpLO}3aV{T{Ez=8lo;PHV$Ads1 zfOtF(s5%5V>3qE|&{lueV1Y1j%GF zXt&#NI-LlGLPN>_18TJzN~Mxf*f6pY5Dteo^|P&3>(H{!KTr&_wGRQWb^rhX07*qo IM6N<$g7V1&P5=M^ literal 0 HcmV?d00001 diff --git a/preview/img/import-icon.png b/preview/img/import-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ed3106560c3b231fd6a325a97a1036469d0b9f34 GIT binary patch literal 720 zcmV;>0x$iEP)LoK~zYI?U&C>6;Tw&Kl3bk8CoFHBIws5ix4;MqM)4>wTPnD zZGwlGo1p(7TDELe&_ddTv~tt7pr95)A_f(uB^2nBRMO|C#mv)*H$UFkGg|nC3&XwV ze81cHlB_4JZSbP{*POhO;iLdM_jT*Ezn!k=+<> zl&PLA6zqxYR!eN$uZ5I!wE}hSwts#twgtBZ|9`=>&r>(>m@uFkum&8<;5h`0Mz%)w zcY(JJma%G4j#Y-8>Kk<}qq<~aM%^FdE7up&Qn~`11>WZ;sR1*k*+nabN&5rhnb28GX z1Zror%Yy|X-V8ngeI>$-Ta literal 0 HcmV?d00001 diff --git a/preview/img/layers.svg b/preview/img/layers.svg new file mode 100644 index 00000000..34f46180 --- /dev/null +++ b/preview/img/layers.svg @@ -0,0 +1,62 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/preview/img/plus.png b/preview/img/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..6b60af79ec6b529de068160deb2ff610ae8b1ccf GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^#z3sc!3HE-Un=GUDYhhUcNd2LAh=-f^2tCE&H|6f zVg?3oVGw3ym^DWND9B#o>FdgVmyMUt%DORWQZ-OWEi)vd#5q4VH#M&W$Yo$~E=o-- zNlj5G&n(GMaQE~LP{_|yNL27mOrH8AJ{qVX%hSa%B;(%O>lZm240u=%xZN?5)vtcV z=H(dpdhdOwhaL)^DQUA+y=Q9}fWZ3GRmYcyZk65p{ho7b0vO2o^vpS%KKbvfo;!0B}XCW~EqswC>kh?rx{an^LB{Ts5AL3WL literal 0 HcmV?d00001 diff --git a/preview/img/save.png b/preview/img/save.png new file mode 100644 index 0000000000000000000000000000000000000000..fb044dc8302391120ef48a3d8f74905e61fc8711 GIT binary patch literal 395 zcmeAS@N?(olHy`uVBq!ia0vp^+CZ$u!3H837o`Fzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&kwYjh9Q6wJSh#5>QAjGbEzKIX^cyHLnE7WngeFN=+HhCPe7kHol9u#KTulJ~V01kxe0!#69g20(H!vs5T`g=ZD>6yT?|4m(5xKPMY@YpXav& f-FDb2=h6;l85`e^duP{rfWpMn)z4*}Q$iB}e|e1s literal 0 HcmV?d00001 diff --git a/preview/img/tools/circle-dark.png b/preview/img/tools/circle-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..dec10e5ece9cfa9e5280fab2ce1dbf69906df266 GIT binary patch literal 3356 zcma);c{J4h_rNt-XOLtmyKka0o>HObAu^05`!WnAhU|qfV{D_5;faP)mMoFIY@@-@ zGz>lEn{Bc`XhO0tLt||WzmMnlJLh-K_pk5gocHH_-uK?mec$sr_ukiiWNU3I0+s>u z@bHM3n;F~l@En02EA;${aZ%Da|LHHS5z+_$AXm zYWY>1;j15SUFY2k9nKmi%}Q|j2t&8h`_m1Sk`yci{U6cIMG4Qdufwe*{h=5ym!;)* zN$<|oG5^ZMM9lWY=s&Sgxz@$v;wB=4RTv5XME2jkLck&p^ef?1W{QP{MQd<_Yb$oc ztGI7Izs+N<656$zeU4WO&ObDC#mXRdj4?4Wae&M8rgY1JQkoYwqlnh!mw#UvnTve6 z!Ql${r29V223dcU(x##JX)H(~Fi3HxX1S6%`p7xj3z$ppZB;G=v;BX9z-- zB_FP1<$wsw^jE)s_w>`w)vPJlKaPxl)7^8G)zS5rp0gm-5E1W_>*JTd;etfGu;9%s z%(?GyEynog>$p4FbCKi;mQA-N$ilX#KC*LPJo!9>MlZV5bBU$;XhzE&?Ro+ae=S=q zEOLAx#c}DK2`HsK=lW5k4xSKCNgcNHI2Zcyo?CS#-Tn*G7Z10CS7EDlK^ARAqP&^^ zLKFtTtOTn%rOS#O6&H;>n&XR{cr!tcF%`0SL`soENPwA$5R{PUDvktO-hWlYo2jlG zU`Z5$IvWh;((Uz<1<-0F2@#kDSE%ZD>6Aj41{lg0pX2y!Qxr#U!h;n0 z+w8(#d7<*iPXc%~($~{G4U1-eDUb8*xr_R*cZ(qEgEjI_yS10vli#uGJ(ux`1kkG) zGeM6e?+8Bh%A*pWl=D@#;~3;jYr3HNa{YH!T8U`N5yxUmdJZ&^DVdrm`NX(h4C8Fe zRPRcwu@FqU3+4DaUrPj;mgWZ(XLhSn^6cc z-Dd0Iy9;9TrO7PPgMjn*Jeni;qDcL^&54P~Tba#do8kQC*^t5S4I!0h)am z{pWz;muIO74^F?*_-+0ap3ioc9(n-2K~-0W2dh4{DVkOqtcZ{kIs#}$|Ix%fhQ2p_ z_NN+yt6t%9EWhq99jhnm9 zMLE$AeSLOj=|2Kdk}$!n-`v3F=?TTf#el+yGENIf;_F7#js%APk^gypef@H*TTWR) z(A~QUdW>hdR)g(m*V0iG(WIC}rOGCLnDRPUSzS%+^OM*dmAxV1WL`e-BG2bBQ0_q0 zhcVLHBaqCX?JWPCebf~h!pzK!8m{-}&(M=Zlf$!#kyC~paOba?EccpJlVXOEAXPcx z0!)xW5Oca&Jb=u2bd>rw_-+pOHr&9`s$1D&m4tBJ$m zFnnDa4bobTl($J`$DIb`l6*xgw>&Olr&JDq=t!-5T-?Qz@4qZ#fHX4OyI?nF_b@LDd_r^mKHJg4uRK+GR{R=PXBD0 zo}MnKs;-W`M64Sg9@hU|Uw{9ROFnHKyGI6kj0&m$3fBU#`s`YrrV=g>qgxQ zqKwD(E%y$0c6JtcD#Weg8vw`R?45)V);HS6>`$dpvr@?E^&N_4ntR03($Ylp{pn9h z~J69I>Yo7`~_;Es{cze4NBUQ_M>IT5YaQaLbjHy5Mi`cl&`zTDyP7{D}Mp9k^O5QU&eW zmjTR}&0%w?o-$c#OWT}hdJLN(&M|kG=8Ns^+y6dcTD~h>85wN8w3^|3dw1bV!QX4^ z9f}7VJvtLHBi&NP<14qV1~vxDxqAcxVeQvat@LmhVb9eFNFHyQq;d+0wDzsHx7SfY zfJ`Q{zqhxyNBcBfb;)D~P{vXB?`vABJ_`v6S!+mBl#@&Nd`DbLTH1)p6!m4i)Y#x? zsE~jRKFeS`(CX%hJG2h`?a1H(@>b`T3Oqa}S{3k!{mCJw8n#Q?RDc znHvDW4mhmN4E3pY0;CkX6oxWwH*G9wwH(+uX2TWHk(naudnVOnHAzfUAs+}y*8NTIcgM_tx^V{sr;hVv< zVE#v!Nc)vT0R#YEYPc$9kO2n1wl&xP^=R{M2(e`o{wBDUyHzdL?(3t`%gmq`-DpFr zC#=oqhVv^Yw7!rp`)GjU=aqUuPu^G-iK>9%XH@@qu0IK8*_p!VqLAwza2&q?&EY9M zR1tDRHl%dk>QLS_EVc2N4q7JVW#Hd#-?4uxgoY-_Y$Tsn79;j6v|5Lr3CT+c*nTcA zHEA{j4`sq&eX zk`)NNcM|XBa)!xAsSkZ()JpJ3#D^lV#$dzgSJAwM0M%y6bc}0-H{Sb|p$GuH)Uj&Z ziZ=B97g!ItDp~TuBrIQwVIu@Nd)D?Q1{tO<2zV}|+n*Fv*E&a%5DWlFF40E#bpn~p zk1k*3&HQdi6N17^p4fVUx`vLSOaB+n<{sy+5E5m?k?YcVMGiWPCLVC-7_e=1g(QCF z&7)sp$fo!rRRHmblEv%tf>6E`v^2824l7xQHt{&;R|Sj-U&Hpw*NUWVEi2<`n*a)o{67mahw@OGcR#AmIwvG5J5d zn78UP*d-Gz1ClE65&wu@Zt2LCyaA76Q)pmV{!1zIA8j1T++e<_eGY;1UjRAGljXpj<~1H<7I0&rpvO3C3x zKE|IZ1iGUL>15D5$XPsr2crC(JtD*VoDO?(&F*RI)$`fBQhH$(hwKEP_+l?oEjHjN z2=dZT;b7-f%Xv+{yum6M9EnQ$*!_I!2<~rGo5N@JdzZ5Nq!01Ij$2u_N~B3m&G)kq oJYO2Wv&02^zW;8F{|~D-vlDyl&QMmar0=1|9Byq~1M`UgA5BtP)Bpeg literal 0 HcmV?d00001 diff --git a/preview/img/tools/circle.png b/preview/img/tools/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..5f88d0969b7a8f448584fddf16fdcc8d0ac86e9c GIT binary patch literal 3194 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 literal 0 HcmV?d00001 diff --git a/preview/img/tools/eyedropper-dark.png b/preview/img/tools/eyedropper-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9ccfe8b20e1344d02e75fab7adc40b3ce54ac103 GIT binary patch literal 3918 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 literal 0 HcmV?d00001 diff --git a/preview/img/tools/eyedropper.png b/preview/img/tools/eyedropper.png new file mode 100644 index 0000000000000000000000000000000000000000..84a27d22a24e26bf3af0f122c91d9e1b7c9d982d GIT binary patch literal 3918 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 literal 0 HcmV?d00001 diff --git a/preview/img/tools/hand-dark.png b/preview/img/tools/hand-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6c641998b2448cd20c1ccb781c9129be33d38f89 GIT binary patch literal 1519 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 literal 0 HcmV?d00001 diff --git a/preview/img/tools/magicwand.png b/preview/img/tools/magicwand.png new file mode 100644 index 0000000000000000000000000000000000000000..a7dea84539c5ab70d087faf44072285fe1e202bd GIT binary patch literal 1982 zcmbVNeNYo;8h>_K$O2Uof(AUkgIJW~&XQIa@(EMNvsw?FsGN1uY6HW;Y&` zFZr+x$Q1diR;&zaKWK||Nc6PAaia`~cpwHa2vh?kRAC{>-W%_aYyW7cbMMUV^X%{W zc%R>I-rdcQ+bUb+w9E+r7R79q#sgr;#ej4`X3wFEe?i9Koh>pcvS_iJT4`h)(>Cw= z0Ki#jyD<3ptUCgE=`n9b^9CL4z$3D^KqE%TmKf=VKWFwE20ISzleyY&t2@PUo%jCe zoaC{6C4a5-=K8ksq%4=}NsoM^(boRwt2*--8K=IBmQpl6lUn2iuA?G542(GxP$~f* zO66pQsdE#Ahht#KS+j_Q8yW8-+`SKFl&W@!FpNrrYzp`x8j=KpA_F^qWz=ROD#}nOBW@}e3ss!jkg-5uXsYCm}iwc$pylK}c zo~}OcRpJ#0dj@tj6i-xpi-hof$-OYFu5zxY_I#uDyF65qMZ%rJspac1#>)q1RvQgg z93rC#NF5ol*uM;abn}}TXULF~i;~Ho=NigOOBlN6B zqz>hDN6B{G^HcKY{jFL2apQEkLeCBR8-lqeaeOoZpWf01HIgJVs2NW=_Cs5@*cbB3 z&E7JwfdT)Qy`B;fFgU#d3HFH(?(5q?hV#Pj9Ri3v9t-o~I%@-otAR+mHe92IC z$r;$0EfXpFBMp~g-^LXJ@2Y?-^Vi7{_Anq)T$?K|!4dekz046yzYOh94iq&7hGUe` zezVp4C2%HG`xjj}*E*w_8QFC|f%Nuv|6RQGUM=n|;LUdgIc0Zy)udx)~ zE{2F}L)??8hmVey6-VRDK4-3+Ya0tBVEAB*KwUTx(y;{IV4%cE&(^tTm3cXUuw+wNH;P2EFp1 z_cJ!cpET>Qv@H0ruModS2}!2fv&o(q@|bNpf>N18gBIn7oi>kWm@U1@j+?C&k=&gLzX@LKdi3yOAAM`qoN-ij zgP^Q04p_^%!LMo~U&l)6dqUOJquB0ACGN|8dtgDw?oMshI5OsQdV0(|aqofF*}$%R zvv8CiRV5_Ph`=P%bd0qgkFAhHyN{S|7Uu$@q$y6c4Xr?&SJ2~=T;^ODpDmO(erDS* zj*XPoS!Vg&MdrPin>)7@;$Tu81{ntPe!m&`k;S z*IR6wofNn~|Hrx20h;%hK&i&kT$85_u*vvXuk$#WYLa+Iy&|>Nn_`#TM=JFl8?L$V zy%fkb$<~ZX<-D68WEY&~dKcb`*hoUX|7b4o3(WYCpRa_qN3&{d!z0mZ&1j}FIr>_e zoYAH#3|K!Dme_{MwT1a8?1Q85IbNIVGwHh>E<)$QS+BwA1 z4w0<$)?91Xjn22b2(=C^M+&3=$6$CZvvAm7(v^WY`t3{gYk0YD*s93JQ@J z-^L#Vc(Cdxs4gs5TsNZ>ylhz=g`TVI}y$nrTgkO!1Kn#Vzsz1bb8ndwf7Q9sP>{!dDez;iQX!` IyisxTKQJ{`Hvj+t literal 0 HcmV?d00001 diff --git a/preview/img/tools/mirror.png b/preview/img/tools/mirror.png new file mode 100644 index 0000000000000000000000000000000000000000..272a37c49221f11f53af5d0cf0112e48dc621e3b GIT binary patch literal 1594 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 DQ}>`* literal 0 HcmV?d00001 diff --git a/preview/img/tools/paintbucket.png b/preview/img/tools/paintbucket.png new file mode 100644 index 0000000000000000000000000000000000000000..092eea7b17b3b26d6f0f1ca0950aa07dcd88db42 GIT binary patch literal 1189 zcmZ{kX;2e)0LA~itYm}4Y!U);Ny=p{Q^W+A7Azzo5mGtCi>k~da(hL}Q50hct0*f9 z2m)3_uy|lC3RA=?S26|^6=Vty5shJ@lq#TXMWqdp#(wXcdG9y#zQ0GTiw>vRIN1OI zXp#ugdJ9#bj9OdrPRRBL7FZ{&4HsEx(cfWhc@|L9BerA$AoBcVh@Yz6XelXKk|;4n zZ*9eLWls5<697QC5|LozzLBY#SosS-TM_U0qthm7QkAJ3F0zk0d)V%%5xWX$8uxg90@dQCxg&RtEDg zeO5<+`$!Q_RW7dOYTUtD-4mR{0DFxx2kHg_U z?G2ej&S8m6^8!RMDA|l4Z&rISbw-P(G8f$k`-Q6bImVB&S9dvJ*rA{VgK&-udo`R#0IuT@YK&Zp3BVtz|4JwlM}KycIad5nx3tQIyAjYjPWT> zG0zGsdvj*^?G{*bu+~eDl?rwYpV#Doe$;8qhuDi=9a{ErL( z9_r2h*(%EO>t7-*j+|AANsV$jRkIvKj(pp($J>u^8t?0Q9O2gd3TW1@|$8+jJ!raM4 zZ$6z$Th^TwW~jFvjS7CH1;6woF;5j(@QSOn=z;5-#5Zc~J6ig*kJ6UE3v4Or!+*M< zPbfand;4{c!`Lp{ylSh-C7`;ZF|gI!?yWpIWa!nIOs;CN>XxzV6vUl?dUtl`Bm?on z1~AabVw&6a@|(M666=oSD^JpC(4oQ3KLFF<0S&CKYc9!3e<>r~pgKZds&10hRLq7x zO&u((njPl>M6)oT*XlW`zqq!(-$1i-yPD4x!#8H7dEs6FLz0b`50%FHDxHuA^fsGk zlChudM%KS$F?)|z= z?|ZPS*&8VpGXeRsZKMaJW8-pTJyWIU=z{+G73db>3}Y%Z;EKO7)^>)fCN7T|c{Nm` z_Ib(7KZ+Wz=5$L}0{W-5CW4HE)Jrtqk#VrvMw3RyS*fix`$!)k%jX*%;B_<<Z-q z5yY1?rKmnXn#=XuR+M*3>rkW|ZeE5!W!@)(kJ0UcJLjGv{#kBObu;5hHrES$$ebbAXm$-%>F-H%|$hf=i173<|5^itWHdhGab~h|NP9hjwi&Ff4vD%%R3aHZ##&Kl@i~bRd|)fqR~)eva8&x(`Bk0%;~%@py}#e@Jih01 z&M&!}X65IIB8No+fG96FYc>FdTMEJjxXk@(?;>0T^FPSR!WAEH<}p1k5t`gN#Q;%* z$VIWBq^oO2u@A-=uHw*bMdxW zT+ZY~x>P5=aYV6TAJaLsSQgl_9@y0b#wUGqqk^fJYDjigaIC)H@g)tfH=AEjPs4)l zAt+@P;R_%JTUQ$1Kv?MH8j65C?0eUw2(TL+i2CSUONr~{%r^0D?)EDX~^qlahwkg9#$`h+E zHq5#P2`d`)1tG_Rx2<5Z_^@|Rr8PCQM=Z{VT%mOBG@e4|F)SWVv{vCE@Qv>z33h1t z7&Spc)Nl91fN%%t$3|hrz(Y^P)CA3;mNRNvV>7pOcEY(Ve6hg{~)w5NQb!7JlLHxFf%1s+61-pebh*e5~^%55oaVE|Eqz zhOFe{Z(G%}JmZGSC7*Q3C2g7x35v@6l6T*-2+#qMQVFyx`&?r%VRp8#g zJ|FRpQqRdw?^bfb3^&rHQ+DPu&#CV_;;&x`H_BWF5xd4lgnB9^O;8wYVd52@odaJ> zTB#IAfVj)rpSe3AwoQ-VHHMp|lUcZ;Yn1bMmc)NA;fXW)@xluSFfgSqh}*-+*|mD+ z;4f;#cuJSqV<*`eC70b<7dX03;wguOGWvk8xFay}ZiM@XoyzH%x^}2B177#fw96p1 z6(l_}wVY?D_WRRx+X_m9@pj`T?G>Rr>vy|x!2~YiQMV=Xaj#8^em_1ksuZBF;rPng1RzJt_Ze+#3cp*soKDe2U{;7NlBY7HQkxcMpj zg0%*bZ8;MT4=5$K>x#aWJ}%<=6*8z5-gNWZm-?j{;_Hyh@M(qCc~@#{=(F@HmeFj6 zI0_z=pHhP57p5IHh6E@!gT*{P*UNz^(H_S2KL5j$)mEmXob`Xk?TdRuO3r!N`C0ql HFIx96u@K5o literal 0 HcmV?d00001 diff --git a/preview/img/tools/pen.png b/preview/img/tools/pen.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e6d3a49e3fe51cbff81ff7bc01446649d52c51 GIT binary patch literal 1679 zcmb7^drVVz6vxl)Efi?VEfbXqqnDwUYz%zN*92P!2sowAkp(xmqRv;Qwhl5BYkNUW zbvRqcHiMPbAyK!8N+iR_SS#u*J|{7P4{XKyiX)Z+j!GXpzpB%J{9`w{_xJmq$M<~B z`6YMLth{Vd#IOhe5as4%&IX`xOF>vDE_1)yy9k%i`5$Cw;);(aoR*3Id zRjpDfuby5qk~2T#5$R{o9@iM2V~u9*+O@udgKZB-fIR0(^?Vkr-?l24G63d`>>WN2 z(vtnPVPRmiV2f5#5zgn5dV#Im3BB;;UWaD@|aLa}VPLfi(^uW}(kKQC9H5YH2 z#AQr&gj0E9y&e6<8UOlM>;jPlZ$Qs4LJ?=sDq5Zc~Jel*LzH zY?yTo;#M^3@`H{AZ(G1<_G0huN=r&`k64@+xq|7MX*{{stzSHxXsyD7f4%o43ASta z7&Spc)Nl7hfnW#e$4X&E|3i1h)HwB_mNP0^Z8f~-Sd>V>j)Z}yMU_JIDwPoD>WMt^ zlF3x@ABWk?a~LaM`kI8@1gWHxl+=^BQ87dVm2g6;kvtG|%-}=h(W)?rD;^sKnAH=uXm$T-%dCh%|%<3%~Gp+>zZfR!UBXqA7b6e5`dR4#NRUE|Eqz z2CeUslo~=EB|?6~64D8uRyXzs+kAF^!75L?jP7S;=T3#n{r9^FvdnWUys)XRX`rmF zMZevXV>bP zgTJT|<0&oK7Cp(vD7dW7I{(pfiMtHqO6ddMq7MJWyWy_ycPgf5XxpL21bE#)(N4Y8 znxF8<*m9ns+V4-(Zp$wT#M%s-G*<+!%-?N>1rxaNN8RR#$G!GBP)KjQ@fa}+{~Drh zZj*9bW387}r@Q)-so$=HcXOJje!>)n^B$xQ`&t+!4c!UAPD!Ko1y1TkP^$;A#KlkA z7pT>Xtjn1&ct9z*T~~Ci^l=f_FHeSA!A%#xeW_2HF1`*q44;;39CxMGhCXwzd>PHA zi-qu@?34n`KQryHF(^Q>=`7~)xn2&8@wO1I_xT^5tkzO3<*5HNW?#%3QgY7C%FEpU Heqs5)Ky}MC literal 0 HcmV?d00001 diff --git a/preview/img/tools/rectangle-dark.png b/preview/img/tools/rectangle-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..37487c8e62b4daefb1b41441b5c54f780af75665 GIT binary patch literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^K0s{6!3HFy-t7Mlq&N#aB8wRqxP?KOkzv*x380{4 ziEBhjaDG}zd16s2gJVj5QmTSyZen_BP-;BmO< za%XjEQR$`|tSoy)YvsKZkGogpxVgxu^V?=dOYGs6Fh~gAdhg8nRj=yXPM%4QIonn_ z=|BS`vswazWb*;Bp(M`?5F1KL7=YMdGUwO3f8N{9=-8M6`3d)52_-P7wzB^;P4WH< PbQOcAtDnm{r-UW|K}=45 literal 0 HcmV?d00001 diff --git a/preview/img/tools/rectangle.png b/preview/img/tools/rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2898f4f8eda96ac59f634b6ce3530982405a49a6 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^K0s{6!3HFy-t7Mlq&N#aB8wRqxP?KOkzv*x380{4 ziEBhjaDG}zd16s2gJVj5QmTSyZen_BP-_|>L79WRYi3(CNx#tOTO|F0t?vQbSJqVtYcm$ae~qcQpf;i3S>t5$vME8bS4G0^ zn)v<-QI2`P^PP%kV1sB{*Mn@mz}w$`&N-MDxV$Vu(lq(Yr0AOOef`r{{pC2-&&b9j zVUTbxF(@?D&TEa;;n$fyI*$@B&)jkBoOQzM`-ze{*Co%hM<%?RbfAHenau}CJkzt> z_r&hs9^-GwCav*cg$SjM_xYjbNz-a%JSuEnC>@Jv6a7qnXeAz?6`bBVfno(-ZK+f*mxui5;%Yg%|80Yw@rTj zWA?pU*M0Wa9?ZURpklcJ%!q?N^?Us6n`geXo&*w70}*E1fjW|x#Y-6fHDm%AbHHSs z(3vmpUjIDprahh3|FPfde9rznE%!^ZY&c60p00009a7bBm000XU z000XU0RWnu7ytkO8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?1XD>w zK~#9!?3_(Z6G0Tm7h_^PQ9KLwCKOE}(U>Tal_OHyi;;MN3k6SdAYNNOgrg=Bj9%bE z^rlToNW6#%@!$bkwRohSz!N3mzV0NmW?A3N&dknsXO>R#LJHd{^V@m<_vY^~S88M}4!#^8`?W}$0X;lu_C zG+>@-1Ym~uy z2ZCLJyDw;giTl?p^9>r7{WJ>AL^LC{0s}!2726$EFWk*aSvv`UFSqR~u?@&QZo2K9wNi9m@Aj7%ty8wPR3$iT>i0*Ne3@}?XZT1k=@ z3Zy4Na-PV*(4g>yQ4$msj`8}+&;|?%3a<{xgc~`Su%Td`k^X;c4MmooG!=|2D9A;i z)pnRrLIXn@j0hBOT?V}l8%k(kASf&~5SD(L0VNbLP$-oZl7nWmk%5u5h>IGEG%zw( zwkkl80*17*)fq})Fr<~O?oa}Ofl85afg%isw6f)eA}imC91I$?_lcg& z-r@#@84MZ-1Vto0*8vI>7}7v^pVK&@T?Z(>V4z({qEJY#D6#=4axhRx7752u)KfwQ z;|--}1qu}m-qCm_p-{niAD!}6;AK_tnrMBtzx7c2KkCJv@B~S^`5ruJF;ey2aNT zc=n)KWJ+}-5|j|cHtEuXtpE^;xSyo2_z|pG&f>twmTg!%+9{stJRjm62)fOsRQZgoN~IqKgoMg zV{>DpadL8U;o<1$MUJ=qwylC~3;%2=cTyi6=>A!&RaoCxE|t)9Gcvgx%jH!U1P6rD z?caH&`@_TIeiwh;-ZpG^F$V4q8kuaCblDddfVvJ4s56G-M!)v%VFa-TS7h kD7L;Pr3FUK_E&%b08G_^h}#zPzW@LL07*qoM6N<$f=QDtxBvhE literal 0 HcmV?d00001 diff --git a/preview/index.html b/preview/index.html new file mode 100644 index 00000000..309d86f3 --- /dev/null +++ b/preview/index.html @@ -0,0 +1,61 @@ + + + + + + Piskel + + + + + + + + + + + + + + +

+ +
+
+ + +
+
+ +
+ +
+
+
+
+
+ +
+ + +
+
+ +
+
+ +
+
+ + + +
+
+
+
+ +
+ + + + diff --git a/preview/js/Constants.js b/preview/js/Constants.js new file mode 100644 index 00000000..fd0bcdc2 --- /dev/null +++ b/preview/js/Constants.js @@ -0,0 +1,52 @@ +// TODO(grosbouddha): put under pskl namespace. +var Constants = { + DEFAULT : { + HEIGHT : 32, + WIDTH : 32, + FPS : 12 + }, + + MODEL_VERSION : 1, + + MAX_HEIGHT : 128, + MAX_WIDTH : 128, + + PREVIEW_FILM_SIZE : 120, + + DEFAULT_PEN_COLOR : '#000000', + TRANSPARENT_COLOR : 'TRANSPARENT', + + /* + * Fake semi-transparent color used to highlight transparent + * strokes and rectangles: + */ + SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)', + + /* + * When a tool is hovering the drawing canvas, we highlight the eventual + * pixel target with this color: + */ + TOOL_TARGET_HIGHLIGHT_COLOR: 'rgba(255, 255, 255, 0.2)', + + /* + * Default entry point for piskel web service: + */ + STATIC : { + URL : { + SAVE : 'http://3.piskel-app.appspot.com/store', + GET : 'http://3.piskel-app.appspot.com/get' + } + }, + APPENGINE : { + URL : { + SAVE : 'save' + } + }, + IMAGE_SERVICE_UPLOAD_URL : 'http://screenletstore.appspot.com/__/upload', + IMAGE_SERVICE_GET_URL : 'http://screenletstore.appspot.com/img/', + + GRID_STROKE_WIDTH: 1, + + LEFT_BUTTON : 'left_button_1', + RIGHT_BUTTON : 'right_button_2' +}; \ No newline at end of file diff --git a/preview/js/Events.js b/preview/js/Events.js new file mode 100644 index 00000000..e7e5e7ea --- /dev/null +++ b/preview/js/Events.js @@ -0,0 +1,67 @@ +// TODO(grosbouddha): put under pskl namespace. +var Events = { + + TOOL_SELECTED : "TOOL_SELECTED", + TOOL_RELEASED : "TOOL_RELEASED", + PRIMARY_COLOR_SELECTED: "PRIMARY_COLOR_SELECTED", + PRIMARY_COLOR_UPDATED: "PRIMARY_COLOR_UPDATED", + SECONDARY_COLOR_SELECTED: "SECONDARY_COLOR_SELECTED", + SECONDARY_COLOR_UPDATED: "SECONDARY_COLOR_UPDATED", + + /** + * When this event is emitted, a request is sent to the localstorage + * Service to save the current framesheet. The storage service + * may not immediately store data (internal throttling of requests). + */ + LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST", + + CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED", + + /** + * Event to request a refresh of the display. + * A bit overkill but, it's just workaround in our current drawing system. + * TODO: Remove or rework when redraw system is refactored. + */ + REFRESH: "REFRESH", + + /** + * Temporary event to bind the redraw of right preview film to the canvas. + * This redraw should be driven by model updates. + * TODO(vincz): Remove. + */ + REDRAW_PREVIEWFILM: "REDRAW_PREVIEWFILM", + + /** + * Fired each time a user setting change. + * The payload will be: + * 1st argument: Name of the settings + * 2nd argument: New value + */ + USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED", + + /* Listened to by SettingsController */ + CLOSE_SETTINGS_DRAWER : "CLOSE_SETTINGS_DRAWER", + + /** + * The framesheet was reseted and is now probably drastically different. + * Number of frames, content of frames, color used for the palette may have changed. + */ + PISKEL_RESET: "PISKEL_RESET", + + FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED", + + CURRENT_FRAME_SET: "CURRENT_FRAME_SET", + + SELECTION_CREATED: "SELECTION_CREATED", + SELECTION_MOVE_REQUEST: "SELECTION_MOVE_REQUEST", + SELECTION_DISMISSED: "SELECTION_DISMISSED", + + SHOW_NOTIFICATION: "SHOW_NOTIFICATION", + HIDE_NOTIFICATION: "HIDE_NOTIFICATION", + + UNDO: "UNDO", + REDO: "REDO", + CUT: "CUT", + COPY: "COPY", + PASTE: "PASTE" +}; \ No newline at end of file diff --git a/preview/js/app.js b/preview/js/app.js new file mode 100644 index 00000000..3262db09 --- /dev/null +++ b/preview/js/app.js @@ -0,0 +1,262 @@ +/** + * @require Constants + * @require Events + */ +(function () { + var ns = $.namespace("pskl"); + /** + * Main application controller + */ + ns.app = { + + init : function () { + var size = this.readSizeFromURL_(); + var piskel = new pskl.model.Piskel(size.width, size.height); + + var layer = new pskl.model.Layer("Layer 1"); + var frame = new pskl.model.Frame(size.width, size.height); + layer.addFrame(frame); + + piskel.addLayer(layer); + + this.piskelController = new pskl.controller.PiskelController(piskel); + + this.drawingController = new pskl.controller.DrawingController(this.piskelController, $('#drawing-canvas-container')); + this.drawingController.init(); + + this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container')); + this.animationController.init(); + + this.previewsController = new pskl.controller.PreviewFilmController(this.piskelController, $('#preview-list')); + this.previewsController.init(); + + this.layersListController = new pskl.controller.LayersListController(this.piskelController); + this.layersListController.init(); + + this.settingsController = new pskl.controller.settings.SettingsController(this.piskelController); + this.settingsController.init(); + + this.selectionManager = new pskl.selection.SelectionManager(this.piskelController); + this.selectionManager.init(); + + this.historyService = new pskl.service.HistoryService(this.piskelController); + this.historyService.init(); + + this.keyboardEventService = new pskl.service.KeyboardEventService(); + this.keyboardEventService.init(); + + this.notificationController = new pskl.controller.NotificationController(); + this.notificationController.init(); + + this.localStorageService = new pskl.service.LocalStorageService(this.piskelController); + this.localStorageService.init(); + + this.imageUploadService = new pskl.service.ImageUploadService(); + this.imageUploadService.init(); + + this.toolController = new pskl.controller.ToolController(); + this.toolController.init(); + + this.paletteController = new pskl.controller.PaletteController(); + this.paletteController.init(); + + var drawingLoop = new pskl.rendering.DrawingLoop(); + drawingLoop.addCallback(this.render, this); + drawingLoop.start(); + + this.initBootstrapTooltips_(); + + /** + * True when piskel is running in static mode (no back end needed). + * When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl + */ + this.isStaticVersion = !pskl.appEngineToken_; + + if (this.isStaticVersion) { + this.finishInitStatic_(); + } else { + this.finishInitAppEngine_(); + } + }, + + finishInitStatic_ : function () { + var framesheetId = this.readFramesheetIdFromURL_(); + if (framesheetId) { + $.publish(Events.SHOW_NOTIFICATION, [{ + "content" : "Loading animation with id : [" + framesheetId + "]" + }]); + this.loadFramesheetFromService(framesheetId); + } else { + this.localStorageService.displayRestoreNotification(); + } + }, + + finishInitAppEngine_ : function () { + if (pskl.framesheetData_ && pskl.framesheetData_.content) { + var piskel = pskl.utils.Serializer.createPiskel(pskl.framesheetData_.content); + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(pskl.framesheetData_.fps); + } + }, + + initBootstrapTooltips_ : function () { + $('body').tooltip({ + selector: '[rel=tooltip]' + }); + }, + + render : function (delta) { + this.drawingController.render(delta); + this.animationController.render(delta); + this.previewsController.render(delta); + }, + + readSizeFromURL_ : function () { + var sizeParam = this.readUrlParameter_("size"), + size; + // parameter expected as size=64x48 => size=widthxheight + var parts = sizeParam.split("x"); + if (parts && parts.length == 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { + var width = parseInt(parts[0], 10), + height = parseInt(parts[1], 10); + + size = { + height : Math.min(height, Constants.MAX_HEIGHT), + width : Math.min(width, Constants.MAX_WIDTH) + }; + } else { + size = { + height : Constants.DEFAULT.HEIGHT, + width : Constants.DEFAULT.WIDTH + }; + } + return size; + }, + + readFramesheetIdFromURL_ : function () { + return this.readUrlParameter_("frameId"); + }, + + readUrlParameter_ : function (paramName) { + var searchString = window.location.search.substring(1), + i, val, params = searchString.split("&"); + + for (i = 0; i < params.length; i++) { + val = params[i].split("="); + if (val[0] == paramName) { + return window.unescape(val[1]); + } + } + return ""; + }, + + loadFramesheetFromService : function (frameId) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', Constants.STATIC.URL.GET + '?l=' + frameId, true); + xhr.responseType = 'text'; + xhr.onload = function (e) { + var res = JSON.parse(this.responseText); + var piskel = pskl.utils.Serializer.createPiskel(res.framesheet); + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(res.fps); + $.publish(Events.HIDE_NOTIFICATION); + }; + + xhr.onerror = function () { + $.publish(Events.HIDE_NOTIFICATION); + }; + + xhr.send(); + }, + + storeSheet : function (event) { + if (this.isStaticVersion) { + this.storeSheetStatic_(); + } else { + this.storeSheetAppEngine_(); + } + + if(event) { + event.stopPropagation(); + event.preventDefault(); + } + return false; + }, + + storeSheetStatic_ : function () { + var xhr = new XMLHttpRequest(); + var formData = new FormData(); + formData.append('framesheet_content', this.piskelController.serialize()); + formData.append('fps_speed', $('#preview-fps').val()); + + xhr.open('POST', Constants.STATIC.URL.SAVE, true); + + xhr.onload = function(e) { + if (this.status == 200) { + var baseUrl = window.location.href.replace(window.location.search, ""); + window.location.href = baseUrl + "?frameId=" + this.responseText; + } else { + this.onerror(e); + } + }; + xhr.onerror = function(e) { + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]); + }; + xhr.send(formData); + }, + + storeSheetAppEngine_ : function () { + var xhr = new XMLHttpRequest(); + var formData = new FormData(); + formData.append('framesheet_content', this.piskelController.serialize()); + formData.append('fps_speed', $('#preview-fps').val()); + formData.append('name', $('#piskel-name').val()); + formData.append('frames', this.piskelController.getFrameCount()); + formData.append('preview', this.getFirstFrameAsPng()); + formData.append('framesheet', this.getFramesheetAsPng()); + + xhr.open('POST', Constants.APPENGINE.URL.SAVE, true); + + xhr.onload = function(e) { + if (this.status == 200) { + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]); + } else { + this.onerror(e); + } + }; + xhr.onerror = function(e) { + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]); + }; + xhr.send(formData); + }, + + getFirstFrameAsPng : function () { + var firstFrame = this.piskelController.getFrameAt(0); + var frameRenderer = new pskl.rendering.CanvasRenderer(firstFrame, 1); + frameRenderer.drawTransparentAs('rgba(0,0,0,0)'); + var firstFrameCanvas = frameRenderer.render().canvas; + return firstFrameCanvas.toDataURL("image/png"); + }, + + getFramesheetAsPng : function () { + var renderer = new pskl.rendering.SpritesheetRenderer(this.piskelController); + var framesheetCanvas = renderer.render(); + return framesheetCanvas.toDataURL("image/png"); + }, + + uploadAsSpritesheetPNG : function () { + var imageData = this.getFramesheetAsPng(); + this.imageUploadService.upload(imageData, this.openWindow.bind(this)); + }, + + openWindow : function (url) { + var options = [ + "dialog=yes", "scrollbars=no", "status=no", + "width=" + this.piskelController.getWidth() * this.piskelController.getFrameCount(), + "height=" + this.piskelController.getHeight() + ].join(","); + + window.open(url, "piskel-export", options); + } + }; +})(); diff --git a/preview/js/controller/AnimatedPreviewController.js b/preview/js/controller/AnimatedPreviewController.js new file mode 100644 index 00000000..2732d9c0 --- /dev/null +++ b/preview/js/controller/AnimatedPreviewController.js @@ -0,0 +1,71 @@ +(function () { + var ns = $.namespace("pskl.controller"); + ns.AnimatedPreviewController = function (piskelController, container, dpi) { + this.piskelController = piskelController; + this.container = container; + + this.elapsedTime = 0; + this.currentIndex = 0; + + this.setFPS(Constants.DEFAULT.FPS); + + var renderingOptions = { + "dpi": this.calculateDPI_() + }; + this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions); + + $.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this)); + }; + + ns.AnimatedPreviewController.prototype.init = function () { + // the oninput event won't work on IE10 unfortunately, but at least will provide a + // consistent behavior across all other browsers that support the input type range + // see https://bugzilla.mozilla.org/show_bug.cgi?id=853670 + $("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this)); + }; + + ns.AnimatedPreviewController.prototype.onFPSSliderChange = function (evt) { + this.setFPS(parseInt($("#preview-fps")[0].value, 10)); + }; + + ns.AnimatedPreviewController.prototype.setFPS = function (fps) { + this.fps = fps; + $("#preview-fps").val(this.fps); + $("#display-fps").html(this.fps + " FPS"); + }; + + ns.AnimatedPreviewController.prototype.getFPS = function () { + return this.fps; + }; + + ns.AnimatedPreviewController.prototype.render = function (delta) { + this.elapsedTime += delta; + var index = Math.floor(this.elapsedTime / (1000/this.fps)); + if (index != this.currentIndex) { + this.currentIndex = index; + if (!this.piskelController.hasFrameAt(this.currentIndex)) { + this.currentIndex = 0; + this.elapsedTime = 0; + } + this.renderer.render(this.piskelController.getFrameAt(this.currentIndex)); + } + }; + + /** + * Calculate the preview DPI depending on the framesheet size + */ + ns.AnimatedPreviewController.prototype.calculateDPI_ = function () { + var previewSize = 200, + framePixelHeight = this.piskelController.getCurrentFrame().getHeight(), + framePixelWidth = this.piskelController.getCurrentFrame().getWidth(); + // TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?) + + //return pskl.PixelUtils.calculateDPIForContainer($(".preview-container"), framePixelHeight, framePixelWidth); + return pskl.PixelUtils.calculateDPI(previewSize, previewSize, framePixelHeight, framePixelWidth); + }; + + ns.AnimatedPreviewController.prototype.updateDPI_ = function () { + this.dpi = this.calculateDPI_(); + this.renderer.setDPI(this.dpi); + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/DrawingController.js b/preview/js/controller/DrawingController.js new file mode 100644 index 00000000..598b6122 --- /dev/null +++ b/preview/js/controller/DrawingController.js @@ -0,0 +1,353 @@ +(function () { + var ns = $.namespace("pskl.controller"); + ns.DrawingController = function (piskelController, container) { + /** + * @public + */ + this.piskelController = piskelController; + + /** + * @public + */ + this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(piskelController.getCurrentFrame()); + + /** + * @private + */ + this.container = container; + + this.dpi = this.calculateDPI_(); + + // TODO(vincz): Store user prefs in a localstorage string ? + var renderingOptions = { + "dpi": this.dpi, + "supportGridRendering" : true + }; + + this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["canvas-overlay"]); + this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["drawing-canvas"]); + this.layersBelowRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["layers-canvas", "layers-below-canvas"]); + this.layersAboveRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["layers-canvas", "layers-above-canvas"]); + + + // State of drawing controller: + this.isClicked = false; + this.isRightClicked = false; + this.previousMousemoveTime = 0; + this.currentToolBehavior = null; + this.primaryColor = Constants.DEFAULT_PEN_COLOR; + this.secondaryColor = Constants.TRANSPARENT_COLOR; + }; + + ns.DrawingController.prototype.init = function () { + this.initMouseBehavior(); + + $.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) { + console.log("Tool selected: ", toolBehavior); + this.currentToolBehavior = toolBehavior; + this.overlayFrame.clear(); + }, this)); + + /** + * TODO(grosbouddha): Primary/secondary color state are kept in this general controller. + * Find a better place to store that. Perhaps PaletteController? + */ + $.subscribe(Events.PRIMARY_COLOR_SELECTED, $.proxy(function(evt, color) { + console.log("Primary color selected: ", color); + this.primaryColor = color; + $.publish(Events.PRIMARY_COLOR_UPDATED, [color]); + }, this)); + $.subscribe(Events.SECONDARY_COLOR_SELECTED, $.proxy(function(evt, color) { + console.log("Secondary color selected: ", color); + this.secondaryColor = color; + $.publish(Events.SECONDARY_COLOR_UPDATED, [color]); + }, this)); + + $(window).resize($.proxy(this.startDPIUpdateTimer_, this)); + + $.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); + $.subscribe(Events.FRAME_SIZE_CHANGED, $.proxy(this.updateDPI_, this)); + + this.updateDPI_(); + }; + + ns.DrawingController.prototype.initMouseBehavior = function() { + var body = $('body'); + this.container.mousedown($.proxy(this.onMousedown_, this)); + this.container.mousemove($.proxy(this.onMousemove_, this)); + body.mouseup($.proxy(this.onMouseup_, this)); + + // Deactivate right click: + body.contextmenu(this.onCanvasContextMenu_); + }; + + + + ns.DrawingController.prototype.startDPIUpdateTimer_ = function () { + if (this.dpiUpdateTimer) { + window.clearInterval(this.dpiUpdateTimer); + } + this.dpiUpdateTimer = window.setTimeout($.proxy(this.updateDPI_, this), 200); + }, + + /** + * @private + */ + ns.DrawingController.prototype.onUserSettingsChange_ = function (evt, settingsName, settingsValue) { + if(settingsName == pskl.UserSettings.SHOW_GRID) { + this.updateDPI_(); + } + }, + + /** + * @private + */ + ns.DrawingController.prototype.onMousedown_ = function (event) { + this.isClicked = true; + + if(event.button == 2) { // right click + this.isRightClicked = true; + $.publish(Events.CANVAS_RIGHT_CLICKED); + } + + var coords = this.getSpriteCoordinates(event); + + this.currentToolBehavior.applyToolAt( + coords.col, coords.row, + this.getCurrentColor_(), + this.piskelController.getCurrentFrame(), + this.overlayFrame, + this.wrapEvtInfo_(event) + ); + + $.publish(Events.LOCALSTORAGE_REQUEST); + }; + + /** + * @private + */ + ns.DrawingController.prototype.onMousemove_ = function (event) { + var currentTime = new Date().getTime(); + // Throttling of the mousemove event: + if ((currentTime - this.previousMousemoveTime) > 40 ) { + var coords = this.getSpriteCoordinates(event); + if (this.isClicked) { + + this.currentToolBehavior.moveToolAt( + coords.col, coords.row, + this.getCurrentColor_(), + this.piskelController.getCurrentFrame(), + this.overlayFrame, + this.wrapEvtInfo_(event) + ); + + // TODO(vincz): Find a way to move that to the model instead of being at the interaction level. + // Eg when drawing, it may make sense to have it here. However for a non drawing tool, + // you don't need to draw anything when mousemoving and you request useless localStorage. + $.publish(Events.LOCALSTORAGE_REQUEST); + } else { + + this.currentToolBehavior.moveUnactiveToolAt( + coords.col, coords.row, + this.getCurrentColor_(), + this.piskelController.getCurrentFrame(), + this.overlayFrame, + this.wrapEvtInfo_(event) + ); + } + this.previousMousemoveTime = currentTime; + } + }; + + /** + * @private + */ + ns.DrawingController.prototype.onMouseup_ = function (event) { + if(this.isClicked || this.isRightClicked) { + // A mouse button was clicked on the drawing canvas before this mouseup event, + // the user was probably drawing on the canvas. + // Note: The mousemove movement (and the mouseup) may end up outside + // of the drawing canvas. + + this.isClicked = false; + this.isRightClicked = false; + + var coords = this.getSpriteCoordinates(event); + //console.log("mousemove: col: " + spriteCoordinate.col + " - row: " + spriteCoordinate.row); + this.currentToolBehavior.releaseToolAt( + coords.col, coords.row, + this.getCurrentColor_(), + this.piskelController.getCurrentFrame(), + this.overlayFrame, + this.wrapEvtInfo_(event) + ); + + $.publish(Events.TOOL_RELEASED); + } + }; + + /** + * @private + */ + ns.DrawingController.prototype.wrapEvtInfo_ = function (event) { + var evtInfo = {}; + if (event.button === 0) { + evtInfo.button = Constants.LEFT_BUTTON; + } else if (event.button == 2) { + evtInfo.button = Constants.RIGHT_BUTTON; + } + return evtInfo; + }; + + /** + * @private + */ + ns.DrawingController.prototype.getRelativeCoordinates = function (clientX, clientY) { + var canvasPageOffset = this.container.offset(); + return { + x : clientX - canvasPageOffset.left, + y : clientY - canvasPageOffset.top + }; + }; + + /** + * @private + */ + ns.DrawingController.prototype.getSpriteCoordinates = function(event) { + var coords = this.getRelativeCoordinates(event.clientX, event.clientY); + return this.renderer.convertPixelCoordinatesIntoSpriteCoordinate(coords); + }; + + /** + * @private + */ + ns.DrawingController.prototype.getCurrentColor_ = function () { + if(this.isRightClicked) { + return this.secondaryColor; + } else { + return this.primaryColor; + } + }; + + /** + * @private + */ + ns.DrawingController.prototype.onCanvasContextMenu_ = function (event) { + if ($(event.target).closest('#drawing-canvas-container').length) { + // Deactivate right click on drawing canvas only. + event.preventDefault(); + event.stopPropagation(); + event.cancelBubble = true; + return false; + } + }; + + ns.DrawingController.prototype.render = function () { + this.renderLayers(); + this.renderFrame(); + this.renderOverlay(); + }; + + ns.DrawingController.prototype.renderFrame = function () { + var frame = this.piskelController.getCurrentFrame(); + var serializedFrame = this.dpi + "-" + frame.serialize(); + if (this.serializedFrame != serializedFrame) { + if (!frame.isSameSize(this.overlayFrame)) { + this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(frame); + } + this.serializedFrame = serializedFrame; + this.renderer.render(frame); + } + }; + + ns.DrawingController.prototype.renderOverlay = function () { + var serializedOverlay = this.dpi + "-" + this.overlayFrame.serialize(); + if (this.serializedOverlay != serializedOverlay) { + this.serializedOverlay = serializedOverlay; + this.overlayRenderer.render(this.overlayFrame); + } + }; + + ns.DrawingController.prototype.renderLayers = function () { + var layers = this.piskelController.getLayers(); + var currentFrameIndex = this.piskelController.currentFrameIndex; + var currentLayerIndex = this.piskelController.currentLayerIndex; + + var serializedLayerFrame = [ + this.dpi, + currentFrameIndex, + currentLayerIndex, + layers.length + ].join("-"); + + if (this.serializedLayerFrame != serializedLayerFrame) { + this.layersAboveRenderer.clear(); + this.layersBelowRenderer.clear(); + + var downLayers = layers.slice(0, currentLayerIndex); + var downFrame = this.getFrameForLayersAt_(currentFrameIndex, downLayers); + this.layersBelowRenderer.render(downFrame); + + if (currentLayerIndex + 1 < layers.length) { + var upLayers = layers.slice(currentLayerIndex + 1, layers.length); + var upFrame = this.getFrameForLayersAt_(currentFrameIndex, upLayers); + this.layersAboveRenderer.render(upFrame); + } + + this.serializedLayerFrame = serializedLayerFrame; + } + }; + + ns.DrawingController.prototype.getFrameForLayersAt_ = function (frameIndex, layers) { + var frames = layers.map(function (l) { + return l.getFrameAt(frameIndex); + }); + return pskl.utils.FrameUtils.merge(frames); + }; + + /** + * @private + */ + ns.DrawingController.prototype.calculateDPI_ = function() { + var availableViewportHeight = $('#main-wrapper').height(), + leftSectionWidth = $('.left-column').outerWidth(true), + rightSectionWidth = $('.right-column').outerWidth(true), + availableViewportWidth = $('#main-wrapper').width() - leftSectionWidth - rightSectionWidth, + framePixelHeight = this.piskelController.getCurrentFrame().getHeight(), + framePixelWidth = this.piskelController.getCurrentFrame().getWidth(); + + if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) { + availableViewportWidth = availableViewportWidth - (framePixelWidth * Constants.GRID_STROKE_WIDTH); + availableViewportHeight = availableViewportHeight - (framePixelHeight * Constants.GRID_STROKE_WIDTH); + } + + var dpi = pskl.PixelUtils.calculateDPI( + availableViewportHeight, availableViewportWidth, framePixelHeight, framePixelWidth); + + return dpi; + }; + + /** + * @private + */ + ns.DrawingController.prototype.updateDPI_ = function() { + this.dpi = this.calculateDPI_(); + + this.overlayRenderer.setDPI(this.dpi); + this.renderer.setDPI(this.dpi); + this.layersAboveRenderer.setDPI(this.dpi); + this.layersBelowRenderer.setDPI(this.dpi); + + var currentFrameHeight = this.piskelController.getCurrentFrame().getHeight(); + var canvasHeight = currentFrameHeight * this.dpi; + if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) { + canvasHeight += Constants.GRID_STROKE_WIDTH * currentFrameHeight; + } + + var verticalGapInPixel = Math.floor(($('#main-wrapper').height() - canvasHeight) / 2); + $('#column-wrapper').css({ + 'top': verticalGapInPixel + 'px', + 'height': canvasHeight + 'px' + }); + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/LayersListController.js b/preview/js/controller/LayersListController.js new file mode 100644 index 00000000..ed8c7786 --- /dev/null +++ b/preview/js/controller/LayersListController.js @@ -0,0 +1,59 @@ +(function () { + var ns = $.namespace('pskl.controller'); + + ns.LayersListController = function (piskelController) { + this.piskelController = piskelController; + }; + + ns.LayersListController.prototype.init = function () { + this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template'); + this.rootEl = document.querySelectorAll('.layers-list-container')[0]; + this.layersListEl = document.querySelectorAll('.layers-list')[0]; + + this.rootEl.addEventListener('click', this.onClick_.bind(this)); + + $.subscribe(Events.PISKEL_RESET, this.renderLayerList_.bind(this)); + + this.renderLayerList_(); + }; + + ns.LayersListController.prototype.renderLayerList_ = function () { + this.layersListEl.innerHTML = ''; + var layers = this.piskelController.getLayers(); + layers.forEach(this.addLayerItem.bind(this)); + }; + + ns.LayersListController.prototype.addLayerItem = function (layer) { + var layerItemHtml = pskl.utils.Template.replace(this.layerItemTemplate_, { + layername : layer.getName() + }); + var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml); + if (this.piskelController.getCurrentLayer() === layer) { + layerItem.classList.add('current-layer-item'); + } + this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild); + }; + + ns.LayersListController.prototype.onClick_ = function (evt) { + var el = evt.target || evt.srcElement; + if (el.nodeName == 'BUTTON') { + this.onButtonClick_(el); + } else if (el.nodeName == 'LI') { + var layerName = el.getAttribute('data-layer-name'); + this.piskelController.selectLayerByName(layerName); + } + }; + + ns.LayersListController.prototype.onButtonClick_ = function (button) { + var action = button.getAttribute('data-action'); + if (action == 'up') { + this.piskelController.moveLayerUp(); + } else if (action == 'down') { + this.piskelController.moveLayerDown(); + } else if (action == 'add') { + this.piskelController.createLayer(); + } else if (action == 'delete') { + this.piskelController.removeCurrentLayer(); + } + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/NotificationController.js b/preview/js/controller/NotificationController.js new file mode 100644 index 00000000..8b3ba25a --- /dev/null +++ b/preview/js/controller/NotificationController.js @@ -0,0 +1,39 @@ +(function () { + var ns = $.namespace("pskl.controller"); + + ns.NotificationController = function () {}; + + /** + * @public + */ + ns.NotificationController.prototype.init = function() { + $.subscribe(Events.SHOW_NOTIFICATION, $.proxy(this.displayMessage_, this)); + $.subscribe(Events.HIDE_NOTIFICATION, $.proxy(this.removeMessage_, this)); + }; + + /** + * @private + */ + ns.NotificationController.prototype.displayMessage_ = function (evt, messageInfo) { + var message = document.createElement('div'); + message.id = "user-message"; + message.className = "user-message"; + message.innerHTML = messageInfo.content; + message.innerHTML = message.innerHTML + "
x
"; + document.body.appendChild(message); + $(message).find(".close").click($.proxy(this.removeMessage_, this)); + if(messageInfo.behavior) { + messageInfo.behavior(message); + } + }; + + /** + * @private + */ + ns.NotificationController.prototype.removeMessage_ = function (evt) { + var message = $("#user-message"); + if (message.length) { + message.remove(); + } + }; +})(); diff --git a/preview/js/controller/PaletteController.js b/preview/js/controller/PaletteController.js new file mode 100644 index 00000000..48b2a141 --- /dev/null +++ b/preview/js/controller/PaletteController.js @@ -0,0 +1,83 @@ +(function () { + var ns = $.namespace("pskl.controller"); + + ns.PaletteController = function () {}; + + /** + * @public + */ + ns.PaletteController.prototype.init = function() { + var transparentColorPalette = $(".palette-color[data-color=TRANSPARENT]"); + transparentColorPalette.mouseup($.proxy(this.onPaletteColorClick_, this)); + + $.subscribe(Events.PRIMARY_COLOR_UPDATED, $.proxy(function(evt, color) { + this.updateColorPicker_(color, $('#color-picker')); + }, this)); + + $.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) { + this.updateColorPicker_(color, $('#secondary-color-picker')); + }, this)); + + // Initialize colorpickers: + var colorPicker = $('#color-picker'); + colorPicker.val(Constants.DEFAULT_PEN_COLOR); + colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this)); + + + var secondaryColorPicker = $('#secondary-color-picker'); + secondaryColorPicker.val(Constants.TRANSPARENT_COLOR); + secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this)); + + window.jscolor.install(); + }; + + /** + * @private + */ + ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) { + var inputPicker = $(evt.target); + if(evt.data.isPrimary) { + $.publish(Events.PRIMARY_COLOR_SELECTED, [inputPicker.val()]); + } else { + $.publish(Events.SECONDARY_COLOR_SELECTED, [inputPicker.val()]); + } + }; + + /** + * @private + */ + ns.PaletteController.prototype.onPaletteColorClick_ = function (event) { + var selectedColor = $(event.target).data("color"); + var isLeftClick = (event.which == 1); + var isRightClick = (event.which == 3); + if (isLeftClick) { + $.publish(Events.PRIMARY_COLOR_SELECTED, [selectedColor]); + } else if (isRightClick) { + $.publish(Events.SECONDARY_COLOR_SELECTED, [selectedColor]); + } + }; + + /** + * @private + */ + ns.PaletteController.prototype.updateColorPicker_ = function (color, colorPicker) { + if (color == Constants.TRANSPARENT_COLOR) { + // We can set the current palette color to transparent. + // You can then combine this transparent color with an advanced + // tool for customized deletions. + // Eg: bucket + transparent: Delete a colored area + // Stroke + transparent: hollow out the equivalent of a stroke + + // The colorpicker can't be set to a transparent state. + // We set its background to white and insert the + // string "TRANSPARENT" to mimic this state: + colorPicker[0].color.fromString("#fff"); + colorPicker.val(Constants.TRANSPARENT_COLOR); + } else { + colorPicker[0].color.fromString(color); + } + }; +})(); + + + diff --git a/preview/js/controller/PiskelController.js b/preview/js/controller/PiskelController.js new file mode 100644 index 00000000..8d102322 --- /dev/null +++ b/preview/js/controller/PiskelController.js @@ -0,0 +1,194 @@ +(function () { + var ns = $.namespace('pskl.controller'); + + ns.PiskelController = function (piskel) { + if (piskel) { + this.setPiskel(piskel); + } else { + throw 'A piskel instance is mandatory for instanciating PiskelController'; + } + }; + + ns.PiskelController.prototype.setPiskel = function (piskel) { + this.piskel = piskel; + this.currentLayerIndex = 0; + this.currentFrameIndex = 0; + + this.layerIdCounter = 1; + + $.publish(Events.PISKEL_RESET); + $.publish(Events.FRAME_SIZE_CHANGED); + }; + + ns.PiskelController.prototype.getHeight = function () { + return this.piskel.getHeight(); + }; + + ns.PiskelController.prototype.getWidth = function () { + return this.piskel.getWidth(); + }; + + /** + * TODO : this should be removed + * FPS should be stored in the Piskel model and not in the + * animationController + * Then piskelController should be able to return this information + * @return {Number} Frames per second for the current animation + */ + ns.PiskelController.prototype.getFPS = function () { + return pskl.app.animationController.getFPS(); + }; + + ns.PiskelController.prototype.getLayers = function () { + return this.piskel.getLayers(); + }; + + ns.PiskelController.prototype.getCurrentLayer = function () { + return this.piskel.getLayerAt(this.currentLayerIndex); + }; + + ns.PiskelController.prototype.getCurrentFrame = function () { + var layer = this.getCurrentLayer(); + return layer.getFrameAt(this.currentFrameIndex); + }; + + ns.PiskelController.prototype.getFrameAt = function (index) { + var frames = this.getLayers().map(function (l) { + return l.getFrameAt(index); + }); + return pskl.utils.FrameUtils.merge(frames); + }; + + ns.PiskelController.prototype.hasFrameAt = function (index) { + return !!this.getCurrentLayer().getFrameAt(index); + }; + + // backward from framesheet + ns.PiskelController.prototype.getFrameByIndex = + ns.PiskelController.prototype.getMergedFrameAt; + + ns.PiskelController.prototype.addEmptyFrame = function () { + var layers = this.getLayers(); + layers.forEach(function (l) { + l.addFrame(this.createEmptyFrame_()); + }.bind(this)); + }; + + ns.PiskelController.prototype.createEmptyFrame_ = function () { + var w = this.piskel.getWidth(), h = this.piskel.getHeight(); + return new pskl.model.Frame(w, h); + }; + + ns.PiskelController.prototype.removeFrameAt = function (index) { + var layers = this.getLayers(); + layers.forEach(function (l) { + l.removeFrameAt(index); + }); + // Current frame index is impacted if the removed frame was before the current frame + if (this.currentFrameIndex >= index) { + this.setCurrentFrameIndex(this.currentFrameIndex - 1); + } + + $.publish(Events.PISKEL_RESET); + }; + + ns.PiskelController.prototype.duplicateFrameAt = function (index) { + var layers = this.getLayers(); + layers.forEach(function (l) { + l.duplicateFrameAt(index); + }); + }; + + ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) { + var layers = this.getLayers(); + layers.forEach(function (l) { + l.moveFrame(fromIndex, toIndex); + }); + }; + + ns.PiskelController.prototype.getFrameCount = function () { + var layer = this.piskel.getLayerAt(0); + return layer.length(); + }; + + ns.PiskelController.prototype.setCurrentFrameIndex = function (index) { + this.currentFrameIndex = index; + $.publish(Events.PISKEL_RESET); + }; + + ns.PiskelController.prototype.setCurrentLayerIndex = function (index) { + this.currentLayerIndex = index; + $.publish(Events.PISKEL_RESET); + }; + + ns.PiskelController.prototype.selectLayer = function (layer) { + var index = this.getLayers().indexOf(layer); + if (index != -1) { + this.setCurrentLayerIndex(index); + } + }; + + ns.PiskelController.prototype.selectLayerByName = function (name) { + if (this.hasLayerForName_(name)) { + var layer = this.piskel.getLayersByName(name)[0]; + this.selectLayer(layer); + } + }; + + ns.PiskelController.prototype.generateLayerName_ = function () { + var name = "Layer " + this.layerIdCounter; + while (this.hasLayerForName_(name)) { + this.layerIdCounter++; + name = "Layer " + this.layerIdCounter; + } + return name; + }; + + ns.PiskelController.prototype.createLayer = function (name) { + if (!name) { + name = this.generateLayerName_(); + } + if (!this.hasLayerForName_(name)) { + var layer = new pskl.model.Layer(name); + for (var i = 0 ; i < this.getFrameCount() ; i++) { + layer.addFrame(this.createEmptyFrame_()); + } + this.piskel.addLayer(layer); + this.setCurrentLayerIndex(this.piskel.getLayers().length - 1); + } else { + throw 'Layer name should be unique'; + } + }; + + ns.PiskelController.prototype.hasLayerForName_ = function (name) { + return this.piskel.getLayersByName(name).length > 0; + }; + + ns.PiskelController.prototype.moveLayerUp = function () { + var layer = this.getCurrentLayer(); + this.piskel.moveLayerUp(layer); + this.selectLayer(layer); + }; + + ns.PiskelController.prototype.moveLayerDown = function () { + var layer = this.getCurrentLayer(); + this.piskel.moveLayerDown(layer); + this.selectLayer(layer); + }; + + ns.PiskelController.prototype.removeCurrentLayer = function () { + if (this.getLayers().length > 1) { + var layer = this.getCurrentLayer(); + this.piskel.removeLayer(layer); + this.setCurrentLayerIndex(0); + } + }; + + ns.PiskelController.prototype.serialize = function () { + return pskl.utils.Serializer.serializePiskel(this.piskel); + }; + + ns.PiskelController.prototype.load = function (data) { + this.deserialize(JSON.stringify(data)); + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/PreviewFilmController.js b/preview/js/controller/PreviewFilmController.js new file mode 100644 index 00000000..7d1d1310 --- /dev/null +++ b/preview/js/controller/PreviewFilmController.js @@ -0,0 +1,220 @@ +(function () { + var ns = $.namespace("pskl.controller"); + ns.PreviewFilmController = function (piskelController, container, dpi) { + + this.piskelController = piskelController; + this.container = container; + this.dpi = this.calculateDPI_(); + + this.redrawFlag = true; + }; + + ns.PreviewFilmController.prototype.init = function() { + $.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this)); + $.subscribe(Events.PISKEL_RESET, this.flagForRedraw_.bind(this)); + $.subscribe(Events.PISKEL_RESET, this.refreshDPI_.bind(this)); + + $('#preview-list-scroller').scroll(this.updateScrollerOverflows.bind(this)); + this.updateScrollerOverflows(); + }; + + ns.PreviewFilmController.prototype.addFrame = function () { + this.piskelController.addEmptyFrame(); + this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1); + this.updateScrollerOverflows(); + }; + + ns.PreviewFilmController.prototype.flagForRedraw_ = function () { + this.redrawFlag = true; + }; + + ns.PreviewFilmController.prototype.refreshDPI_ = function () { + this.dpi = this.calculateDPI_(); + }; + + ns.PreviewFilmController.prototype.render = function () { + if (this.redrawFlag) { + // TODO(vincz): Full redraw on any drawing modification, optimize. + this.createPreviews_(); + this.redrawFlag = false; + } + }; + + ns.PreviewFilmController.prototype.updateScrollerOverflows = function () { + var scroller = $('#preview-list-scroller'); + var scrollerHeight = scroller.height(); + var scrollTop = scroller.scrollTop(); + var scrollerContentHeight = $('#preview-list').height(); + var treshold = $('.top-overflow').height(); + var overflowTop = false, + overflowBottom = false; + if (scrollerHeight < scrollerContentHeight) { + if (scrollTop > treshold) { + overflowTop = true; + } + var scrollBottom = (scrollerContentHeight - scrollTop) - scrollerHeight; + if (scrollBottom > treshold) { + overflowBottom = true; + } + } + var wrapper = $('#preview-list-wrapper'); + wrapper.toggleClass('top-overflow-visible', overflowTop); + wrapper.toggleClass('bottom-overflow-visible', overflowBottom); + }; + + ns.PreviewFilmController.prototype.createPreviews_ = function () { + + this.container.html(""); + // Manually remove tooltips since mouseout events were shortcut by the DOM refresh: + $(".tooltip").remove(); + + var frameCount = this.piskelController.getFrameCount(); + + for (var i = 0, l = frameCount; i < l ; i++) { + this.container.append(this.createPreviewTile_(i)); + } + // Append 'new empty frame' button + var newFrameButton = document.createElement("div"); + newFrameButton.id = "add-frame-action"; + newFrameButton.className = "add-frame-action"; + newFrameButton.innerHTML = "

Add new frame

"; + this.container.append(newFrameButton); + + $(newFrameButton).click(this.addFrame.bind(this)); + + var needDragndropBehavior = (frameCount > 1); + if(needDragndropBehavior) { + this.initDragndropBehavior_(); + } + this.updateScrollerOverflows(); + }; + + + /** + * @private + */ + ns.PreviewFilmController.prototype.initDragndropBehavior_ = function () { + + $("#preview-list").sortable({ + placeholder: "preview-tile-drop-proxy", + update: $.proxy(this.onUpdate_, this), + items: ".preview-tile" + }); + $("#preview-list").disableSelection(); + }; + + /** + * @private + */ + ns.PreviewFilmController.prototype.onUpdate_ = function( event, ui ) { + var originFrameId = parseInt(ui.item.data("tile-number"), 10); + var targetInsertionId = $('.preview-tile').index(ui.item); + + this.piskelController.moveFrame(originFrameId, targetInsertionId); + this.piskelController.setCurrentFrameIndex(targetInsertionId); + + // TODO(grosbouddha): move localstorage request to the model layer? + $.publish(Events.LOCALSTORAGE_REQUEST); + }; + + + /** + * @private + * TODO(vincz): clean this giant rendering function & remove listeners. + */ + ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) { + var currentFrame = this.piskelController.getCurrentLayer().getFrameAt(tileNumber); + + var previewTileRoot = document.createElement("li"); + var classname = "preview-tile"; + previewTileRoot.setAttribute("data-tile-number", tileNumber); + + if (this.piskelController.getCurrentFrame() == currentFrame) { + classname += " selected"; + } + previewTileRoot.className = classname; + + var canvasContainer = document.createElement("div"); + canvasContainer.className = "canvas-container"; + + var canvasBackground = document.createElement("div"); + canvasBackground.className = "canvas-background"; + canvasContainer.appendChild(canvasBackground); + + previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber)); + + var cloneFrameButton = document.createElement("button"); + cloneFrameButton.setAttribute('rel', 'tooltip'); + cloneFrameButton.setAttribute('data-placement', 'right'); + cloneFrameButton.setAttribute('title', 'Duplicate this frame'); + cloneFrameButton.className = "tile-overlay duplicate-frame-action"; + previewTileRoot.appendChild(cloneFrameButton); + cloneFrameButton.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber)); + + // TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim + // is to make this update function (#createPreviewTile) less aggressive. + var renderingOptions = {"dpi": this.dpi }; + var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, ["tile-view"]); + currentFrameRenderer.render(currentFrame); + + previewTileRoot.appendChild(canvasContainer); + + if(tileNumber > 0 || this.piskelController.getFrameCount() > 1) { + // Add 'remove frame' button. + var deleteButton = document.createElement("button"); + deleteButton.setAttribute('rel', 'tooltip'); + deleteButton.setAttribute('data-placement', 'right'); + deleteButton.setAttribute('title', 'Delete this frame'); + deleteButton.className = "tile-overlay delete-frame-action"; + deleteButton.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber)); + previewTileRoot.appendChild(deleteButton); + + // Add 'dragndrop handle'. + var dndHandle = document.createElement("div"); + dndHandle.className = "tile-overlay dnd-action"; + previewTileRoot.appendChild(dndHandle); + } + var tileCount = document.createElement("div"); + tileCount.className = "tile-overlay tile-count"; + tileCount.innerHTML = tileNumber; + previewTileRoot.appendChild(tileCount); + + + return previewTileRoot; + }; + + ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) { + // has not class tile-action: + if(!evt.target.classList.contains('tile-overlay')) { + this.piskelController.setCurrentFrameIndex(index); + } + }; + + ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) { + this.piskelController.removeFrameAt(index); + $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model + this.updateScrollerOverflows(); + }; + + ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) { + this.piskelController.duplicateFrameAt(index); + $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model + this.piskelController.setCurrentFrameIndex(index + 1); + this.updateScrollerOverflows(); + }; + + /** + * Calculate the preview DPI depending on the piskel size + */ + ns.PreviewFilmController.prototype.calculateDPI_ = function () { + var curFrame = this.piskelController.getCurrentFrame(), + frameHeight = curFrame.getHeight(), + frameWidth = curFrame.getWidth(), + maxFrameDim = Math.max(frameWidth, frameHeight); + + var previewHeight = Constants.PREVIEW_FILM_SIZE * frameHeight / maxFrameDim; + var previewWidth = Constants.PREVIEW_FILM_SIZE * frameWidth / maxFrameDim; + + return pskl.PixelUtils.calculateDPI(previewHeight, previewWidth, frameHeight, frameWidth) || 1; + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/ToolController.js b/preview/js/controller/ToolController.js new file mode 100644 index 00000000..4b38b2f9 --- /dev/null +++ b/preview/js/controller/ToolController.js @@ -0,0 +1,107 @@ +(function () { + var ns = $.namespace("pskl.controller"); + + + ns.ToolController = function () { + + this.toolInstances = { + "simplePen" : new pskl.drawingtools.SimplePen(), + "verticalMirrorPen" : new pskl.drawingtools.VerticalMirrorPen(), + "eraser" : new pskl.drawingtools.Eraser(), + "paintBucket" : new pskl.drawingtools.PaintBucket(), + "stroke" : new pskl.drawingtools.Stroke(), + "rectangle" : new pskl.drawingtools.Rectangle(), + "circle" : new pskl.drawingtools.Circle(), + "move" : new pskl.drawingtools.Move(), + "rectangleSelect" : new pskl.drawingtools.RectangleSelect(), + "shapeSelect" : new pskl.drawingtools.ShapeSelect(), + "colorPicker" : new pskl.drawingtools.ColorPicker() + }; + + this.currentSelectedTool = this.toolInstances.simplePen; + this.previousSelectedTool = this.toolInstances.simplePen; + }; + + /** + * @public + */ + ns.ToolController.prototype.init = function() { + this.createToolMarkup_(); + + // Initialize tool: + // Set SimplePen as default selected tool: + this.selectTool_(this.toolInstances.simplePen); + // Activate listener on tool panel: + $("#tool-section").click($.proxy(this.onToolIconClicked_, this)); + }; + + /** + * @private + */ + ns.ToolController.prototype.activateToolOnStage_ = function(tool) { + var stage = $("body"); + var previousSelectedToolClass = stage.data("selected-tool-class"); + if(previousSelectedToolClass) { + stage.removeClass(previousSelectedToolClass); + } + stage.addClass(tool.toolId); + stage.data("selected-tool-class", tool.toolId); + }; + + /** + * @private + */ + ns.ToolController.prototype.selectTool_ = function(tool) { + console.log("Selecting Tool:" , this.currentSelectedTool); + this.currentSelectedTool = tool; + this.activateToolOnStage_(this.currentSelectedTool); + $.publish(Events.TOOL_SELECTED, [tool]); + }; + + /** + * @private + */ + ns.ToolController.prototype.onToolIconClicked_ = function(evt) { + var target = $(evt.target); + var clickedTool = target.closest(".tool-icon"); + + if(clickedTool.length) { + var toolId = clickedTool.data().toolId; + var tool = this.getToolById_(toolId); + if (tool) { + this.selectTool_(tool); + + // Show tool as selected: + $('#tool-section .tool-icon.selected').removeClass('selected'); + clickedTool.addClass('selected'); + } + } + }; + + ns.ToolController.prototype.getToolById_ = function (toolId) { + for(var key in this.toolInstances) { + if (this.toolInstances[key].toolId == toolId) { + return this.toolInstances[key]; + } + } + return null; + }; + + /** + * @private + */ + ns.ToolController.prototype.createToolMarkup_ = function() { + var currentTool, toolMarkup = '', extraClass; + // TODO(vincz): Tools rendering order is not enforced by the data stucture (this.toolInstances), fix that. + for (var toolKey in this.toolInstances) { + currentTool = this.toolInstances[toolKey]; + extraClass = currentTool.toolId; + if (this.currentSelectedTool == currentTool) { + extraClass = extraClass + " selected"; + } + toolMarkup += '
  • '; + } + $('#tools-container').html(toolMarkup); + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/settings/ApplicationSettingsController.js b/preview/js/controller/settings/ApplicationSettingsController.js new file mode 100644 index 00000000..7329b596 --- /dev/null +++ b/preview/js/controller/settings/ApplicationSettingsController.js @@ -0,0 +1,38 @@ +(function () { + var ns = $.namespace("pskl.controller.settings"); + + ns.ApplicationSettingsController = function () {}; + + /** + * @public + */ + ns.ApplicationSettingsController.prototype.init = function() { + // Highlight selected background picker: + var backgroundClass = pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND); + $('#background-picker-wrapper') + .find('.background-picker[data-background-class=' + backgroundClass + ']') + .addClass('selected'); + + // Initial state for grid display: + var show_grid = pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID); + $('#show-grid').prop('checked', show_grid); + + // Handle grid display changes: + $('#show-grid').change($.proxy(function(evt) { + var checked = $('#show-grid').prop('checked'); + pskl.UserSettings.set(pskl.UserSettings.SHOW_GRID, checked); + }, this)); + + // Handle canvas background changes: + $('#background-picker-wrapper').click(function(evt) { + var target = $(evt.target).closest('.background-picker'); + if (target.length) { + var backgroundClass = target.data('background-class'); + pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, backgroundClass); + + $('.background-picker').removeClass('selected'); + target.addClass('selected'); + } + }); + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/settings/GifExportController.js b/preview/js/controller/settings/GifExportController.js new file mode 100644 index 00000000..42e57d3d --- /dev/null +++ b/preview/js/controller/settings/GifExportController.js @@ -0,0 +1,129 @@ +(function () { + var ns = $.namespace("pskl.controller.settings"); + + ns.GifExportController = function (piskelController) { + this.piskelController = piskelController; + }; + + /** + * List of Resolutions applicable for Gif export + * @static + * @type {Array} array of Objects {dpi:{Number}, default:{Boolean}} + */ + ns.GifExportController.RESOLUTIONS = [ + { + 'dpi' : 1 + },{ + 'dpi' : 5 + },{ + 'dpi' : 10, + 'default' : true + },{ + 'dpi' : 20 + } + ]; + + ns.GifExportController.prototype.init = function () { + this.radioTemplate_ = pskl.utils.Template.get("gif-export-radio-template"); + + this.previewContainerEl = document.querySelectorAll(".gif-export-preview")[0]; + this.radioGroupEl = document.querySelectorAll(".gif-export-radio-group")[0]; + + this.uploadForm = $("[name=gif-export-upload-form]"); + this.uploadForm.submit(this.onUploadFormSubmit_.bind(this)); + + this.createRadioElements_(); + }; + + ns.GifExportController.prototype.onUploadFormSubmit_ = function (evt) { + evt.originalEvent.preventDefault(); + var selectedDpi = this.getSelectedDpi_(), + fps = this.piskelController.getFPS(), + dpi = selectedDpi; + + this.renderAsImageDataAnimatedGIF(dpi, fps, this.onGifRenderingCompleted_.bind(this)); + }; + + ns.GifExportController.prototype.onGifRenderingCompleted_ = function (imageData) { + this.updatePreview_(imageData); + this.previewContainerEl.classList.add("preview-upload-ongoing"); + pskl.app.imageUploadService.upload(imageData, this.onImageUploadCompleted_.bind(this)); + }; + + ns.GifExportController.prototype.onImageUploadCompleted_ = function (imageUrl) { + this.updatePreview_(imageUrl); + this.previewContainerEl.classList.remove("preview-upload-ongoing"); + }; + + ns.GifExportController.prototype.updatePreview_ = function (src) { + this.previewContainerEl.innerHTML = "
    "; + }; + + ns.GifExportController.prototype.getSelectedDpi_ = function () { + var radiosColl = this.uploadForm.get(0).querySelectorAll("[name=gif-dpi]"), + radios = Array.prototype.slice.call(radiosColl,0); + var selectedRadios = radios.filter(function(radio) {return !!radio.checked;}); + + if (selectedRadios.length == 1) { + return selectedRadios[0].value; + } else { + throw "Unexpected error when retrieving selected dpi"; + } + }; + + ns.GifExportController.prototype.createRadioElements_ = function () { + var resolutions = ns.GifExportController.RESOLUTIONS; + for (var i = 0 ; i < resolutions.length ; i++) { + var radio = this.createRadioForResolution_(resolutions[i]); + this.radioGroupEl.appendChild(radio); + } + }; + + ns.GifExportController.prototype.createRadioForResolution_ = function (resolution) { + var dpi = resolution.dpi; + var label = dpi*this.piskelController.getWidth() + "x" + dpi*this.piskelController.getHeight(); + var value = dpi; + + var radioHTML = pskl.utils.Template.replace(this.radioTemplate_, {value : value, label : label}); + var radioEl = pskl.utils.Template.createFromHTML(radioHTML); + + if (resolution['default']) { + var input = radioEl.getElementsByTagName("input")[0]; + input.setAttribute("checked", "checked"); + } + + return radioEl; + }; + + ns.GifExportController.prototype.blobToBase64_ = function(blob, cb) { + var reader = new FileReader(); + reader.onload = function() { + var dataUrl = reader.result; + cb(dataUrl); + }; + reader.readAsDataURL(blob); + }; + + ns.GifExportController.prototype.renderAsImageDataAnimatedGIF = function(dpi, fps, cb) { + var gif = new window.GIF({ + workers: 2, + quality: 10, + width: this.piskelController.getWidth()*dpi, + height: this.piskelController.getHeight()*dpi + }); + + for (var i = 0; i < this.piskelController.getFrameCount(); i++) { + var frame = this.piskelController.getFrameAt(i); + var renderer = new pskl.rendering.CanvasRenderer(frame, dpi); + gif.addFrame(renderer.render(), { + delay: 1000 / fps + }); + } + + gif.on('finished', function(blob) { + this.blobToBase64_(blob, cb); + }.bind(this)); + + gif.render(); + }; +})(); \ No newline at end of file diff --git a/preview/js/controller/settings/ImportController.js b/preview/js/controller/settings/ImportController.js new file mode 100644 index 00000000..f28166f9 --- /dev/null +++ b/preview/js/controller/settings/ImportController.js @@ -0,0 +1,168 @@ +(function () { + var ns = $.namespace('pskl.controller.settings'); + var DEFAULT_FILE_STATUS = 'No file selected ...'; + var PREVIEW_HEIGHT = 60; + + ns.ImportController = function (piskelController) { + this.piskelController = piskelController; + this.importedImage_ = null; + }; + + ns.ImportController.prototype.init = function () { + this.importForm = $("[name=import-form]"); + this.hiddenFileInput = $("[name=file-upload-input]"); + this.fileInputButton = $(".file-input-button"); + this.fileInputStatus = $(".file-input-status"); + this.fileInputStatus.html(DEFAULT_FILE_STATUS); + + this.importPreview = $(".import-section-preview"); + + this.resizeWidth = $("[name=resize-width]"); + this.resizeHeight = $("[name=resize-height]"); + this.smoothResize = $("[name=smooth-resize-checkbox]"); + this.submitButton = $("[name=import-submit]"); + + this.importForm.submit(this.onImportFormSubmit_.bind(this)); + this.hiddenFileInput.change(this.onFileUploadChange_.bind(this)); + this.fileInputButton.click(this.onFileInputClick_.bind(this)); + + this.resizeWidth.keyup(this.onResizeInputKeyUp_.bind(this, 'width')); + this.resizeHeight.keyup(this.onResizeInputKeyUp_.bind(this, 'height')); + }; + + ns.ImportController.prototype.reset_ = function () { + this.importForm.get(0).reset(); + this.fileInputStatus.html(DEFAULT_FILE_STATUS); + $.publish(Events.CLOSE_SETTINGS_DRAWER); + }; + + ns.ImportController.prototype.onResizeInputKeyUp_ = function (from, evt) { + if (this.importedImage_) { + this.synchronizeResizeFields_(evt.target.value, from); + } + }; + + ns.ImportController.prototype.synchronizeResizeFields_ = function (value, from) { + value = parseInt(value, 10); + if (isNaN(value)) { + value = 0; + } + var height = this.importedImage_.height, width = this.importedImage_.width; + if (from === 'width') { + this.resizeHeight.val(Math.round(value * height / width)); + } else { + this.resizeWidth.val(Math.round(value * width / height)); + } + }; + + ns.ImportController.prototype.onImportFormSubmit_ = function (evt) { + evt.originalEvent.preventDefault(); + this.importImageToPiskel_(); + }; + + ns.ImportController.prototype.onFileUploadChange_ = function (evt) { + this.importFromFile_(); + }; + + ns.ImportController.prototype.onFileInputClick_ = function (evt) { + this.hiddenFileInput.click(); + }; + + ns.ImportController.prototype.importFromFile_ = function () { + var files = this.hiddenFileInput.get(0).files; + if (files.length == 1) { + var file = files[0]; + if (this.isImage_(file)) { + this.readImageFile_(file); + this.enableDisabledSections_(); + } else { + this.reset_(); + throw "File is not an image : " + file.type; + } + } + }; + + ns.ImportController.prototype.enableDisabledSections_ = function () { + this.resizeWidth.removeAttr('disabled'); + this.resizeHeight.removeAttr('disabled'); + this.smoothResize.removeAttr('disabled'); + this.submitButton.removeAttr('disabled'); + + this.fileInputButton.removeClass('button-primary'); + this.fileInputButton.blur(); + + $('.import-section-disabled').removeClass('import-section-disabled'); + }; + + ns.ImportController.prototype.readImageFile_ = function (imageFile) { + pskl.utils.FileUtils.readFile(imageFile, this.processImageSource_.bind(this)); + }; + + /** + * Create an image from the given source (url or data-url), and onload forward to onImageLoaded + * TODO : should be a generic utility method, should take a callback + * @param {String} imageSource url or data-url, will be used as src for the image + */ + ns.ImportController.prototype.processImageSource_ = function (imageSource) { + this.importedImage_ = new Image(); + this.importedImage_.onload = this.onImageLoaded_.bind(this); + this.importedImage_.src = imageSource; + }; + + ns.ImportController.prototype.onImageLoaded_ = function (evt) { + var w = this.importedImage_.width, + h = this.importedImage_.height; + var filePath = this.hiddenFileInput.val(); + var fileName = this.extractFileNameFromPath_(filePath); + this.fileInputStatus.html(fileName); + + this.resizeWidth.val(w); + this.resizeHeight.val(h); + + this.importPreview.width("auto"); + this.importPreview.append(this.createImagePreview_()); + }; + + ns.ImportController.prototype.createImagePreview_ = function () { + var image = document.createElement('IMG'); + image.src = this.importedImage_.src; + image.setAttribute('height', PREVIEW_HEIGHT); + return image; + }; + + ns.ImportController.prototype.extractFileNameFromPath_ = function (path) { + var parts = []; + if (path.indexOf('/') !== -1) { + parts = path.split('/'); + } else if (path.indexOf('\\') !== -1) { + parts = path.split('\\'); + } else { + parts = [path]; + } + return parts[parts.length-1]; + }; + + ns.ImportController.prototype.importImageToPiskel_ = function () { + if (this.importedImage_) { + if (window.confirm("You are about to create a new Piskel, unsaved changes will be lost.")) { + var w = this.resizeWidth.val(), + h = this.resizeHeight.val(), + smoothing = !!this.smoothResize.prop('checked'); + + var image = pskl.utils.ImageResizer.resize(this.importedImage_, w, h, smoothing); + var frame = pskl.utils.FrameUtils.createFromImage(image); + + var piskel = pskl.utils.Serializer.createPiskel([frame]); + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); + + this.reset_(); + } + } + }; + + ns.ImportController.prototype.isImage_ = function (file) { + return file.type.indexOf('image') === 0; + }; + +})(); \ No newline at end of file diff --git a/preview/js/controller/settings/SettingsController.js b/preview/js/controller/settings/SettingsController.js new file mode 100644 index 00000000..708b3fa1 --- /dev/null +++ b/preview/js/controller/settings/SettingsController.js @@ -0,0 +1,76 @@ +(function () { + var ns = $.namespace("pskl.controller.settings"); + + var settings = { + 'user' : { + template : 'templates/settings/application.html', + controller : ns.ApplicationSettingsController + }, + 'gif' : { + template : 'templates/settings/export-gif.html', + controller : ns.GifExportController + }, + 'import' : { + template : 'templates/settings/import.html', + controller : ns.ImportController + } + }; + + var SEL_SETTING_CLS = 'has-expanded-drawer'; + var EXP_DRAWER_CLS = 'expanded'; + + ns.SettingsController = function (piskelController) { + this.piskelController = piskelController; + this.drawerContainer = document.getElementById("drawer-container"); + this.settingsContainer = $('[data-pskl-controller=settings]'); + this.expanded = false; + this.currentSetting = null; + }; + + /** + * @public + */ + ns.SettingsController.prototype.init = function() { + // Expand drawer when clicking 'Settings' tab. + $('[data-setting]').click(function(evt) { + var el = evt.originalEvent.currentTarget; + var setting = el.getAttribute("data-setting"); + if (this.currentSetting != setting) { + this.loadSetting(setting); + } else { + this.closeDrawer(); + } + }.bind(this)); + + $('body').click(function (evt) { + var isInSettingsContainer = $.contains(this.settingsContainer.get(0), evt.target); + if (this.expanded && !isInSettingsContainer) { + this.closeDrawer(); + } + }.bind(this)); + + $.subscribe(Events.CLOSE_SETTINGS_DRAWER, this.closeDrawer.bind(this)); + }; + + ns.SettingsController.prototype.loadSetting = function (setting) { + this.drawerContainer.innerHTML = pskl.utils.Template.get(settings[setting].template); + (new settings[setting].controller(this.piskelController)).init(); + + this.settingsContainer.addClass(EXP_DRAWER_CLS); + + $('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS); + $('[data-setting='+setting+']').addClass(SEL_SETTING_CLS); + + this.expanded = true; + this.currentSetting = setting; + }; + + ns.SettingsController.prototype.closeDrawer = function () { + this.settingsContainer.removeClass(EXP_DRAWER_CLS); + $('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS); + + this.expanded = false; + this.currentSetting = null; + }; + +})(); \ No newline at end of file diff --git a/preview/js/drawingtools/BaseTool.js b/preview/js/drawingtools/BaseTool.js new file mode 100644 index 00000000..8b482df2 --- /dev/null +++ b/preview/js/drawingtools/BaseTool.js @@ -0,0 +1,75 @@ +/** + * @provide pskl.drawingtools.BaseTool + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.BaseTool = function() {}; + + ns.BaseTool.prototype.applyToolAt = function(col, row, color, frame, overlay) {}; + + ns.BaseTool.prototype.moveToolAt = function(col, row, color, frame, overlay) {}; + + ns.BaseTool.prototype.moveUnactiveToolAt = function(col, row, color, frame, overlay) { + if (overlay.containsPixel(col, row)) { + if (!isNaN(this.highlightedPixelCol) && + !isNaN(this.highlightedPixelRow) && + (this.highlightedPixelRow != row || + this.highlightedPixelCol != col)) { + + // Clean the previously highlighted pixel: + overlay.clear(); + } + + // Show the current pixel targeted by the tool: + overlay.setPixel(col, row, Constants.TOOL_TARGET_HIGHLIGHT_COLOR); + + this.highlightedPixelCol = col; + this.highlightedPixelRow = row; + } + }; + + ns.BaseTool.prototype.releaseToolAt = function(col, row, color, frame, overlay) {}; + + /** + * Bresenham line algorihtm: Get an array of pixels from + * start and end coordinates. + * + * http://en.wikipedia.org/wiki/Bresenham's_line_algorithm + * http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript + * + * @private + */ + ns.BaseTool.prototype.getLinePixels_ = function(x0, x1, y0, y1) { + + var pixels = []; + var dx = Math.abs(x1-x0); + var dy = Math.abs(y1-y0); + var sx = (x0 < x1) ? 1 : -1; + var sy = (y0 < y1) ? 1 : -1; + var err = dx-dy; + + while(true){ + + // Do what you need to for this + pixels.push({"col": x0, "row": y0}); + + if ((x0==x1) && (y0==y1)) { + break; + } + + var e2 = 2*err; + if (e2>-dy){ + err -= dy; + x0 += sx; + } + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + return pixels; + }; +})(); diff --git a/preview/js/drawingtools/Circle.js b/preview/js/drawingtools/Circle.js new file mode 100644 index 00000000..c5c0322e --- /dev/null +++ b/preview/js/drawingtools/Circle.js @@ -0,0 +1,85 @@ +/** + * @provide pskl.drawingtools.Circle + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.Circle = function() { + this.toolId = "tool-circle"; + this.helpText = "Circle tool"; + + // Circle's first point coordinates (set in applyToolAt) + this.startCol = null; + this.startRow = null; + }; + + pskl.utils.inherit(ns.Circle, ns.BaseTool); + + /** + * @override + */ + ns.Circle.prototype.applyToolAt = function(col, row, color, frame, overlay) { + this.startCol = col; + this.startRow = row; + + // Drawing the first point of the rectangle in the fake overlay canvas: + overlay.setPixel(col, row, color); + }; + + ns.Circle.prototype.moveToolAt = function(col, row, color, frame, overlay) { + overlay.clear(); + if(color == Constants.TRANSPARENT_COLOR) { + color = Constants.SELECTION_TRANSPARENT_COLOR; + } + + // draw in overlay + this.drawCircle_(col, row, color, overlay); + }; + + /** + * @override + */ + ns.Circle.prototype.releaseToolAt = function(col, row, color, frame, overlay) { + overlay.clear(); + if(frame.containsPixel(col, row)) { // cancel if outside of canvas + // draw in frame to finalize + this.drawCircle_(col, row, color, frame); + } + }; + + ns.Circle.prototype.drawCircle_ = function (col, row, color, targetFrame) { + var circlePoints = this.getCirclePixels_(this.startCol, this.startRow, col, row); + for(var i = 0; i< circlePoints.length; i++) { + // Change model: + targetFrame.setPixel(circlePoints[i].col, circlePoints[i].row, color); + } + }; + + ns.Circle.prototype.getCirclePixels_ = function (x0, y0, x1, y1) { + var coords = pskl.PixelUtils.getOrderedRectangleCoordinates(x0, y0, x1, y1); + var xC = (coords.x0 + coords.x1)/2; + var yC = (coords.y0 + coords.y1)/2; + + var rX = coords.x1 - xC; + var rY = coords.y1 - yC; + + var pixels = []; + var x, y, angle; + for (x = coords.x0 ; x < coords.x1 ; x++) { + angle = Math.acos((x - xC)/rX); + y = Math.round(rY * Math.sin(angle) + yC); + pixels.push({"col": x, "row": y}); + pixels.push({"col": 2*xC - x, "row": 2*yC - y}); + } + + for (y = coords.y0 ; y < coords.y1 ; y++) { + angle = Math.asin((y - yC)/rY); + x = Math.round(rX * Math.cos(angle) + xC); + pixels.push({"col": x, "row": y}); + pixels.push({"col": 2*xC - x, "row": 2*yC - y}); + } + return pixels; + }; +})(); diff --git a/preview/js/drawingtools/ColorPicker.js b/preview/js/drawingtools/ColorPicker.js new file mode 100644 index 00000000..8bbedf0d --- /dev/null +++ b/preview/js/drawingtools/ColorPicker.js @@ -0,0 +1,29 @@ +/** + * @provide pskl.drawingtools.ColorPicker + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.ColorPicker = function() { + this.toolId = "tool-colorpicker"; + this.helpText = "Color picker"; + }; + + pskl.utils.inherit(ns.ColorPicker, ns.BaseTool); + + /** + * @override + */ + ns.ColorPicker.prototype.applyToolAt = function(col, row, color, frame, overlay, context) { + if (frame.containsPixel(col, row)) { + var sampledColor = frame.getPixel(col, row); + if (context.button == Constants.LEFT_BUTTON) { + $.publish(Events.PRIMARY_COLOR_SELECTED, [sampledColor]); + } else if (context.button == Constants.RIGHT_BUTTON) { + $.publish(Events.SECONDARY_COLOR_SELECTED, [sampledColor]); + } + } + }; +})(); diff --git a/preview/js/drawingtools/Eraser.js b/preview/js/drawingtools/Eraser.js new file mode 100644 index 00000000..c66a4220 --- /dev/null +++ b/preview/js/drawingtools/Eraser.js @@ -0,0 +1,23 @@ +/** + * @provide pskl.drawingtools.Eraser + * + * @require Constants + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.Eraser = function() { + this.toolId = "tool-eraser"; + this.helpText = "Eraser tool"; + }; + + pskl.utils.inherit(ns.Eraser, ns.SimplePen); + + /** + * @override + */ + ns.Eraser.prototype.applyToolAt = function(col, row, color, frame, overlay) { + this.superclass.applyToolAt.call(this, col, row, Constants.TRANSPARENT_COLOR, frame, overlay); + }; +})(); \ No newline at end of file diff --git a/preview/js/drawingtools/Move.js b/preview/js/drawingtools/Move.js new file mode 100644 index 00000000..1155db21 --- /dev/null +++ b/preview/js/drawingtools/Move.js @@ -0,0 +1,54 @@ +/** + * @provide pskl.drawingtools.Move + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.Move = function() { + this.toolId = "tool-move"; + this.helpText = "Move tool"; + + // Stroke's first point coordinates (set in applyToolAt) + this.startCol = null; + this.startRow = null; + }; + + pskl.utils.inherit(ns.Move, ns.BaseTool); + + /** + * @override + */ + ns.Move.prototype.applyToolAt = function(col, row, color, frame, overlay) { + this.startCol = col; + this.startRow = row; + this.frameClone = frame.clone(); + }; + + ns.Move.prototype.moveToolAt = function(col, row, color, frame, overlay) { + var colDiff = col - this.startCol, rowDiff = row - this.startRow; + this.shiftFrame(colDiff, rowDiff, frame, this.frameClone); + }; + + ns.Move.prototype.shiftFrame = function (colDiff, rowDiff, frame, reference) { + var color; + for (var col = 0 ; col < frame.getWidth() ; col++) { + for (var row = 0 ; row < frame.getHeight() ; row++) { + if (reference.containsPixel(col - colDiff, row - rowDiff)) { + color = reference.getPixel(col - colDiff, row - rowDiff); + } else { + color = Constants.TRANSPARENT_COLOR; + } + frame.setPixel(col, row, color); + } + } + }; + + /** + * @override + */ + ns.Move.prototype.releaseToolAt = function(col, row, color, frame, overlay) { + this.moveToolAt(col, row, color, frame, overlay); + }; +})(); diff --git a/preview/js/drawingtools/PaintBucket.js b/preview/js/drawingtools/PaintBucket.js new file mode 100644 index 00000000..cb0ffa66 --- /dev/null +++ b/preview/js/drawingtools/PaintBucket.js @@ -0,0 +1,36 @@ +/** + * @provide pskl.drawingtools.PaintBucket + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.PaintBucket = function() { + this.toolId = "tool-paint-bucket"; + this.helpText = "Paint bucket tool"; + }; + + pskl.utils.inherit(ns.PaintBucket, ns.BaseTool); + + /** + * @override + */ + ns.PaintBucket.prototype.applyToolAt = function(col, row, color, frame, overlay) { + + pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, col, row, color); + }; +})(); + + + + + + + + + + + + + diff --git a/preview/js/drawingtools/Rectangle.js b/preview/js/drawingtools/Rectangle.js new file mode 100644 index 00000000..dd2a57e5 --- /dev/null +++ b/preview/js/drawingtools/Rectangle.js @@ -0,0 +1,59 @@ +/** + * @provide pskl.drawingtools.Rectangle + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.Rectangle = function() { + this.toolId = "tool-rectangle"; + this.helpText = "Rectangle tool"; + + // Rectangle's first point coordinates (set in applyToolAt) + this.startCol = null; + this.startRow = null; + }; + + pskl.utils.inherit(ns.Rectangle, ns.BaseTool); + + /** + * @override + */ + ns.Rectangle.prototype.applyToolAt = function(col, row, color, frame, overlay) { + this.startCol = col; + this.startRow = row; + + // Drawing the first point of the rectangle in the fake overlay canvas: + overlay.setPixel(col, row, color); + }; + + ns.Rectangle.prototype.moveToolAt = function(col, row, color, frame, overlay) { + overlay.clear(); + if(color == Constants.TRANSPARENT_COLOR) { + color = Constants.SELECTION_TRANSPARENT_COLOR; + } + + // draw in overlay + this.drawRectangle_(col, row, color, overlay); + }; + + /** + * @override + */ + ns.Rectangle.prototype.releaseToolAt = function(col, row, color, frame, overlay) { + overlay.clear(); + if(frame.containsPixel(col, row)) { // cancel if outside of canvas + // draw in frame to finalize + this.drawRectangle_(col, row, color, frame); + } + }; + + ns.Rectangle.prototype.drawRectangle_ = function (col, row, color, targetFrame) { + var strokePoints = pskl.PixelUtils.getBoundRectanglePixels(this.startCol, this.startRow, col, row); + for(var i = 0; i< strokePoints.length; i++) { + // Change model: + targetFrame.setPixel(strokePoints[i].col, strokePoints[i].row, color); + } + }; +})(); diff --git a/preview/js/drawingtools/SimplePen.js b/preview/js/drawingtools/SimplePen.js new file mode 100644 index 00000000..a5dbbb0e --- /dev/null +++ b/preview/js/drawingtools/SimplePen.js @@ -0,0 +1,49 @@ +/** + * @provide pskl.drawingtools.SimplePen + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.SimplePen = function() { + this.toolId = "tool-pen"; + this.helpText = "Pen tool"; + + this.previousCol = null; + this.previousRow = null; + + }; + + pskl.utils.inherit(ns.SimplePen, ns.BaseTool); + + /** + * @override + */ + ns.SimplePen.prototype.applyToolAt = function(col, row, color, frame, overlay) { + if (frame.containsPixel(col, row)) { + frame.setPixel(col, row, color); + } + this.previousCol = col; + this.previousRow = row; + }; + + ns.SimplePen.prototype.moveToolAt = function(col, row, color, frame, overlay) { + if((Math.abs(col - this.previousCol) > 1) || (Math.abs(row - this.previousRow) > 1)) { + // The pen movement is too fast for the mousemove frequency, there is a gap between the + // current point and the previously drawn one. + // We fill the gap by calculating missing dots (simple linear interpolation) and draw them. + var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow); + for(var i=0, l=interpolatedPixels.length; i
    ' + , trigger: 'hover' + , title: '' + , delay: 0 + , html: true + } + +}(window.jQuery); diff --git a/preview/js/lib/bootstrap/readme.txt b/preview/js/lib/bootstrap/readme.txt new file mode 100644 index 00000000..2a24c100 --- /dev/null +++ b/preview/js/lib/bootstrap/readme.txt @@ -0,0 +1 @@ +Bootstrap custom build containing only the tooltip component \ No newline at end of file diff --git a/preview/js/lib/gif/gif.js b/preview/js/lib/gif/gif.js new file mode 100644 index 00000000..ffdc3b54 --- /dev/null +++ b/preview/js/lib/gif/gif.js @@ -0,0 +1,3 @@ +(function(c){function a(b,d){if({}.hasOwnProperty.call(a.cache,b))return a.cache[b];var e=a.resolve(b);if(!e)throw new Error('Failed to resolve module '+b);var c={id:b,require:a,filename:b,exports:{},loaded:!1,parent:d,children:[]};d&&d.children.push(c);var f=b.slice(0,b.lastIndexOf('/')+1);return a.cache[b]=c.exports,e.call(c.exports,c,c.exports,f,b),c.loaded=!0,a.cache[b]=c.exports}a.modules={},a.cache={},a.resolve=function(b){return{}.hasOwnProperty.call(a.modules,b)?a.modules[b]:void 0},a.define=function(b,c){a.modules[b]=c};var b=function(a){return a='/',{title:'browser',version:'v0.10.5',browser:!0,env:{},argv:[],nextTick:c.setImmediate||function(a){setTimeout(a,0)},cwd:function(){return a},chdir:function(b){a=b}}}();a.define('/gif.coffee',function(b,k,j,i){function c(a,b){return{}.hasOwnProperty.call(a,b)}function g(d,b){for(var a=0,c=b.length;athis.frames.length;0<=this.frames.length?++a:--a)b.push(a);return b}.apply(this,arguments).length;athis.frames.length;0<=this.frames.length?++a:--a)b.push(a);return b}.apply(this,arguments)[a],b.push(null);return b}.call(this,[]),a=this.spawnWorkers();for(var b=0,c=function(){var c,c;c=[];for(var b=0;0<=a?ba;0<=a?++b:--b)c.push(b);return c}.apply(this,arguments).length;ba;0<=a?++b:--b)c.push(b);return c}.apply(this,arguments)[b],this.renderNextFrame();return this.emit('start'),this.emit('progress',0)},a.prototype.abort=function(){var a;while(!0){if(a=this.activeWorkers.shift(),!(null!=a))break;console.log('killing active worker'),a.terminate()}return this.running=!1,this.emit('abort')},a.prototype.spawnWorkers=function(){var a,b;return a=Math.min(this.options.workers,this.frames.length),function(){var c;c=[];for(var b=this.freeWorkers.length;this.freeWorkers.length<=a?ba;this.freeWorkers.length<=a?++b:--b)c.push(b);return c}.apply(this,arguments).forEach((b=this,function(d){var a,c;return console.log('spawning worker '+d),c=new Worker(b.options.workerScript),a=b,c.onmessage=function(b){return a.activeWorkers.splice(a.activeWorkers.indexOf(c),1),a.freeWorkers.push(c),a.frameFinished(b.data)},b.freeWorkers.push(c)})),a},a.prototype.frameFinished=function(a){return console.log('frame '+a.index+' finished - '+this.activeWorkers.length+' active'),this.finishedFrames++,this.emit('progress',this.finishedFrames/this.frames.length),this.imageParts[a.index]=a,g(null,this.imageParts)?this.renderNextFrame():this.finishRendering()},a.prototype.finishRendering=function(){var e,a,k,m,b,d,h;b=0;for(var f=0,j=this.imageParts.length;f=this.frames.length?void 0:(c=this.frames[this.nextFrame++],b=this.freeWorkers.shift(),a=this.getTask(c),console.log('starting frame '+(a.index+1)+' of '+this.frames.length),this.activeWorkers.push(b),b.postMessage(a))},a.prototype.getContextData=function(a){return a.getImageData(0,0,this.options.width,this.options.height).data},a.prototype.getImageData=function(b){var a;return null!=this._canvas||(this._canvas=document.createElement('canvas'),this._canvas.width=this.options.width,this._canvas.height=this.options.height),a=this._canvas.getContext('2d'),a.setFill=this.options.background,a.fillRect(0,0,this.options.width,this.options.height),a.drawImage(b,0,0),this.getContextData(a)},a.prototype.getTask=function(a){var c,b;if(c=this.frames.indexOf(a),b={index:c,last:c===this.frames.length-1,delay:a.delay,width:this.options.width,height:this.options.height,quality:this.options.quality,repeat:this.options.repeat,canTransfer:e.name==='chrome'},null!=a.data)b.data=a.data;else if(null!=a.context)b.data=this.getContextData(a.context);else if(null!=a.image)b.data=this.getImageData(a.image);else throw new Error('Invalid frame');return b},a}(d),b.exports=f}),a.define('/browser.coffee',function(f,g,h,i){var a,d,e,c,b;c=navigator.userAgent.toLowerCase(),e=navigator.platform.toLowerCase(),b=c.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,'unknown',0],d=b[1]==='ie'&&document.documentMode,a={name:b[1]==='version'?b[3]:b[1],version:d||parseFloat(b[1]==='opera'&&b[4]?b[4]:b[2]),platform:{name:c.match(/ip(?:ad|od|hone)/)?'ios':(c.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||['other'])[0]}},a[a.name]=!0,a[a.name+parseInt(a.version,10)]=!0,a.platform[a.platform.name]=!0,f.exports=a}),a.define('events',function(f,e,g,h){b.EventEmitter||(b.EventEmitter=function(){});var a=e.EventEmitter=b.EventEmitter,c=typeof Array.isArray==='function'?Array.isArray:function(a){return Object.prototype.toString.call(a)==='[object Array]'},d=10;a.prototype.setMaxListeners=function(a){this._events||(this._events={}),this._events.maxListeners=a},a.prototype.emit=function(f){if(f==='error'&&(!(this._events&&this._events.error)||c(this._events.error)&&!this._events.error.length))throw arguments[1]instanceof Error?arguments[1]:new Error("Uncaught, unspecified 'error' event.");if(!this._events)return!1;var a=this._events[f];if(!a)return!1;if(!(typeof a=='function'))if(c(a)){var b=Array.prototype.slice.call(arguments,1),e=a.slice();for(var d=0,g=e.length;d0&&this._events[a].length>e&&(this._events[a].warned=!0,console.error('(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.',this._events[a].length),console.trace())}this._events[a].push(b)}else this._events[a]=[this._events[a],b];return this},a.prototype.on=a.prototype.addListener,a.prototype.once=function(b,c){var a=this;return a.on(b,function d(){a.removeListener(b,d),c.apply(this,arguments)}),this},a.prototype.removeListener=function(a,d){if('function'!==typeof d)throw new Error('removeListener only takes instances of Function');if(!(this._events&&this._events[a]))return this;var b=this._events[a];if(c(b)){var e=b.indexOf(d);if(e<0)return this;b.splice(e,1),b.length==0&&delete this._events[a]}else this._events[a]===d&&delete this._events[a];return this},a.prototype.removeAllListeners=function(a){return a&&this._events&&this._events[a]&&(this._events[a]=null),this},a.prototype.listeners=function(a){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),c(this._events[a])||(this._events[a]=[this._events[a]]),this._events[a]}}),c.GIF=a('/gif.coffee')}.call(this,this)) +//# sourceMappingURL=gif.js.map +// gif.js 0.1.4 - https://github.com/jnordberg/gif.js \ No newline at end of file diff --git a/preview/js/lib/gif/gif.worker.js b/preview/js/lib/gif/gif.worker.js new file mode 100644 index 00000000..d2be02ac --- /dev/null +++ b/preview/js/lib/gif/gif.worker.js @@ -0,0 +1,12 @@ +(function () { + var worker = function () { + (function(b){function a(b,d){if({}.hasOwnProperty.call(a.cache,b))return a.cache[b];var e=a.resolve(b);if(!e)throw new Error('Failed to resolve module '+b);var c={id:b,require:a,filename:b,exports:{},loaded:!1,parent:d,children:[]};d&&d.children.push(c);var f=b.slice(0,b.lastIndexOf('/')+1);return a.cache[b]=c.exports,e.call(c.exports,c,c.exports,f,b),c.loaded=!0,a.cache[b]=c.exports}a.modules={},a.cache={},a.resolve=function(b){return{}.hasOwnProperty.call(a.modules,b)?a.modules[b]:void 0},a.define=function(b,c){a.modules[b]=c},a.define('/gif.worker.coffee',function(d,e,f,g){var b,c;b=a('/GIFEncoder.js',d),c=function(a){var c,d,e;return c=new b(a.width,a.height),a.index===0?c.writeHeader():c.firstFrame=!1,c.setRepeat(a.repeat),c.setDelay(a.delay),c.setQuality(a.quality),c.addFrame(a.data),a.last&&c.finish(),d=c.stream(),a.data=d.pages,a.cursor=d.cursor,a.pageSize=d.constructor.pageSize,a.canTransfer?(e=function(c){var d;for(var b=0,e=a.data.length;b=c.pageSize&&this.newPage(),this.pages[this.page][this.cursor++]=a},c.prototype.writeUTFBytes=function(b){for(var c=b.length,a=0;a=0&&(this.dispose=a)},b.prototype.setRepeat=function(a){this.repeat=a},b.prototype.setTransparent=function(a){this.transparent=a},b.prototype.addFrame=function(a){this.image=a,this.getImagePixels(),this.analyzePixels(),this.firstFrame&&(this.writeLSD(),this.writePalette(),this.repeat>=0&&this.writeNetscapeExt()),this.writeGraphicCtrlExt(),this.writeImageDesc(),this.firstFrame||this.writePalette(),this.writePixels(),this.firstFrame=!1},b.prototype.finish=function(){this.out.writeByte(59)},b.prototype.setQuality=function(a){a<1&&(a=1),this.sample=a},b.prototype.writeHeader=function(){this.out.writeUTFBytes('GIF89a')},b.prototype.analyzePixels=function(){var g=this.pixels.length,d=g/3;this.indexedPixels=new Uint8Array(d);var a=new f(this.pixels,this.sample);a.buildColormap(),this.colorTab=a.getColormap();var b=0;for(var c=0;c>16,l=(e&65280)>>8,m=e&255,c=0,d=16777216,j=this.colorTab.length;for(var a=0;a=0&&(a=dispose&7),a<<=2,this.out.writeByte(0|a|0|b),this.writeShort(this.delay),this.out.writeByte(this.transIndex),this.out.writeByte(0)},b.prototype.writeImageDesc=function(){this.out.writeByte(44),this.writeShort(0),this.writeShort(0),this.writeShort(this.width),this.writeShort(this.height),this.firstFrame?this.out.writeByte(0):this.out.writeByte(128|this.palSize)},b.prototype.writeLSD=function(){this.writeShort(this.width),this.writeShort(this.height),this.out.writeByte(240|this.palSize),this.out.writeByte(0),this.out.writeByte(0)},b.prototype.writeNetscapeExt=function(){this.out.writeByte(33),this.out.writeByte(255),this.out.writeByte(11),this.out.writeUTFBytes('NETSCAPE2.0'),this.out.writeByte(3),this.out.writeByte(1),this.writeShort(this.repeat),this.out.writeByte(0)},b.prototype.writePalette=function(){this.out.writeBytes(this.colorTab);var b=768-this.colorTab.length;for(var a=0;a>8&255)},b.prototype.writePixels=function(){var a=new g(this.width,this.height,this.indexedPixels,this.colorDepth);a.encode(this.out)},b.prototype.stream=function(){return this.out},e.exports=b}),a.define('/LZWEncoder.js',function(e,g,h,i){function f(y,D,C,B){function w(a,b){r[f++]=a,f>=254&&t(b)}function x(b){u(a),k=i+2,j=!0,l(i,b)}function u(b){for(var a=0;a=0){y=w-d,d===0&&(y=1);do if((d-=y)<0&&(d+=w),h[d]===g){e=n[d];continue a}while(h[d]>=0)}l(e,r),e=t,k<1<0&&(a.writeByte(f),a.writeBytes(r,0,f),f=0)}function p(a){return(1<0?g|=a<=8)w(g&255,c),g>>=8,e-=8;if((k>m||j)&&(j?(m=p(n_bits=q),j=!1):(++n_bits,n_bits==b?m=1<0)w(g&255,c),g>>=8,e-=8;t(c)}}var s=Math.max(2,B),r=new Uint8Array(256),h=new Int32Array(a),n=new Int32Array(a),g,e=0,f,k=0,m,j=!1,q,i,o;this.encode=z}var c=-1,b=12,a=5003,d=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535];e.exports=f}),a.define('/TypedNeuQuant.js',function(A,F,E,D){function C(A,B){function I(){o=[],q=new Int32Array(256),t=new Int32Array(a),y=new Int32Array(a),z=new Int32Array(a>>3);var c,d;for(c=0;c>=b,o[c][1]>>=b,o[c][2]>>=b,o[c][3]=c}function K(b,a,c,e,f){o[a][0]-=b*(o[a][0]-c)/d,o[a][1]-=b*(o[a][1]-e)/d,o[a][2]-=b*(o[a][2]-f)/d}function L(j,e,n,l,k){var h=Math.abs(e-j),i=Math.min(e+j,a),g=e+1,f=e-1,m=1,b,d;while(gh)d=z[m++],gh&&(b=o[f--],b[0]-=d*(b[0]-n)/c,b[1]-=d*(b[1]-l)/c,b[2]-=d*(b[2]-k)/c)}function C(p,s,q){var h=2147483647,k=h,d=-1,m=d,c,j,e,n,l;for(c=0;c>i-b),n>g,y[c]-=l,t[c]+=l<>1,b=f+1;b>1,b=f+1;b<256;b++)q[b]=n}function E(j,i,k){var b,d,c,e=1e3,h=-1,f=q[i],g=f-1;while(f=0)f=e?f=a:(f++,c<0&&(c=-c),b=d[0]-j,b<0&&(b=-b),c+=b,c=0&&(d=o[g],c=i-d[1],c>=e?g=-1:(g--,c<0&&(c=-c),b=d[0]-j,b<0&&(b=-b),c+=b,c>h;for(a<=1&&(a=0),c=0;c=f&&(g-=f),c++,q===0&&(q=1),c%q===0)for(n-=n/D,o-=o/v,a=o>>h,a<=1&&(a=0),e=0;e>g,r=e<>3,h=6,t=1<{iframe-content} + * The content can later be accessed by getting the script (through getElementById for instance) and reading innerHTML + * @param {HTMLElement} iframe + */ + var storeFrame = function (iframe) { + var script=document.createElement("script"); + script.setAttribute("type", "text/html"); + script.setAttribute("id", iframe.getAttribute("src")); + script.innerHTML = iframe.contentWindow.document.body.innerHTML; + iframe.parentNode.removeChild(iframe); + document.body.appendChild(script); + }; + + window.iframeloader = { + onLoad : function (event) { + var iframe = event.target || event.srcElement; + processFrame(iframe); + } + } +})(); \ No newline at end of file diff --git a/preview/js/lib/jquery-1.8.0.js b/preview/js/lib/jquery-1.8.0.js new file mode 100644 index 00000000..43991b38 --- /dev/null +++ b/preview/js/lib/jquery-1.8.0.js @@ -0,0 +1,9227 @@ +/*! + * jQuery JavaScript Library v1.8.0 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: Thu Aug 09 2012 16:24:48 GMT-0400 (Eastern Daylight Time) + */ +(function( window, undefined ) { +var + // A central reference to the root jQuery(document) + rootjQuery, + + // The deferred used on DOM ready + readyList, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + location = window.location, + navigator = window.navigator, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Save a reference to some core methods + core_push = Array.prototype.push, + core_slice = Array.prototype.slice, + core_indexOf = Array.prototype.indexOf, + core_toString = Object.prototype.toString, + core_hasOwn = Object.prototype.hasOwnProperty, + core_trim = String.prototype.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source, + + // Used for detecting and trimming whitespace + core_rnotwhite = /\S/, + core_rspace = /\s+/, + + // IE doesn't match non-breaking spaces with \s + rtrim = core_rnotwhite.test("\xA0") ? (/^[\s\xA0]+|[\s\xA0]+$/g) : /^\s+|\s+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // The ready event handler and self cleanup method + DOMContentLoaded = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + } else if ( document.readyState === "complete" ) { + // we're here because readyState === "complete" in oldIE + // which is good enough for us to call the dom ready! + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context && context.nodeType ? context.ownerDocument || context : document ); + + // scripts is true for back-compat + selector = jQuery.parseHTML( match[1], doc, true ); + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + this.attr.call( selector, context, true ); + } + + return jQuery.merge( this, selector ); + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.8.0", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ), + "slice", core_slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ core_toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // scripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, scripts ) { + var parsed; + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + scripts = context; + context = 0; + } + context = context || document; + + // Single tag + if ( (parsed = rsingleTag.exec( data )) ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] ); + return jQuery.merge( [], + (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes ); + }, + + parseJSON: function( data ) { + if ( !data || typeof data !== "string") { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && core_rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var name, + i = 0, + length = obj.length, + isObj = length === undefined || jQuery.isFunction( obj ); + + if ( args ) { + if ( isObj ) { + for ( name in obj ) { + if ( callback.apply( obj[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( obj[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in obj ) { + if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var type, + ret = results || []; + + if ( arr != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + type = jQuery.type( arr ); + + if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) { + core_push.call( ret, arr ); + } else { + jQuery.merge( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, + ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + } + chainable = 1; + + // Sets one value + } else if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } + + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" || ( document.readyState !== "loading" && document.addEventListener ) ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready, 1 ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.split( core_rspace ), function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( jQuery.isFunction( arg ) && ( !options.unique || !self.has( arg ) ) ) { + list.push( arg ); + } else if ( arg && arg.length ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + return jQuery.inArray( fn, list ) > -1; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ]( jQuery.isFunction( fn ) ? + function() { + var returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + } : + newDefer[ action ] + ); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] = list.fire + deferred[ tuple[0] ] = list.fire; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + fragment, + eventName, + i, + isSupported, + clickFn, + div = document.createElement("div"); + + // Preliminary tests + div.setAttribute( "className", "t" ); + div.innerHTML = "
    a"; + + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + a.style.cssText = "top:1px;float:left;opacity:.5"; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: ( document.compatMode === "CSS1Compat" ), + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", clickFn = function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent("onclick"); + div.detachEvent( "onclick", clickFn ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + input.setAttribute( "checked", "checked" ); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: true, + change: true, + focusin: true + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + // Run tests that need a body at doc ready + jQuery(function() { + var container, div, tds, marginDiv, + divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
    t
    "; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // NOTE: To any future maintainer, window.getComputedStyle was used here + // instead of getComputedStyle because it gave a better gzip size. + // The difference between window.getComputedStyle and getComputedStyle is + // 7 bytes + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = document.createElement("div"); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
    "; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + container.style.zoom = 1; + } + + // Null elements to avoid leaks in IE + body.removeChild( container ); + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + fragment.removeChild( div ); + all = a = select = opt = input = fragment = div = null; + + return support; +})(); +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + deletedIds: [], + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + if ( !queue.length && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery.removeData( elem, type + "queue", true ); + jQuery.removeData( elem, key, true ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, fixSpecified, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea|)$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( core_rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var removes, className, elem, c, cl, i, l; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + if ( (value && typeof value === "string") || value === undefined ) { + removes = ( value || "" ).split( core_rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + if ( elem.nodeType === 1 && elem.className ) { + + className = (" " + elem.className + " ").replace( rclass, " " ); + + // loop over each item in the removal list + for ( c = 0, cl = removes.length; c < cl; c++ ) { + // Remove until there is nothing to remove, + while ( className.indexOf(" " + removes[ c ] + " ") > -1 ) { + className = className.replace( " " + removes[ c ] + " " , " " ); + } + } + elem.className = value ? jQuery.trim( className ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( core_rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9 + attrFn: {}, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + + attrNames = value.split( core_rspace ); + + for ( ; i < attrNames.length; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.value = value + "" ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/, + rhoverHack = /(?:^|\s)hover(\.\S+|)\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var t, tns, type, origType, namespaces, origCount, + j, events, special, eventType, handleObj, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, "events", true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType, + type = event.type || event, + namespaces = []; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + for ( old = elem; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old === (elem.ownerDocument || document) ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related, + handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = []; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx) + if ( cur.disabled !== true || event.type !== "click" ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = jqcur.is( sel ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8) + event.metaKey = !!event.metaKey; + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 – + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === "undefined" ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "_submit_attached" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "_submit_attached", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "_change_attached", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var cachedruns, + dirruns, + sortOrder, + siblingCheck, + assertGetIdNotName, + + document = window.document, + docElem = document.documentElement, + + strundefined = "undefined", + hasDuplicate = false, + baseHasDuplicate = true, + done = 0, + slice = [].slice, + push = [].push, + + expando = ( "sizcache" + Math.random() ).replace( ".", "" ), + + // Regex + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors) + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)", + pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)", + combinators = whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*", + groups = "(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|" + attributes + "|" + pseudos.replace( 2, 7 ) + "|[^\\\\(),])+", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcombinators = new RegExp( "^" + combinators ), + + // All simple (non-comma) selectors, excluding insignifant trailing whitespace + rgroups = new RegExp( groups + "?(?=" + whitespace + "*,|$)", "g" ), + + // A selector, or everything after leading whitespace + // Optionally followed in either case by a ")" for terminating sub-selectors + rselector = new RegExp( "^(?:(?!,)(?:(?:^|,)" + whitespace + "*" + groups + ")*?|" + whitespace + "*(.*?))(\\)|$)" ), + + // All combinators and selector components (attribute test, tag, pseudo, etc.), the latter appearing together when consecutive + rtokens = new RegExp( groups.slice( 19, -6 ) + "\\x20\\t\\r\\n\\f>+~])+|" + combinators, "g" ), + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + rendsWithNot = /:not\($/, + + rheader = /h\d/i, + rinputs = /input|select|textarea|button/i, + + rbackslash = /\\(?!\\)/g, + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "[-", "[-\\*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|nth|last|first)-child(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "POS": new RegExp( pos, "ig" ), + // For use in libraries implementing .is() + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" ) + }, + + classCache = {}, + cachedClasses = [], + compilerCache = {}, + cachedSelectors = [], + + // Mark a function for use in filtering + markFunction = function( fn ) { + fn.sizzleFilter = true; + return fn; + }, + + // Returns a function to use in pseudos for input types + createInputFunction = function( type ) { + return function( elem ) { + // Check the input's nodeName and type + return elem.nodeName.toLowerCase() === "input" && elem.type === type; + }; + }, + + // Returns a function to use in pseudos for buttons + createButtonFunction = function( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; + }, + + // Used for testing something on an element + assert = function( fn ) { + var pass = false, + div = document.createElement("div"); + try { + pass = fn( div ); + } catch (e) {} + // release memory in IE + div = null; + return pass; + }, + + // Check if attributes should be retrieved by attribute nodes + assertAttributes = assert(function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }), + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + assertUsableName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "
    "; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = document.getElementsByName && + // buggy browsers will return fewer than the correct 2 + document.getElementsByName( expando ).length === + // buggy browsers will return more than the correct 0 + 2 + document.getElementsByName( expando + 0 ).length; + assertGetIdNotName = !document.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }), + + // Check if the browser returns only elements + // when doing getElementsByTagName("*") + assertTagNameNoComments = assert(function( div ) { + div.appendChild( document.createComment("") ); + return div.getElementsByTagName("*").length === 0; + }), + + // Check if getAttribute returns normalized href attributes + assertHrefNotNormalized = assert(function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }), + + // Check if getElementsByClassName can be trusted + assertUsableClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return false; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length !== 1; + }); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + var match, elem, xml, m, + nodeType = context.nodeType; + + if ( nodeType !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + xml = isXML( context ); + + if ( !xml && !seed ) { + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + } + + // All others + return select( selector, context, results, seed, xml ); +}; + +var Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + match: matchExpr, + + order: [ "ID", "TAG" ], + + attrHandle: {}, + + createPseudo: markFunction, + + find: { + "ID": assertGetIdNotName ? + function( id, context, xml ) { + if ( typeof context.getElementById !== strundefined && !xml ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + } : + function( id, context, xml ) { + if ( typeof context.getElementById !== strundefined && !xml ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }, + + "TAG": assertTagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + var elem, + tmp = [], + i = 0; + + for ( ; (elem = results[i]); i++ ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + } + }, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( rbackslash, "" ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr.CHILD + 1 type (only|nth|...) + 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 3 xn-component of xn+y argument ([+-]?\d*n|) + 4 sign of xn-component + 5 x of xn-component + 6 sign of y-component + 7 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1] === "nth" ) { + // nth-child requires argument + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) ); + match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" ); + + // other types prohibit arguments + } else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var argument, + unquoted = match[4]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Relinquish our claim on characters in `unquoted` from a closing parenthesis on + if ( unquoted && (argument = rselector.exec( unquoted )) && argument.pop() ) { + + match[0] = match[0].slice( 0, argument[0].length - unquoted.length - 1 ); + unquoted = argument[0].slice( 0, -1 ); + } + + // Quoted or unquoted, we have the full argument + // Return only captures needed by the pseudo filter method (type and argument) + match.splice( 2, 3, unquoted || match[3] ); + return match; + } + }, + + filter: { + "ID": assertGetIdNotName ? + function( id ) { + id = id.replace( rbackslash, "" ); + return function( elem ) { + return elem.getAttribute("id") === id; + }; + } : + function( id ) { + id = id.replace( rbackslash, "" ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === id; + }; + }, + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + nodeName = nodeName.replace( rbackslash, "" ).toLowerCase(); + + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className ]; + if ( !pattern ) { + pattern = classCache[ className ] = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" ); + cachedClasses.push( className ); + // Avoid too large of a cache + if ( cachedClasses.length > Expr.cacheLength ) { + delete classCache[ cachedClasses.shift() ]; + } + } + return function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }; + }, + + "ATTR": function( name, operator, check ) { + if ( !operator ) { + return function( elem ) { + return Sizzle.attr( elem, name ) != null; + }; + } + + return function( elem ) { + var result = Sizzle.attr( elem, name ), + value = result + ""; + + if ( result == null ) { + return operator === "!="; + } + + switch ( operator ) { + case "=": + return value === check; + case "!=": + return value !== check; + case "^=": + return check && value.indexOf( check ) === 0; + case "*=": + return check && value.indexOf( check ) > -1; + case "$=": + return check && value.substr( value.length - check.length ) === check; + case "~=": + return ( " " + value + " " ).indexOf( check ) > -1; + case "|=": + return value === check || value.substr( 0, check.length + 1 ) === check + "-"; + } + }; + }, + + "CHILD": function( type, argument, first, last ) { + + if ( type === "nth" ) { + var doneName = done++; + + return function( elem ) { + var parent, diff, + count = 0, + node = elem; + + if ( first === 1 && last === 0 ) { + return true; + } + + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.sizset) ) { + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.sizset = ++count; + if ( node === elem ) { + break; + } + } + } + + parent[ expando ] = doneName; + } + + diff = elem.sizset - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + }; + } + + return function( elem ) { + var node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + } + }; + }, + + "PSEUDO": function( pseudo, argument, context, xml ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + var fn = Expr.pseudos[ pseudo ] || Expr.pseudos[ pseudo.toLowerCase() ]; + + if ( !fn ) { + Sizzle.error( "unsupported pseudo: " + pseudo ); + } + + // The user may set fn.sizzleFilter to indicate + // that arguments are needed to create the filter function + // just as Sizzle does + if ( !fn.sizzleFilter ) { + return fn; + } + + return fn( argument, context, xml ); + } + }, + + pseudos: { + "not": markFunction(function( selector, context, xml ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var matcher = compile( selector.replace( rtrim, "$1" ), context, xml ); + return function( elem ) { + return !matcher( elem ); + }; + }), + + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + var nodeType; + elem = elem.firstChild; + while ( elem ) { + if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) { + return false; + } + elem = elem.nextSibling; + } + return true; + }, + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "text": function( elem ) { + var type, attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + (type = elem.type) === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type ); + }, + + // Input types + "radio": createInputFunction("radio"), + "checkbox": createInputFunction("checkbox"), + "file": createInputFunction("file"), + "password": createInputFunction("password"), + "image": createInputFunction("image"), + + "submit": createButtonFunction("submit"), + "reset": createButtonFunction("reset"), + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "focus": function( elem ) { + var doc = elem.ownerDocument; + return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href); + }, + + "active": function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + + setFilters: { + "first": function( elements, argument, not ) { + return not ? elements.slice( 1 ) : [ elements[0] ]; + }, + + "last": function( elements, argument, not ) { + var elem = elements.pop(); + return not ? elements : [ elem ]; + }, + + "even": function( elements, argument, not ) { + var results = [], + i = not ? 1 : 0, + len = elements.length; + for ( ; i < len; i = i + 2 ) { + results.push( elements[i] ); + } + return results; + }, + + "odd": function( elements, argument, not ) { + var results = [], + i = not ? 0 : 1, + len = elements.length; + for ( ; i < len; i = i + 2 ) { + results.push( elements[i] ); + } + return results; + }, + + "lt": function( elements, argument, not ) { + return not ? elements.slice( +argument ) : elements.slice( 0, +argument ); + }, + + "gt": function( elements, argument, not ) { + return not ? elements.slice( 0, +argument + 1 ) : elements.slice( +argument + 1 ); + }, + + "eq": function( elements, argument, not ) { + var elem = elements.splice( +argument, 1 ); + return not ? elements : elem; + } + } +}; + +// Deprecated +Expr.setFilters["nth"] = Expr.setFilters["eq"]; + +// Back-compat +Expr.filters = Expr.pseudos; + +// IE6/7 return a modified href +if ( !assertHrefNotNormalized ) { + Expr.attrHandle = { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; +} + +// Add getElementsByName if usable +if ( assertUsableName ) { + Expr.order.push("NAME"); + Expr.find["NAME"] = function( name, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; +} + +// Add getElementsByClassName if usable +if ( assertUsableClassName ) { + Expr.order.splice( 1, 0, "CLASS" ); + Expr.find["CLASS"] = function( className, context, xml ) { + if ( typeof context.getElementsByClassName !== strundefined && !xml ) { + return context.getElementsByClassName( className ); + } + }; +} + +// If slice is not available, provide a backup +try { + slice.call( docElem.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, results = []; + for ( ; (elem = this[i]); i++ ) { + results.push( elem ); + } + return results; + }; +} + +var isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +// Element contains another +var contains = Sizzle.contains = docElem.compareDocumentPosition ? + function( a, b ) { + return !!( a.compareDocumentPosition( b ) & 16 ); + } : + docElem.contains ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) ); + } : + function( a, b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + return false; + }; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + } else { + + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } + return ret; +}; + +Sizzle.attr = function( elem, name ) { + var attr, + xml = isXML( elem ); + + if ( !xml ) { + name = name.toLowerCase(); + } + if ( Expr.attrHandle[ name ] ) { + return Expr.attrHandle[ name ]( elem ); + } + if ( assertAttributes || xml ) { + return elem.getAttribute( name ); + } + attr = elem.getAttributeNode( name ); + return attr ? + typeof elem[ name ] === "boolean" ? + elem[ name ] ? name : null : + attr.specified ? attr.value : null : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + return (baseHasDuplicate = 0); +}); + + +if ( docElem.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + return ( !a.compareDocumentPosition || !b.compareDocumentPosition ? + a.compareDocumentPosition : + a.compareDocumentPosition(b) & 4 + ) ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + i = 1; + + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +function multipleContexts( selector, contexts, results, seed ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results, seed ); + } +} + +function handlePOSGroup( selector, posfilter, argument, contexts, seed, not ) { + var results, + fn = Expr.setFilters[ posfilter.toLowerCase() ]; + + if ( !fn ) { + Sizzle.error( posfilter ); + } + + if ( selector || !(results = seed) ) { + multipleContexts( selector || "*", contexts, (results = []), seed ); + } + + return results.length > 0 ? fn( results, argument, not ) : []; +} + +function handlePOS( selector, context, results, seed, groups ) { + var match, not, anchor, ret, elements, currentContexts, part, lastIndex, + i = 0, + len = groups.length, + rpos = matchExpr["POS"], + // This is generated here in case matchExpr["POS"] is extended + rposgroups = new RegExp( "^" + rpos.source + "(?!" + whitespace + ")", "i" ), + // This is for making sure non-participating + // matching groups are represented cross-browser (IE6-8) + setUndefined = function() { + var i = 1, + len = arguments.length - 2; + for ( ; i < len; i++ ) { + if ( arguments[i] === undefined ) { + match[i] = undefined; + } + } + }; + + for ( ; i < len; i++ ) { + // Reset regex index to 0 + rpos.exec(""); + selector = groups[i]; + ret = []; + anchor = 0; + elements = seed; + while ( (match = rpos.exec( selector )) ) { + lastIndex = rpos.lastIndex = match.index + match[0].length; + if ( lastIndex > anchor ) { + part = selector.slice( anchor, match.index ); + anchor = lastIndex; + currentContexts = [ context ]; + + if ( rcombinators.test(part) ) { + if ( elements ) { + currentContexts = elements; + } + elements = seed; + } + + if ( (not = rendsWithNot.test( part )) ) { + part = part.slice( 0, -5 ).replace( rcombinators, "$&*" ); + } + + if ( match.length > 1 ) { + match[0].replace( rposgroups, setUndefined ); + } + elements = handlePOSGroup( part, match[1], match[2], currentContexts, elements, not ); + } + } + + if ( elements ) { + ret = ret.concat( elements ); + + if ( (part = selector.slice( anchor )) && part !== ")" ) { + if ( rcombinators.test(part) ) { + multipleContexts( part, ret, results, seed ); + } else { + Sizzle( part, context, results, seed ? seed.concat(elements) : elements ); + } + } else { + push.apply( results, ret ); + } + } else { + Sizzle( selector, context, results, seed ); + } + } + + // Do not sort if this is a single filter + return len === 1 ? results : Sizzle.uniqueSort( results ); +} + +function tokenize( selector, context, xml ) { + var tokens, soFar, type, + groups = [], + i = 0, + + // Catch obvious selector issues: terminal ")"; nonempty fallback match + // rselector never fails to match *something* + match = rselector.exec( selector ), + matched = !match.pop() && !match.pop(), + selectorGroups = matched && selector.match( rgroups ) || [""], + + preFilters = Expr.preFilter, + filters = Expr.filter, + checkContext = !xml && context !== document; + + for ( ; (soFar = selectorGroups[i]) != null && matched; i++ ) { + groups.push( tokens = [] ); + + // Need to make sure we're within a narrower context if necessary + // Adding a descendant combinator will generate what is needed + if ( checkContext ) { + soFar = " " + soFar; + } + + while ( soFar ) { + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + soFar = soFar.slice( match[0].length ); + + // Cast descendant combinators to space + matched = tokens.push({ part: match.pop().replace( rtrim, " " ), captures: match }); + } + + // Filters + for ( type in filters ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match, context, xml )) ) ) { + + soFar = soFar.slice( match.shift().length ); + matched = tokens.push({ part: type, captures: match }); + } + } + + if ( !matched ) { + break; + } + } + } + + if ( !matched ) { + Sizzle.error( selector ); + } + + return groups; +} + +function addCombinator( matcher, combinator, context ) { + var dir = combinator.dir, + doneName = done++; + + if ( !matcher ) { + // If there is no matcher to check, check against the context + matcher = function( elem ) { + return elem === context; + }; + } + return combinator.first ? + function( elem, context ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 ) { + return matcher( elem, context ) && elem; + } + } + } : + function( elem, context ) { + var cache, + dirkey = doneName + "." + dirruns, + cachedkey = dirkey + "." + cachedruns; + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 ) { + if ( (cache = elem[ expando ]) === cachedkey ) { + return elem.sizset; + } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) { + if ( elem.sizset ) { + return elem; + } + } else { + elem[ expando ] = cachedkey; + if ( matcher( elem, context ) ) { + elem.sizset = true; + return elem; + } + elem.sizset = false; + } + } + } + }; +} + +function addMatcher( higher, deeper ) { + return higher ? + function( elem, context ) { + var result = deeper( elem, context ); + return result && higher( result === true ? elem : result, context ); + } : + deeper; +} + +// ["TAG", ">", "ID", " ", "CLASS"] +function matcherFromTokens( tokens, context, xml ) { + var token, matcher, + i = 0; + + for ( ; (token = tokens[i]); i++ ) { + if ( Expr.relative[ token.part ] ) { + matcher = addCombinator( matcher, Expr.relative[ token.part ], context ); + } else { + token.captures.push( context, xml ); + matcher = addMatcher( matcher, Expr.filter[ token.part ].apply( null, token.captures ) ); + } + } + + return matcher; +} + +function matcherFromGroupMatchers( matchers ) { + return function( elem, context ) { + var matcher, + j = 0; + for ( ; (matcher = matchers[j]); j++ ) { + if ( matcher(elem, context) ) { + return true; + } + } + return false; + }; +} + +var compile = Sizzle.compile = function( selector, context, xml ) { + var tokens, group, i, + cached = compilerCache[ selector ]; + + // Return a cached group function if already generated (context dependent) + if ( cached && cached.context === context ) { + return cached; + } + + // Generate a function of recursive functions that can be used to check each element + group = tokenize( selector, context, xml ); + for ( i = 0; (tokens = group[i]); i++ ) { + group[i] = matcherFromTokens( tokens, context, xml ); + } + + // Cache the compiled function + cached = compilerCache[ selector ] = matcherFromGroupMatchers( group ); + cached.context = context; + cached.runs = cached.dirruns = 0; + cachedSelectors.push( selector ); + // Ensure only the most recent are cached + if ( cachedSelectors.length > Expr.cacheLength ) { + delete compilerCache[ cachedSelectors.shift() ]; + } + return cached; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + return Sizzle( expr, null, null, [ elem ] ).length > 0; +}; + +var select = function( selector, context, results, seed, xml ) { + // Remove excessive whitespace + selector = selector.replace( rtrim, "$1" ); + var elements, matcher, i, len, elem, token, + type, findContext, notTokens, + match = selector.match( rgroups ), + tokens = selector.match( rtokens ), + contextNodeType = context.nodeType; + + // POS handling + if ( matchExpr["POS"].test(selector) ) { + return handlePOS( selector, context, results, seed, match ); + } + + if ( seed ) { + elements = slice.call( seed, 0 ); + + // To maintain document order, only narrow the + // set if there is one group + } else if ( match && match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + if ( tokens.length > 1 && contextNodeType === 9 && !xml && + (match = matchExpr["ID"].exec( tokens[0] )) ) { + + context = Expr.find["ID"]( match[1], context, xml )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().length ); + } + + findContext = ( (match = rsibling.exec( tokens[0] )) && !match.index && context.parentNode ) || context; + + // Get the last token, excluding :not + notTokens = tokens.pop(); + token = notTokens.split(":not")[0]; + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = matchExpr[ type ].exec( token )) ) { + elements = Expr.find[ type ]( (match[1] || "").replace( rbackslash, "" ), findContext, xml ); + + if ( elements == null ) { + continue; + } + + if ( token === notTokens ) { + selector = selector.slice( 0, selector.length - notTokens.length ) + + token.replace( matchExpr[ type ], "" ); + + if ( !selector ) { + push.apply( results, slice.call(elements, 0) ); + } + } + break; + } + } + } + + // Only loop over the given elements once + // If selector is empty, we're already done + if ( selector ) { + matcher = compile( selector, context, xml ); + dirruns = matcher.dirruns++; + + if ( elements == null ) { + elements = Expr.find["TAG"]( "*", (rsibling.test( selector ) && context.parentNode) || context ); + } + for ( i = 0; (elem = elements[i]); i++ ) { + cachedruns = matcher.runs++; + if ( matcher(elem, context) ) { + results.push( elem ); + } + } + } + + return results; +}; + +if ( document.querySelectorAll ) { + (function() { + var disconnectedMatch, + oldSelect = select, + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + rbuggyQSA = [], + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + // A support test would require too much code (would include document ready) + // just skip matchesSelector for :active + rbuggyMatches = [":active"], + matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector; + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here (do not put tests after this one) + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE9 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = "

    "; + if ( div.querySelectorAll("[test^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here (do not put tests after this one) + div.innerHTML = ""; + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push(":enabled", ":disabled"); + } + }); + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + + select = function( selector, context, results, seed, xml ) { + // Only use querySelectorAll when not filtering, + // when this is not xml, + // and when no QSA bugs apply + if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + if ( context.nodeType === 9 ) { + try { + push.apply( results, slice.call(context.querySelectorAll( selector ), 0) ); + return results; + } catch(qsaError) {} + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var old = context.getAttribute("id"), + nid = old || expando, + newContext = rsibling.test( selector ) && context.parentNode || context; + + if ( old ) { + nid = nid.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + + try { + push.apply( results, slice.call( newContext.querySelectorAll( + selector.replace( rgroups, "[id='" + nid + "'] $&" ) + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + + return oldSelect( selector, context, results, seed, xml ); + }; + + if ( matches ) { + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + try { + matches.call( div, "[test!='']:sizzle" ); + rbuggyMatches.push( Expr.match.PSEUDO ); + } catch ( e ) {} + }); + + // rbuggyMatches always contains :active, so no need for a length check + rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") ); + + Sizzle.matchesSelector = function( elem, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyMatches always contains :active, so no need for an existence check + if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, null, null, [ elem ] ).length > 0; + }; + } + })(); +} + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, l, length, n, r, ret, + self = this; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + ret = this.pushStack( "", "find", selector ); + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, core_slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + rcheckableType = /^(?:checkbox|radio)$/, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*\s*$/g, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
    ", "
    " ], + thead: [ 1, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + col: [ 2, "", "
    " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, +// unless wrapped in a div with non-breaking characters in front of it. +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "X
    ", "
    " ]; +} + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( !isDisconnected( this[0] ) ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } + + if ( arguments.length ) { + var set = jQuery.clean( arguments ); + return this.pushStack( jQuery.merge( set, this ), "before", this.selector ); + } + }, + + after: function() { + if ( !isDisconnected( this[0] ) ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } + + if ( arguments.length ) { + var set = jQuery.clean( arguments ); + return this.pushStack( jQuery.merge( this, set ), "after", this.selector ); + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName( "*" ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + if ( !isDisconnected( this[0] ) ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } + + return this.length ? + this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : + this; + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = [].concat.apply( [], args ); + + var results, first, fragment, iNoClone, + i = 0, + value = args[0], + scripts = [], + l = this.length; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call( this, i, table ? self.html() : undefined ); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + results = jQuery.buildFragment( args, this, scripts ); + fragment = results.fragment; + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + // Fragments from the fragment cache must always be cloned and never used in place. + for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) { + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + i === iNoClone ? + fragment : + jQuery.clone( fragment, true, true ) + ); + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + + if ( scripts.length ) { + jQuery.each( scripts, function( i, elem ) { + if ( elem.src ) { + if ( jQuery.ajax ) { + jQuery.ajax({ + url: elem.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.error("no ajax"); + } + } else { + jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + }); + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function cloneFixAttributes( src, dest ) { + var nodeName; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + // clearAttributes removes the attributes, which we don't want, + // but also removes the attachEvent events, which we *do* want + if ( dest.clearAttributes ) { + dest.clearAttributes(); + } + + // mergeAttributes, in contrast, only merges back on the + // original attributes, not the events + if ( dest.mergeAttributes ) { + dest.mergeAttributes( src ); + } + + nodeName = dest.nodeName.toLowerCase(); + + if ( nodeName === "object" ) { + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + + // IE blanks contents when cloning scripts + } else if ( nodeName === "script" && dest.text !== src.text ) { + dest.text = src.text; + } + + // Event data gets referenced instead of copied if the expando + // gets copied too + dest.removeAttribute( jQuery.expando ); +} + +jQuery.buildFragment = function( args, context, scripts ) { + var fragment, cacheable, cachehit, + first = args[ 0 ]; + + // Set context from what may come in as undefined or a jQuery collection or a node + context = context || document; + context = (context[0] || context).ownerDocument || context[0] || context; + + // Ensure that an attr object doesn't incorrectly stand in as a document object + // Chrome and Firefox seem to allow this to occur and will throw exception + // Fixes #8950 + if ( typeof context.createDocumentFragment === "undefined" ) { + context = document; + } + + // Only cache "small" (1/2 KB) HTML strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put or elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 + if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document && + first.charAt(0) === "<" && !rnocache.test( first ) && + (jQuery.support.checkClone || !rchecked.test( first )) && + (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { + + // Mark cacheable and look for a hit + cacheable = true; + fragment = jQuery.fragments[ first ]; + cachehit = fragment !== undefined; + } + + if ( !fragment ) { + fragment = context.createDocumentFragment(); + jQuery.clean( args, context, fragment, scripts ); + + // Update the cache, but only store false + // unless this is a second parsing of the same content + if ( cacheable ) { + jQuery.fragments[ first ] = cachehit && fragment; + } + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + l = insert.length, + parent = this.length === 1 && this[0].parentNode; + + if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) { + insert[ original ]( this[0] ); + return this; + } else { + for ( ; i < l; i++ ) { + elems = ( i > 0 ? this.clone(true) : this ).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +function getAll( elem ) { + if ( typeof elem.getElementsByTagName !== "undefined" ) { + return elem.getElementsByTagName( "*" ); + + } else if ( typeof elem.querySelectorAll !== "undefined" ) { + return elem.querySelectorAll( "*" ); + + } else { + return []; + } +} + +// Used in clean, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var srcElements, + destElements, + i, + clone; + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + // IE copies events bound via attachEvent when using cloneNode. + // Calling detachEvent on the clone will also remove the events + // from the original. In order to get around this, we use some + // proprietary methods to clear the events. Thanks to MooTools + // guys for this hotness. + + cloneFixAttributes( elem, clone ); + + // Using Sizzle here is crazy slow, so we use getElementsByTagName instead + srcElements = getAll( elem ); + destElements = getAll( clone ); + + // Weird iteration because IE will replace the length property + // with an element if you are cloning the body and one of the + // elements on the page has a name or id of "length" + for ( i = 0; srcElements[i]; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + cloneFixAttributes( srcElements[i], destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + cloneCopyEvent( elem, clone ); + + if ( deepDataAndEvents ) { + srcElements = getAll( elem ); + destElements = getAll( clone ); + + for ( i = 0; srcElements[i]; ++i ) { + cloneCopyEvent( srcElements[i], destElements[i] ); + } + } + } + + srcElements = destElements = null; + + // Return the cloned set + return clone; + }, + + clean: function( elems, context, fragment, scripts ) { + var j, safe, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags, + i = 0, + ret = []; + + // Ensure that context is a document + if ( !context || typeof context.createDocumentFragment === "undefined" ) { + context = document; + } + + // Use the already-created safe fragment if context permits + for ( safe = context === document && safeFragment; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + if ( !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + } else { + // Ensure a safe container in which to render the html + safe = safe || createSafeFragment( context ); + div = div || safe.appendChild( context.createElement("div") ); + + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1>"); + + // Go to html and back, then peel off extra wrappers + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + depth = wrap[0]; + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + hasBody = rtbody.test(elem); + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare or + wrap[1] === "
    " && !hasBody ? + div.childNodes : + []; + + for ( j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + + // Remember the top-level container for proper cleanup + div = safe.lastChild; + } + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + // Fix #11356: Clear elements from safeFragment + if ( div ) { + safe.removeChild( div ); + elem = div = safe = null; + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + for ( i = 0; (elem = ret[i]) != null; i++ ) { + if ( jQuery.nodeName( elem, "input" ) ) { + fixDefaultChecked( elem ); + } else if ( typeof elem.getElementsByTagName !== "undefined" ) { + jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); + } + } + } + + // Append elements to a provided document fragment + if ( fragment ) { + // Special handling of each script element + handleScript = function( elem ) { + // Check if we consider it executable + if ( !elem.type || rscriptType.test( elem.type ) ) { + // Detach the script and store it in the scripts array (if provided) or the fragment + // Return truthy to indicate that it has been handled + return scripts ? + scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) : + fragment.appendChild( elem ); + } + }; + + for ( i = 0; (elem = ret[i]) != null; i++ ) { + // Check if we're done after handling an executable script + if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) { + // Append to fragment and handle embedded scripts + fragment.appendChild( elem ); + if ( typeof elem.getElementsByTagName !== "undefined" ) { + // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration + jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript ); + + // Splice the scripts into ret after their former ancestor and advance our index beyond them + ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); + i += jsTags.length; + } + } + } + } + + return ret; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var data, id, elem, type, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + jQuery.deletedIds.push( id ); + } + } + } + } + } +}); +// Limit scope pollution from any deprecated API +(function() { + +var matched, browser; + +// Use of jQuery.browser is frowned upon. +// More details: http://api.jquery.com/jQuery.browser +// jQuery.uaMatch maintained for back-compat +jQuery.uaMatch = function( ua ) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || + /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; +}; + +matched = jQuery.uaMatch( navigator.userAgent ); +browser = {}; + +if ( matched.browser ) { + browser[ matched.browser ] = true; + browser.version = matched.version; +} + +// Deprecated, use jQuery.browser.webkit instead +// Maintained for back-compat only +if ( browser.webkit ) { + browser.safari = true; +} + +jQuery.browser = browser; + +jQuery.sub = function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; +}; + +})(); +var curCSS, iframe, iframeDoc, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ), + elemdisplay = {}, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400, + lineHeight: 1 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ], + + eventsToggle = jQuery.fn.toggle; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var elem, display, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + values[ index ] = jQuery._data( elem, "olddisplay" ); + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && elem.style.display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + display = curCSS( elem, "display" ); + + if ( !values[ index ] && display !== "none" ) { + jQuery._data( elem, "olddisplay", display ); + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state, fn2 ) { + var bool = typeof state === "boolean"; + + if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) { + return eventsToggle.apply( this, arguments ); + } + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, numeric, extra ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( numeric || extra !== undefined ) { + num = parseFloat( val ); + return numeric || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: To any future maintainer, we've used both window.getComputedStyle +// and getComputedStyle here to produce a better gzip size +if ( window.getComputedStyle ) { + curCSS = function( elem, name ) { + var ret, width, minWidth, maxWidth, + computed = getComputedStyle( elem, null ), + style = elem.style; + + if ( computed ) { + + ret = computed[ name ]; + if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + curCSS = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + // we use jQuery.css instead of curCSS here + // because of the reliableMarginRight CSS hook! + val += jQuery.css( elem, extra + cssExpand[ i ], true ); + } + + // From this point on we use curCSS for maximum performance (relevant in animations) + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + } + } else { + // at this point, extra isn't content, so add padding + val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + valueIsBorderBox = true, + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"; + + if ( val <= 0 ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox + ) + ) + "px"; +} + + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + if ( elemdisplay[ nodeName ] ) { + return elemdisplay[ nodeName ]; + } + + var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ), + display = elem.css("display"); + elem.remove(); + + // If the simple way fails, + // get element's real default display by attaching it to a temp iframe + if ( display === "none" || display === "" ) { + // Use the already-created iframe if possible + iframe = document.body.appendChild( + iframe || jQuery.extend( document.createElement("iframe"), { + frameBorder: 0, + width: 0, + height: 0 + }) + ); + + // Create a cacheable copy of the iframe document on first call. + // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML + // document to it; WebKit & Firefox won't allow reusing the iframe document. + if ( !iframeDoc || !iframe.createElement ) { + iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; + iframeDoc.write(""); + iframeDoc.close(); + } + + elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) ); + + display = curCSS( elem, "display" ); + document.body.removeChild( iframe ); + } + + // Store the correct default display + elemdisplay[ nodeName ] = display; + + return display; +} + +jQuery.each([ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + if ( elem.offsetWidth !== 0 || curCSS( elem, "display" ) !== "none" ) { + return getWidthOrHeight( elem, name, extra ); + } else { + return jQuery.swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + }); + } + } + }, + + set: function( elem, value, extra ) { + return setPositiveNumber( elem, value, extra ? + augmentWidthOrHeight( + elem, + name, + extra, + jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box" + ) : 0 + ); + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? + ( 0.01 * parseFloat( RegExp.$1 ) ) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style, + currentStyle = elem.currentStyle, + opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", + filter = currentStyle && currentStyle.filter || style.filter || ""; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 + if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" && + style.removeAttribute ) { + + // Setting style.filter to null, "" & " " still leave "filter:" in the cssText + // if "filter:" is present at all, clearType is disabled, we want to avoid this + // style.removeAttribute is IE Only, but so apparently is this code path... + style.removeAttribute( "filter" ); + + // if there there is no filter style applied in a css rule, we are done + if ( currentStyle && !currentStyle.filter ) { + return; + } + } + + // otherwise, set new filter values + style.filter = ralpha.test( filter ) ? + filter.replace( ralpha, opacity ) : + filter + " " + opacity; + } + }; +} + +// These hooks cannot be added until DOM ready because the support test +// for it is not run until after DOM ready +jQuery(function() { + if ( !jQuery.support.reliableMarginRight ) { + jQuery.cssHooks.marginRight = { + get: function( elem, computed ) { + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + // Work around by temporarily setting element display to inline-block + return jQuery.swap( elem, { "display": "inline-block" }, function() { + if ( computed ) { + return curCSS( elem, "marginRight" ); + } + }); + } + }; + } + + // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 + // getComputedStyle returns percent when specified for top/left/bottom/right + // rather than make the css module depend on the offset module, we just check for it here + if ( !jQuery.support.pixelPosition && jQuery.fn.position ) { + jQuery.each( [ "top", "left" ], function( i, prop ) { + jQuery.cssHooks[ prop ] = { + get: function( elem, computed ) { + if ( computed ) { + var ret = curCSS( elem, prop ); + // if curCSS returns percentage, fallback to offset + return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret; + } + } + }; + }); + } + +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + +// These hooks are used by animate to expand properties +jQuery.each({ + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i, + + // assumes a single number if not a string + parts = typeof value === "string" ? value.split(" ") : [ value ], + expanded = {}; + + for ( i = 0; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +}); +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rselectTextarea = /^(?:select|textarea)/i; + +jQuery.fn.extend({ + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); + + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); + +//Serialize an array of form elements or a set of +//key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); +}; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( jQuery.isArray( obj ) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} +var // Document location + ajaxLocation, + // Document location segments + ajaxLocParts, + + rhash = /#.*$/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + rquery = /\?/, + rscript = /)<[^<]*)*<\/script>/gi, + rts = /([?&])_=[^&]*/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = ["*/"] + ["*"]; + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, list, placeBefore, + dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ), + i = 0, + length = dataTypes.length; + + if ( jQuery.isFunction( func ) ) { + // For each dataType in the dataTypeExpression + for ( ; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var selection, + list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ); + + for ( ; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jqXHR ); + // If we got redirected to another dataType + // we try there if executing only and not done already + if ( typeof selection === "string" ) { + if ( !executeOnly || inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } +} + +jQuery.fn.load = function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + } + + // Don't do a request if no elements are being requested + if ( !this.length ) { + return this; + } + + var selector, type, response, + self = this, + off = url.indexOf(" "); + + if ( off >= 0 ) { + selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( jQuery.isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + type = "POST"; + } + + // Request the remote document + jQuery.ajax({ + url: url, + + // if "type" variable is undefined, then "GET" method will be used + type: type, + dataType: "html", + data: params, + complete: function( jqXHR, status ) { + if ( callback ) { + self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); + } + } + }).done(function( responseText ) { + + // Save response for use in complete callback + response = arguments; + + // See if a selector was specified + self.html( selector ? + + // Create a dummy div to hold the results + jQuery("
    ") + + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append( responseText.replace( rscript, "" ) ) + + // Locate the specified elements + .find( selector ) : + + // If not, just inject the full result + responseText ); + + }); + + return this; +}; + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.on( o, f ); + }; +}); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); + }; +}); + +jQuery.extend({ + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + if ( settings ) { + // Building a settings object + ajaxExtend( target, jQuery.ajaxSettings ); + } else { + // Extending ajaxSettings + settings = target; + target = jQuery.ajaxSettings; + } + ajaxExtend( target, settings ); + return target; + }, + + ajaxSettings: { + url: ajaxLocation, + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + processData: true, + async: true, + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + text: "text/plain", + json: "application/json, text/javascript", + "*": allTypes + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + context: true, + url: true + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // ifModified key + ifModifiedKey, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events + // It's the callbackContext if one was provided in the options + // and if it's a DOM node or a jQuery collection + globalEventContext = callbackContext !== s && + ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? + jQuery( callbackContext ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // The jqXHR state + state = 0, + // Default abort message + strAbort = "canceled", + // Fake xhr + jqXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( !state ) { + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match === undefined ? null : match; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + statusText = statusText || strAbort; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + modified = jqXHR.getResponseHeader("Last-Modified"); + if ( modified ) { + jQuery.lastModified[ ifModifiedKey ] = modified; + } + modified = jqXHR.getResponseHeader("Etag"); + if ( modified ) { + jQuery.etag[ ifModifiedKey ] = modified; + } + } + + // If not modified + if ( status === 304 ) { + + statusText = "notmodified"; + isSuccess = true; + + // If we have data + } else { + + isSuccess = ajaxConvert( s, response ); + statusText = isSuccess.state; + success = isSuccess.data; + error = isSuccess.error; + isSuccess = !error; + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if ( !statusText || status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = "" + ( nativeStatusText || statusText ); + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jqXHR ); + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + jqXHR.complete = completeDeferred.add; + + // Status-dependent callbacks + jqXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for ( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jqXHR.status ]; + jqXHR.always( tmp ); + } + } + return this; + }; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace ); + + // Determine if a cross-domain request is in order + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( state === 2 ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + fireGlobals = s.global; + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Get ifModifiedKey before adding the anti-cache parameter + ifModifiedKey = s.url; + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + ifModifiedKey = ifModifiedKey || s.url; + if ( jQuery.lastModified[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); + } + if ( jQuery.etag[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); + } + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already and return + return jqXHR.abort(); + + } + + // aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + state = 1; + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( state < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + throw e; + } + } + } + + return jqXHR; + }, + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {} + +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields; + + // Fill responseXXX fields + for ( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + + var conv, conv2, current, tmp, + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(), + prev = dataTypes[ 0 ], + converters = {}, + i = 0; + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + // Convert to each sequential dataType, tolerating list modification + for ( ; (current = dataTypes[++i]); ) { + + // There's only work to do if current dataType is non-auto + if ( current !== "*" ) { + + // Convert response if prev dataType is non-auto and differs from current + if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.splice( i--, 0, current ); + } + + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s["throws"] ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; + } + } + } + } + + // Update prev for next iteration + prev = current; + } + } + + return { state: "success", data: response }; +} +var oldCallbacks = [], + rquestion = /\?/, + rjsonp = /(=)\?(?=&|$)|\?\?/, + nonce = jQuery.now(); + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + this[ callback ] = true; + return callback; + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var callbackName, overwritten, responseContainer, + data = s.data, + url = s.url, + hasCallback = s.jsonp !== false, + replaceInUrl = hasCallback && rjsonp.test( url ), + replaceInData = hasCallback && !replaceInUrl && typeof data === "string" && + !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && + rjsonp.test( data ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + overwritten = window[ callbackName ]; + + // Insert callback into url or form data + if ( replaceInUrl ) { + s.url = url.replace( rjsonp, "$1" + callbackName ); + } else if ( replaceInData ) { + s.data = data.replace( rjsonp, "$1" + callbackName ); + } else if ( hasCallback ) { + s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( callbackName + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Install callback + window[ callbackName ] = function() { + responseContainer = arguments; + }; + + // Clean-up function (fires after converters) + jqXHR.always(function() { + // Restore preexisting value + window[ callbackName ] = overwritten; + + // Save back as free + if ( s[ callbackName ] ) { + // make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // save the callback name for future use + oldCallbacks.push( callbackName ); + } + + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + + responseContainer = overwritten = undefined; + }); + + // Delegate to script + return "script"; + } +}); +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /javascript|ecmascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } +}); +var xhrCallbacks, + // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { + // Abort all pending requests + for ( var key in xhrCallbacks ) { + xhrCallbacks[ key ]( 0, 1 ); + } + } : false, + xhrId = 0; + +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject( "Microsoft.XMLHTTP" ); + } catch( e ) {} +} + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var handle, i, + xhr = s.xhr(); + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + var status, + statusText, + responseHeaders, + responses, + xml; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occurred + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + responses = {}; + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + + // When requesting binary data, IE6-9 will throw an exception + // on any attempt to access responseText (#11426) + try { + responses.text = xhr.responseText; + } catch( _ ) { + } + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + if ( !s.async ) { + // if we're in sync mode we fire the callback + callback(); + } else if ( xhr.readyState === 4 ) { + // (IE6 & IE7) if it's in cache and has been + // retrieved directly we need to fire the callback + setTimeout( callback, 0 ); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} +var fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ), + rrun = /queueHooks$/, + animationPrefilters = [ defaultPrefilter ], + tweeners = { + "*": [function( prop, value ) { + var end, unit, prevScale, + tween = this.createTween( prop, value ), + parts = rfxnum.exec( value ), + target = tween.cur(), + start = +target || 0, + scale = 1; + + if ( parts ) { + end = +parts[2]; + unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + + // We need to compute starting value + if ( unit !== "px" && start ) { + // Iteratively approximate from a nonzero starting point + // Prefer the current property, because this process will be trivial if it uses the same units + // Fallback to end or a simple constant + start = jQuery.css( tween.elem, prop, true ) || end || 1; + + do { + // If previous iteration zeroed out, double until we get *something* + // Use a string for doubling factor so we don't accidentally see scale as unchanged below + prevScale = scale = scale || ".5"; + + // Adjust and apply + start = start / scale; + jQuery.style( tween.elem, prop, start + unit ); + + // Update scale, tolerating zeroes from tween.cur() + scale = tween.cur() / target; + + // Stop looping if we've hit the mark or scale is unchanged + } while ( scale !== 1 && scale !== prevScale ); + } + + tween.unit = unit; + tween.start = start; + // If a +=/-= token was provided, we're doing a relative animation + tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; + } + return tween; + }] + }; + +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout(function() { + fxNow = undefined; + }, 0 ); + return ( fxNow = jQuery.now() ); +} + +function createTweens( animation, props ) { + jQuery.each( props, function( prop, value ) { + var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( collection[ index ].call( animation, prop, value ) ) { + + // we're done with this property + return; + } + } + }); +} + +function Animation( elem, properties, options ) { + var result, + index = 0, + tweenerIndex = 0, + length = animationPrefilters.length, + deferred = jQuery.Deferred().always( function() { + // don't match elem in the :animated selector + delete tick.elem; + }), + tick = function() { + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + percent = 1 - ( remaining / animation.duration || 0 ), + index = 0, + length = animation.tweens.length; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ]); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { specialEasing: {} }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end, easing ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + // if we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // resolve when we played the last frame + // otherwise, reject + if ( gotoEnd ) { + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + }), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length ; index++ ) { + result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + return result; + } + } + + createTweens( animation, props ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + anim: animation, + queue: animation.opts.queue, + elem: elem + }) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // not quite $.extend, this wont overwrite keys already present. + // also - reusing 'index' from above because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.split(" "); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length ; index++ ) { + prop = props[ index ]; + tweeners[ prop ] = tweeners[ prop ] || []; + tweeners[ prop ].unshift( callback ); + } + }, + + prefilter: function( callback, prepend ) { + if ( prepend ) { + animationPrefilters.unshift( callback ); + } else { + animationPrefilters.push( callback ); + } + } +}); + +function defaultPrefilter( elem, props, opts ) { + var index, prop, value, length, dataShow, tween, hooks, oldfire, + anim = this, + style = elem.style, + orig = {}, + handled = [], + hidden = elem.nodeType && isHidden( elem ); + + // handle queue: false promises + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always(function() { + // doing this makes sure that the complete handler will be called + // before this completes + anim.always(function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + }); + }); + } + + // height/width overflow pass + if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height animated + if ( jQuery.css( elem, "display" ) === "inline" && + jQuery.css( elem, "float" ) === "none" ) { + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) { + style.display = "inline-block"; + + } else { + style.zoom = 1; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + if ( !jQuery.support.shrinkWrapBlocks ) { + anim.done(function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + }); + } + } + + + // show/hide pass + for ( index in props ) { + value = props[ index ]; + if ( rfxtypes.exec( value ) ) { + delete props[ index ]; + if ( value === ( hidden ? "hide" : "show" ) ) { + continue; + } + handled.push( index ); + } + } + + length = handled.length; + if ( length ) { + dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); + if ( hidden ) { + jQuery( elem ).show(); + } else { + anim.done(function() { + jQuery( elem ).hide(); + }); + } + anim.done(function() { + var prop; + jQuery.removeData( elem, "fxshow", true ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + }); + for ( index = 0 ; index < length ; index++ ) { + prop = handled[ index ]; + tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); + orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); + + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = tween.start; + if ( hidden ) { + tween.end = tween.start; + tween.start = prop === "width" || prop === "height" ? 1 : 0; + } + } + } + } +} + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || "swing"; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration ); + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + if ( tween.elem[ tween.prop ] != null && + (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { + return tween.elem[ tween.prop ]; + } + + // passing any value as a 4th parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails + // so, simple values such as "10px" are parsed to Float. + // complex values such as "rotate(1rad)" are returned as is. + result = jQuery.css( tween.elem, tween.prop, false, "" ); + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + // use step hook for back compat - use cssHook if its there - use .style if its + // available and use plain properties where available + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Remove in 2.0 - this supports IE8's panic based approach +// to setting things on disconnected nodes + +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" || + // special check for .toggle( handler, handler, ... ) + ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +}); + +jQuery.fn.extend({ + fadeTo: function( speed, to, easing, callback ) { + + // show any hidden elements after setting opacity to 0 + return this.filter( isHidden ).css( "opacity", 0 ).show() + + // animate to the value specified + .end().animate({ opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations resolve immediately + if ( empty ) { + anim.stop( true ); + } + }; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each(function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = jQuery._data( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // start the next in the queue if the last step wasn't forced + // timers currently will call their complete callbacks, which will dequeue + // but only if they were gotoEnd + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + }); + } +}); + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + attrs = { height: type }, + i = 0; + + // if we include width, step value is 1 to do all cssExpand values, + // if we don't include width, step value is 2 to skip over Left and Right + for( ; i < 4 ; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p*Math.PI ) / 2; + } +}; + +jQuery.timers = []; +jQuery.fx = Tween.prototype.init; +jQuery.fx.tick = function() { + var timer, + timers = jQuery.timers, + i = 0; + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } +}; + +jQuery.fx.timer = function( timer ) { + if ( timer() && jQuery.timers.push( timer ) && !timerId ) { + timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.interval = 13; + +jQuery.fx.stop = function() { + clearInterval( timerId ); + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + // Default speed + _default: 400 +}; + +// Back Compat <1.8 extension point +jQuery.fx.step = {}; + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} +var rroot = /^(?:body|html)$/i; + +jQuery.fn.offset = function( options ) { + if ( arguments.length ) { + return options === undefined ? + this : + this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + var box, docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, top, left, + elem = this[ 0 ], + doc = elem && elem.ownerDocument; + + if ( !doc ) { + return; + } + + if ( (body = doc.body) === elem ) { + return jQuery.offset.bodyOffset( elem ); + } + + docElem = doc.documentElement; + + // Make sure we're not dealing with a disconnected DOM node + if ( !jQuery.contains( docElem, elem ) ) { + return { top: 0, left: 0 }; + } + + box = elem.getBoundingClientRect(); + win = getWindow( doc ); + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + scrollTop = win.pageYOffset || docElem.scrollTop; + scrollLeft = win.pageXOffset || docElem.scrollLeft; + top = box.top + scrollTop - clientTop; + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; +}; + +jQuery.offset = { + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is either absolute or fixed + if ( calculatePosition ) { + curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; + } + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; + } + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + + position: function() { + if ( !this[0] ) { + return; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || document.body; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { + var top = /Y/.test( prop ); + + jQuery.fn[ method ] = function( val ) { + return jQuery.access( this, function( elem, method, val ) { + var win = getWindow( elem ); + + if ( val === undefined ) { + return win ? (prop in win) ? win[ prop ] : + win.document.documentElement[ method ] : + elem[ method ]; + } + + if ( win ) { + win.scrollTo( + !top ? val : jQuery( win ).scrollLeft(), + top ? val : jQuery( win ).scrollTop() + ); + + } else { + elem[ method ] = val; + } + }, method, val, arguments.length, null ); + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { + jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { + // margin is only for outerHeight, outerWidth + jQuery.fn[ funcName ] = function( margin, value ) { + var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), + extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); + + return jQuery.access( this, function( elem, type, value ) { + var doc; + + if ( jQuery.isWindow( elem ) ) { + // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there + // isn't a whole lot we can do. See pull request at this URL for discussion: + // https://github.com/jquery/jquery/pull/764 + return elem.document.documentElement[ "client" + name ]; + } + + // Get document width or height + if ( elem.nodeType === 9 ) { + doc = elem.documentElement; + + // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest + // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it. + return Math.max( + elem.body[ "scroll" + name ], doc[ "scroll" + name ], + elem.body[ "offset" + name ], doc[ "offset" + name ], + doc[ "client" + name ] + ); + } + + return value === undefined ? + // Get width or height on the element, requesting but not forcing parseFloat + jQuery.css( elem, type, value, extra ) : + + // Set width or height on the element + jQuery.style( elem, type, value, extra ); + }, type, chainable ? margin : undefined, chainable ); + }; + }); +}); +// Expose jQuery to the global object +window.jQuery = window.$ = jQuery; + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + +})( window ); \ No newline at end of file diff --git a/preview/js/lib/jquery-ui-1.10.3.custom.js b/preview/js/lib/jquery-ui-1.10.3.custom.js new file mode 100644 index 00000000..4bc322f8 --- /dev/null +++ b/preview/js/lib/jquery-ui-1.10.3.custom.js @@ -0,0 +1,2252 @@ +/*! jQuery UI - v1.10.3 - 2013-06-12 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js +* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ + +(function( $, undefined ) { + +var uuid = 0, + runiqueId = /^ui-id-\d+$/; + +// $.ui might exist from components with no dependencies, e.g., $.ui.position +$.ui = $.ui || {}; + +$.extend( $.ui, { + version: "1.10.3", + + keyCode: { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + NUMPAD_ADD: 107, + NUMPAD_DECIMAL: 110, + NUMPAD_DIVIDE: 111, + NUMPAD_ENTER: 108, + NUMPAD_MULTIPLY: 106, + NUMPAD_SUBTRACT: 109, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 + } +}); + +// plugins +$.fn.extend({ + focus: (function( orig ) { + return function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + orig.apply( this, arguments ); + }; + })( $.fn.focus ), + + scrollParent: function() { + var scrollParent; + if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { + scrollParent = this.parents().filter(function() { + return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); + }).eq(0); + } else { + scrollParent = this.parents().filter(function() { + return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); + }).eq(0); + } + + return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; + }, + + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); + } + + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + //
    + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + }, + + uniqueId: function() { + return this.each(function() { + if ( !this.id ) { + this.id = "ui-id-" + (++uuid); + } + }); + }, + + removeUniqueId: function() { + return this.each(function() { + if ( runiqueId.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + }); + } +}); + +// selectors +function focusable( element, isTabIndexNotNaN ) { + var map, mapName, img, + nodeName = element.nodeName.toLowerCase(); + if ( "area" === nodeName ) { + map = element.parentNode; + mapName = map.name; + if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { + return false; + } + img = $( "img[usemap=#" + mapName + "]" )[0]; + return !!img && visible( img ); + } + return ( /input|select|textarea|button|object/.test( nodeName ) ? + !element.disabled : + "a" === nodeName ? + element.href || isTabIndexNotNaN : + isTabIndexNotNaN) && + // the element and all of its ancestors must be visible + visible( element ); +} + +function visible( element ) { + return $.expr.filters.visible( element ) && + !$( element ).parents().addBack().filter(function() { + return $.css( this, "visibility" ) === "hidden"; + }).length; +} + +$.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo(function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + }) : + // support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + }, + + focusable: function( element ) { + return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); + }, + + tabbable: function( element ) { + var tabIndex = $.attr( element, "tabindex" ), + isTabIndexNaN = isNaN( tabIndex ); + return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); + } +}); + +// support: jQuery <1.8 +if ( !$( "" ).outerWidth( 1 ).jquery ) { + $.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; + if ( border ) { + size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; + } + }); + return size; + } + + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); + } + + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; + + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } + + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; + }); +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) +if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { + $.fn.removeData = (function( removeData ) { + return function( key ) { + if ( arguments.length ) { + return removeData.call( this, $.camelCase( key ) ); + } else { + return removeData.call( this ); + } + }; + })( $.fn.removeData ); +} + + + + + +// deprecated +$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +$.support.selectstart = "onselectstart" in document.createElement( "div" ); +$.fn.extend({ + disableSelection: function() { + return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }, + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + } +}); + +$.extend( $.ui, { + // $.ui.plugin is deprecated. Use $.widget() extensions instead. + plugin: { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args ) { + var i, + set = instance.plugins[ name ]; + if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } + }, + + // only used by resizable + hasScroll: function( el, a ) { + + //If overflow is hidden, the element might have extra content, but the user wants to hide it + if ( $( el ).css( "overflow" ) === "hidden") { + return false; + } + + var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", + has = false; + + if ( el[ scroll ] > 0 ) { + return true; + } + + // TODO: determine which cases actually cause this to happen + // if the element doesn't have the scroll set, see if it's possible to + // set the scroll + el[ scroll ] = 1; + has = ( el[ scroll ] > 0 ); + el[ scroll ] = 0; + return has; + } +}); + +})( jQuery ); +(function( $, undefined ) { + +var uuid = 0, + slice = Array.prototype.slice, + _cleanData = $.cleanData; +$.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); +}; + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
    ", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + // 1.9 BC for #7810 + // TODO remove dual storage + .removeData( this.widgetName ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +})( jQuery ); +(function( $, undefined ) { + +var mouseHandled = false; +$( document ).mouseup( function() { + mouseHandled = false; +}); + +$.widget("ui.mouse", { + version: "1.10.3", + options: { + cancel: "input,textarea,button,select,option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .bind("mousedown."+this.widgetName, function(event) { + return that._mouseDown(event); + }) + .bind("click."+this.widgetName, function(event) { + if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { + $.removeData(event.target, that.widgetName + ".preventClickEvent"); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind("."+this.widgetName); + if ( this._mouseMoveDelegate ) { + $(document) + .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + } + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + if( mouseHandled ) { return; } + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = (event.which === 1), + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + that.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { + $.removeData(event.target, this.widgetName + ".preventClickEvent"); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return that._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return that._mouseUp(event); + }; + $(document) + .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .bind("mouseup."+this.widgetName, this._mouseUpDelegate); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + $(document) + .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target === this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + ".preventClickEvent", true); + } + + this._mouseStop(event); + } + + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(/* event */) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(/* event */) {}, + _mouseDrag: function(/* event */) {}, + _mouseStop: function(/* event */) {}, + _mouseCapture: function(/* event */) { return true; } +}); + +})(jQuery); +(function( $, undefined ) { + +/*jshint loopfunc: true */ + +function isOverAxis( x, reference, size ) { + return ( x > reference ) && ( x < ( reference + size ) ); +} + +function isFloating(item) { + return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); +} + +$.widget("ui.sortable", $.ui.mouse, { + version: "1.10.3", + widgetEventPrefix: "sort", + ready: false, + options: { + appendTo: "parent", + axis: false, + connectWith: false, + containment: false, + cursor: "auto", + cursorAt: false, + dropOnEmpty: true, + forcePlaceholderSize: false, + forceHelperSize: false, + grid: false, + handle: false, + helper: "original", + items: "> *", + opacity: false, + placeholder: false, + revert: false, + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + scope: "default", + tolerance: "intersect", + zIndex: 1000, + + // callbacks + activate: null, + beforeStop: null, + change: null, + deactivate: null, + out: null, + over: null, + receive: null, + remove: null, + sort: null, + start: null, + stop: null, + update: null + }, + _create: function() { + + var o = this.options; + this.containerCache = {}; + this.element.addClass("ui-sortable"); + + //Get the items + this.refresh(); + + //Let's determine if the items are being displayed horizontally + this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; + + //Let's determine the parent's offset + this.offset = this.element.offset(); + + //Initialize mouse events for interaction + this._mouseInit(); + + //We're ready to go + this.ready = true; + + }, + + _destroy: function() { + this.element + .removeClass("ui-sortable ui-sortable-disabled"); + this._mouseDestroy(); + + for ( var i = this.items.length - 1; i >= 0; i-- ) { + this.items[i].item.removeData(this.widgetName + "-item"); + } + + return this; + }, + + _setOption: function(key, value){ + if ( key === "disabled" ) { + this.options[ key ] = value; + + this.widget().toggleClass( "ui-sortable-disabled", !!value ); + } else { + // Don't call widget base _setOption for disable as it adds ui-state-disabled class + $.Widget.prototype._setOption.apply(this, arguments); + } + }, + + _mouseCapture: function(event, overrideHandle) { + var currentItem = null, + validHandle = false, + that = this; + + if (this.reverting) { + return false; + } + + if(this.options.disabled || this.options.type === "static") { + return false; + } + + //We have to refresh the items data once first + this._refreshItems(event); + + //Find out if the clicked node (or one of its parents) is a actual item in this.items + $(event.target).parents().each(function() { + if($.data(this, that.widgetName + "-item") === that) { + currentItem = $(this); + return false; + } + }); + if($.data(event.target, that.widgetName + "-item") === that) { + currentItem = $(event.target); + } + + if(!currentItem) { + return false; + } + if(this.options.handle && !overrideHandle) { + $(this.options.handle, currentItem).find("*").addBack().each(function() { + if(this === event.target) { + validHandle = true; + } + }); + if(!validHandle) { + return false; + } + } + + this.currentItem = currentItem; + this._removeCurrentsFromItems(); + return true; + + }, + + _mouseStart: function(event, overrideHandle, noActivation) { + + var i, body, + o = this.options; + + this.currentContainer = this; + + //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture + this.refreshPositions(); + + //Create and append the visible helper + this.helper = this._createHelper(event); + + //Cache the helper size + this._cacheHelperProportions(); + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Get the next scrolling parent + this.scrollParent = this.helper.scrollParent(); + + //The element's absolute position on the page minus margins + this.offset = this.currentItem.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + // Only after we got the offset, we can change the helper's position to absolute + // TODO: Still need to figure out a way to make relative sorting possible + this.helper.css("position", "absolute"); + this.cssPosition = this.helper.css("position"); + + //Generate the original position + this.originalPosition = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Cache the former DOM position + this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; + + //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way + if(this.helper[0] !== this.currentItem[0]) { + this.currentItem.hide(); + } + + //Create the placeholder + this._createPlaceholder(); + + //Set a containment if given in the options + if(o.containment) { + this._setContainment(); + } + + if( o.cursor && o.cursor !== "auto" ) { // cursor option + body = this.document.find( "body" ); + + // support: IE + this.storedCursor = body.css( "cursor" ); + body.css( "cursor", o.cursor ); + + this.storedStylesheet = $( "" ).appendTo( body ); + } + + if(o.opacity) { // opacity option + if (this.helper.css("opacity")) { + this._storedOpacity = this.helper.css("opacity"); + } + this.helper.css("opacity", o.opacity); + } + + if(o.zIndex) { // zIndex option + if (this.helper.css("zIndex")) { + this._storedZIndex = this.helper.css("zIndex"); + } + this.helper.css("zIndex", o.zIndex); + } + + //Prepare scrolling + if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { + this.overflowOffset = this.scrollParent.offset(); + } + + //Call callbacks + this._trigger("start", event, this._uiHash()); + + //Recache the helper size + if(!this._preserveHelperProportions) { + this._cacheHelperProportions(); + } + + + //Post "activate" events to possible containers + if( !noActivation ) { + for ( i = this.containers.length - 1; i >= 0; i-- ) { + this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); + } + } + + //Prepare possible droppables + if($.ui.ddmanager) { + $.ui.ddmanager.current = this; + } + + if ($.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + + this.dragging = true; + + this.helper.addClass("ui-sortable-helper"); + this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position + return true; + + }, + + _mouseDrag: function(event) { + var i, item, itemElement, intersection, + o = this.options, + scrolled = false; + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + //Do scrolling + if(this.options.scroll) { + if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + } + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + } + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + } + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + } + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + //Set the helper position + if(!this.options.axis || this.options.axis !== "y") { + this.helper[0].style.left = this.position.left+"px"; + } + if(!this.options.axis || this.options.axis !== "x") { + this.helper[0].style.top = this.position.top+"px"; + } + + //Rearrange + for (i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + item = this.items[i]; + itemElement = item.item[0]; + intersection = this._intersectsWithPointer(item); + if (!intersection) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items form other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this moving items in "sub-sortables" can cause the placeholder to jitter + // beetween the outer and inner container. + if (item.instance !== this.currentContainer) { + continue; + } + + // cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if (itemElement !== this.currentItem[0] && + this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && + !$.contains(this.placeholder[0], itemElement) && + (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { + this._rearrange(event, item); + } else { + break; + } + + this._trigger("change", event, this._uiHash()); + break; + } + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) { + $.ui.ddmanager.drag(this, event); + } + + //Call callbacks + this._trigger("sort", event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + if(!event) { + return; + } + + //If we are using droppables, inform the manager about the drop + if ($.ui.ddmanager && !this.options.dropBehaviour) { + $.ui.ddmanager.drop(this, event); + } + + if(this.options.revert) { + var that = this, + cur = this.placeholder.offset(), + axis = this.options.axis, + animation = {}; + + if ( !axis || axis === "x" ) { + animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft); + } + if ( !axis || axis === "y" ) { + animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop); + } + this.reverting = true; + $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { + that._clear(event); + }); + } else { + this._clear(event, noPropagation); + } + + return false; + + }, + + cancel: function() { + + if(this.dragging) { + + this._mouseUp({ target: null }); + + if(this.options.helper === "original") { + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + //Post deactivating events to containers + for (var i = this.containers.length - 1; i >= 0; i--){ + this.containers[i]._trigger("deactivate", null, this._uiHash(this)); + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", null, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + if (this.placeholder) { + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + if(this.placeholder[0].parentNode) { + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + } + if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { + this.helper.remove(); + } + + $.extend(this, { + helper: null, + dragging: false, + reverting: false, + _noFinalSort: null + }); + + if(this.domPosition.prev) { + $(this.domPosition.prev).after(this.currentItem); + } else { + $(this.domPosition.parent).prepend(this.currentItem); + } + } + + return this; + + }, + + serialize: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected), + str = []; + o = o || {}; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); + if (res) { + str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); + } + }); + + if(!str.length && o.key) { + str.push(o.key + "="); + } + + return str.join("&"); + + }, + + toArray: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected), + ret = []; + + o = o || {}; + + items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); + return ret; + + }, + + /* Be careful with the following core functions */ + _intersectsWith: function(item) { + + var x1 = this.positionAbs.left, + x2 = x1 + this.helperProportions.width, + y1 = this.positionAbs.top, + y2 = y1 + this.helperProportions.height, + l = item.left, + r = l + item.width, + t = item.top, + b = t + item.height, + dyClick = this.offset.click.top, + dxClick = this.offset.click.left, + isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), + isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), + isOverElement = isOverElementHeight && isOverElementWidth; + + if ( this.options.tolerance === "pointer" || + this.options.forcePointerForContainers || + (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) + ) { + return isOverElement; + } else { + + return (l < x1 + (this.helperProportions.width / 2) && // Right Half + x2 - (this.helperProportions.width / 2) < r && // Left Half + t < y1 + (this.helperProportions.height / 2) && // Bottom Half + y2 - (this.helperProportions.height / 2) < b ); // Top Half + + } + }, + + _intersectsWithPointer: function(item) { + + var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), + isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), + isOverElement = isOverElementHeight && isOverElementWidth, + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (!isOverElement) { + return false; + } + + return this.floating ? + ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) + : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); + + }, + + _intersectsWithSides: function(item) { + + var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), + isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (this.floating && horizontalDirection) { + return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); + } else { + return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); + } + + }, + + _getDragVerticalDirection: function() { + var delta = this.positionAbs.top - this.lastPositionAbs.top; + return delta !== 0 && (delta > 0 ? "down" : "up"); + }, + + _getDragHorizontalDirection: function() { + var delta = this.positionAbs.left - this.lastPositionAbs.left; + return delta !== 0 && (delta > 0 ? "right" : "left"); + }, + + refresh: function(event) { + this._refreshItems(event); + this.refreshPositions(); + return this; + }, + + _connectWith: function() { + var options = this.options; + return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; + }, + + _getItemsAsjQuery: function(connected) { + + var i, j, cur, inst, + items = [], + queries = [], + connectWith = this._connectWith(); + + if(connectWith && connected) { + for (i = connectWith.length - 1; i >= 0; i--){ + cur = $(connectWith[i]); + for ( j = cur.length - 1; j >= 0; j--){ + inst = $.data(cur[j], this.widgetFullName); + if(inst && inst !== this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); + } + } + } + } + + queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); + + for (i = queries.length - 1; i >= 0; i--){ + queries[i][0].each(function() { + items.push(this); + }); + } + + return $(items); + + }, + + _removeCurrentsFromItems: function() { + + var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); + + this.items = $.grep(this.items, function (item) { + for (var j=0; j < list.length; j++) { + if(list[j] === item.item[0]) { + return false; + } + } + return true; + }); + + }, + + _refreshItems: function(event) { + + this.items = []; + this.containers = [this]; + + var i, j, cur, inst, targetData, _queries, item, queriesLength, + items = this.items, + queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], + connectWith = this._connectWith(); + + if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down + for (i = connectWith.length - 1; i >= 0; i--){ + cur = $(connectWith[i]); + for (j = cur.length - 1; j >= 0; j--){ + inst = $.data(cur[j], this.widgetFullName); + if(inst && inst !== this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); + this.containers.push(inst); + } + } + } + } + + for (i = queries.length - 1; i >= 0; i--) { + targetData = queries[i][1]; + _queries = queries[i][0]; + + for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { + item = $(_queries[j]); + + item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) + + items.push({ + item: item, + instance: targetData, + width: 0, height: 0, + left: 0, top: 0 + }); + } + } + + }, + + refreshPositions: function(fast) { + + //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change + if(this.offsetParent && this.helper) { + this.offset.parent = this._getParentOffset(); + } + + var i, item, t, p; + + for (i = this.items.length - 1; i >= 0; i--){ + item = this.items[i]; + + //We ignore calculating positions of all connected containers when we're not over them + if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { + continue; + } + + t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; + + if (!fast) { + item.width = t.outerWidth(); + item.height = t.outerHeight(); + } + + p = t.offset(); + item.left = p.left; + item.top = p.top; + } + + if(this.options.custom && this.options.custom.refreshContainers) { + this.options.custom.refreshContainers.call(this); + } else { + for (i = this.containers.length - 1; i >= 0; i--){ + p = this.containers[i].element.offset(); + this.containers[i].containerCache.left = p.left; + this.containers[i].containerCache.top = p.top; + this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); + this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); + } + } + + return this; + }, + + _createPlaceholder: function(that) { + that = that || this; + var className, + o = that.options; + + if(!o.placeholder || o.placeholder.constructor === String) { + className = o.placeholder; + o.placeholder = { + element: function() { + + var nodeName = that.currentItem[0].nodeName.toLowerCase(), + element = $( "<" + nodeName + ">", that.document[0] ) + .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") + .removeClass("ui-sortable-helper"); + + if ( nodeName === "tr" ) { + that.currentItem.children().each(function() { + $( "
    ", that.document[0] ) + .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) + .appendTo( element ); + }); + } else if ( nodeName === "img" ) { + element.attr( "src", that.currentItem.attr( "src" ) ); + } + + if ( !className ) { + element.css( "visibility", "hidden" ); + } + + return element; + }, + update: function(container, p) { + + // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that + // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified + if(className && !o.forcePlaceholderSize) { + return; + } + + //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item + if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } + if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } + } + }; + } + + //Create the placeholder + that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); + + //Append it after the actual current item + that.currentItem.after(that.placeholder); + + //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) + o.placeholder.update(that, that.placeholder); + + }, + + _contactContainers: function(event) { + var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating, + innermostContainer = null, + innermostIndex = null; + + // get innermost container that intersects with item + for (i = this.containers.length - 1; i >= 0; i--) { + + // never consider a container that's located within the item itself + if($.contains(this.currentItem[0], this.containers[i].element[0])) { + continue; + } + + if(this._intersectsWith(this.containers[i].containerCache)) { + + // if we've already found a container and it's more "inner" than this, then continue + if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { + continue; + } + + innermostContainer = this.containers[i]; + innermostIndex = i; + + } else { + // container doesn't intersect. trigger "out" event if necessary + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", event, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + // if no intersecting containers found, return + if(!innermostContainer) { + return; + } + + // move the item into the container if it's not there already + if(this.containers.length === 1) { + if (!this.containers[innermostIndex].containerCache.over) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + } else { + + //When entering a new container, we will find the item with the least distance and append our item near it + dist = 10000; + itemWithLeastDistance = null; + floating = innermostContainer.floating || isFloating(this.currentItem); + posProperty = floating ? "left" : "top"; + sizeProperty = floating ? "width" : "height"; + base = this.positionAbs[posProperty] + this.offset.click[posProperty]; + for (j = this.items.length - 1; j >= 0; j--) { + if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { + continue; + } + if(this.items[j].item[0] === this.currentItem[0]) { + continue; + } + if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) { + continue; + } + cur = this.items[j].item.offset()[posProperty]; + nearBottom = false; + if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ + nearBottom = true; + cur += this.items[j][sizeProperty]; + } + + if(Math.abs(cur - base) < dist) { + dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; + this.direction = nearBottom ? "up": "down"; + } + } + + //Check if dropOnEmpty is enabled + if(!itemWithLeastDistance && !this.options.dropOnEmpty) { + return; + } + + if(this.currentContainer === this.containers[innermostIndex]) { + return; + } + + itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + this.currentContainer = this.containers[innermostIndex]; + + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + + + }, + + _createHelper: function(event) { + + var o = this.options, + helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); + + //Add the helper to the DOM if that didn't happen already + if(!helper.parents("body").length) { + $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); + } + + if(helper[0] === this.currentItem[0]) { + this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; + } + + if(!helper[0].style.width || o.forceHelperSize) { + helper.width(this.currentItem.width()); + } + if(!helper[0].style.height || o.forceHelperSize) { + helper.height(this.currentItem.height()); + } + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj === "string") { + obj = obj.split(" "); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ("left" in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ("right" in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ("top" in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ("bottom" in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + // This needs to be actually done for all browsers, since pageX/pageY includes this information + // with an ugly IE fix + if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition === "relative") { + var p = this.currentItem.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), + top: (parseInt(this.currentItem.css("marginTop"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var ce, co, over, + o = this.options; + if(o.containment === "parent") { + o.containment = this.helper[0].parentNode; + } + if(o.containment === "document" || o.containment === "window") { + this.containment = [ + 0 - this.offset.relative.left - this.offset.parent.left, + 0 - this.offset.relative.top - this.offset.parent.top, + $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, + ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top + ]; + } + + if(!(/^(document|window|parent)$/).test(o.containment)) { + ce = $(o.containment)[0]; + co = $(o.containment).offset(); + over = ($(ce).css("overflow") !== "hidden"); + + this.containment = [ + co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, + co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, + co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, + co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top + ]; + } + + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) { + pos = this.position; + } + var mod = d === "absolute" ? 1 : -1, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, + scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + return { + top: ( + pos.top + // The absolute mouse position + this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) + ), + left: ( + pos.left + // The absolute mouse position + this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) + ) + }; + + }, + + _generatePosition: function(event) { + + var top, left, + o = this.options, + pageX = event.pageX, + pageY = event.pageY, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + // This is another very weird special case that only happens for relative elements: + // 1. If the css position is relative + // 2. and the scroll parent is the document or similar to the offset parent + // we have to refresh the relative offset during the scroll so there are no jumps + if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { + this.offset.relative = this._getRelativeOffset(); + } + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + if(this.originalPosition) { //If we are not dragging yet, we won't check for options + + if(this.containment) { + if(event.pageX - this.offset.click.left < this.containment[0]) { + pageX = this.containment[0] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top < this.containment[1]) { + pageY = this.containment[1] + this.offset.click.top; + } + if(event.pageX - this.offset.click.left > this.containment[2]) { + pageX = this.containment[2] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top > this.containment[3]) { + pageY = this.containment[3] + this.offset.click.top; + } + } + + if(o.grid) { + top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; + pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; + pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY - // The absolute mouse position + this.offset.click.top - // Click offset (relative to the element) + this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) + ), + left: ( + pageX - // The absolute mouse position + this.offset.click.left - // Click offset (relative to the element) + this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) + ) + }; + + }, + + _rearrange: function(event, i, a, hardRefresh) { + + a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); + + //Various things done here to improve the performance: + // 1. we create a setTimeout, that calls refreshPositions + // 2. on the instance, we have a counter variable, that get's higher after every append + // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same + // 4. this lets only the last addition to the timeout stack through + this.counter = this.counter ? ++this.counter : 1; + var counter = this.counter; + + this._delay(function() { + if(counter === this.counter) { + this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove + } + }); + + }, + + _clear: function(event, noPropagation) { + + this.reverting = false; + // We delay all events that have to be triggered to after the point where the placeholder has been removed and + // everything else normalized again + var i, + delayedTriggers = []; + + // We first have to update the dom position of the actual currentItem + // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) + if(!this._noFinalSort && this.currentItem.parent().length) { + this.placeholder.before(this.currentItem); + } + this._noFinalSort = null; + + if(this.helper[0] === this.currentItem[0]) { + for(i in this._storedCSS) { + if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { + this._storedCSS[i] = ""; + } + } + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + if(this.fromOutside && !noPropagation) { + delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); + } + if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { + delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed + } + + // Check if the items Container has Changed and trigger appropriate + // events. + if (this !== this.currentContainer) { + if(!noPropagation) { + delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); + delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + } + } + + + //Post events to containers + for (i = this.containers.length - 1; i >= 0; i--){ + if(!noPropagation) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + } + if(this.containers[i].containerCache.over) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + this.containers[i].containerCache.over = 0; + } + } + + //Do what was originally in plugins + if ( this.storedCursor ) { + this.document.find( "body" ).css( "cursor", this.storedCursor ); + this.storedStylesheet.remove(); + } + if(this._storedOpacity) { + this.helper.css("opacity", this._storedOpacity); + } + if(this._storedZIndex) { + this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); + } + + this.dragging = false; + if(this.cancelHelperRemoval) { + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + for (i=0; i < delayedTriggers.length; i++) { + delayedTriggers[i].call(this, event); + } //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return false; + } + + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + } + + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + + if(this.helper[0] !== this.currentItem[0]) { + this.helper.remove(); + } + this.helper = null; + + if(!noPropagation) { + for (i=0; i < delayedTriggers.length; i++) { + delayedTriggers[i].call(this, event); + } //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return true; + + }, + + _trigger: function() { + if ($.Widget.prototype._trigger.apply(this, arguments) === false) { + this.cancel(); + } + }, + + _uiHash: function(_inst) { + var inst = _inst || this; + return { + helper: inst.helper, + placeholder: inst.placeholder || $([]), + position: inst.position, + originalPosition: inst.originalPosition, + offset: inst.positionAbs, + item: inst.currentItem, + sender: _inst ? _inst.element : null + }; + } + +}); + +})(jQuery); diff --git a/preview/js/lib/jsColor_1_4_0/arrow.gif b/preview/js/lib/jsColor_1_4_0/arrow.gif new file mode 100644 index 0000000000000000000000000000000000000000..246478a864f812d93d4cd0e0d0ad1c8e2d09c825 GIT binary patch literal 66 zcmZ?wbhEHbWM|-JSjfZx1poj4Utv=LM2bII7`Ygj7<53QAbAERVV@R>f}L*~a`L;Z PKCZl?^}@uHk--`OML81Y literal 0 HcmV?d00001 diff --git a/preview/js/lib/jsColor_1_4_0/cross.gif b/preview/js/lib/jsColor_1_4_0/cross.gif new file mode 100644 index 0000000000000000000000000000000000000000..0ee9c7ac517bee40b78aea03afa809631f0b69fa GIT binary patch literal 83 zcmZ?wbhEHb + + jscolor demo + + + + + + Click here: + + + diff --git a/preview/js/lib/jsColor_1_4_0/hs.png b/preview/js/lib/jsColor_1_4_0/hs.png new file mode 100644 index 0000000000000000000000000000000000000000..3d94486cedaf7111d010fdaf2cefad45fd1f878b GIT binary patch literal 2684 zcmXArc_0(~AIBXrHY{E4t*qoJ#GWM;%`sPsQg|ZI9OX`l9COTa&5?Dua<6nb(vY&6 z6?>3k<8jPQJQ=fuv5eVo^ZVob{r>y?yxzz6n|{I3RuQNUl#q~6w6{Y!i_W*AbzM$M zv?qg)_ee+pt?W@2m!pR{B_p?CM-{APX1sq0k!v%Dx?=2xC6f&jlZz_Dn#dT_Y>InI z6zGP*0qIZBM5F|EZW=wONLxZCI;oxx8efxF<=55NtkzwRuA@S|$OSZUS~0 zbSDgzxPwEAll?BgdT}XWOAZOcMD`pUO+CGfB}rVxe|WEK0gw_Tz-cDmNG4L+O1BG& zsF@TK6o0+-x8xsR`X2n=3~i7e()sQ8nbs7jD=(px9H|qDd%c<@)UuI(EKt5ngFHjS zh|qTPjol+wD*VrOrP2AHgU&lZ7-h-S-Ry`OC^$7+KSF!aG?mu&pn3IaR(#Gf!EQP! zqeS?o6nx*@RWD5RUuQ+fDEvFrgK2PwV_AaZi_nFD)fY6UXpc#9)D{%+=WH ztBRU(0-f)NSeD3_)#+p<)n=t7Y1EDi=wXXR?Z#vSuGOPoT|XQYUp$jeW`dW9Q1kjz#P#FKJ!aH3zn{59p6;=yx%D9tmqxQwiPk>^XJ#hnQPa&UStGb;zpzVAA13RQ2C{(WQtPtlHN+xES>Qyk?T{p)3L zKdG1CZ}noWtWb#qCxunq6cmKZkSp8AJFQzC5VAmliNf$>rVM$2Q1P|$>9btrJLz|{ zSEp42us*S@mNJKs{J(s20WSGheRHFfo!iS!%VR(!BLtg%5&G73YBwnQiPTC@yb!(mV=4^=hMGc{L0KQ&GaYT41_sNl0+=?tU1#T8=w<5Hp>qu7gwyz zwLL3bXQ5-%%-%`x@Pre{8xpv$>VeI=t#(~rx$b&PV(x))i0v`~VbW)#SQBCXq2{3C z4AjaS!bRt4Q?8=ly@6M=+29nh0d(fs{K~%zNbr`+-cFUZNG!0O= ztBy>FTi+F8DTC6pr<-QLfNg0`0~OrWJ{oz=)6L|D%a{=NsYO5|-NfUp7Y1+9*lFRJ z5eJQ!*^GZ@s9{T9oBa_K8|h+p-X6RjZg2E_MT>zCjV(~r*6%7Sij>(`t)O^DQ?5kw z*5IZ}6Yl9oCX2J~K~(ouR!YtW!n|s+M;#USu3QS5lI+Z7T#@>eP@xM!TPE-?u=Htd z49Zqbfk!YI?d;mMp}J=|f0P<;(10YFMR-D=9)aeZ@G{Ezv(9sw5v++8tRHhq6NB;- zH2vx(PU1qD_8+#|>&xHm)UbkK4M?w_%wFEr_E+5T%vx9E!xmutZiFRIq28_H5VovP z_lMzL{cKdx%a2HTauX~RSRYm)64<0F_{Nbco!|_?rpPw_J}NA-umuds1iE=$J7Q`F zd!s(MKF>pom>+ITdp;<{lMnDf4uN>z#SiECQt#jM)8rIa7;kozw6%A3yMEz5P%>>m zP^aP@mh`^eyybD<|3=Fv?u;ybH7RFB;|YJx_d1J;6wJ4KGeT+E44^QNm!_X5b@{by z^qAAkM5!d@0aKxHZ8pbuQ7dm3CvtBsZV5_Hgs2II9V+0N-jO z<7fC^Ce~l|mVr*^!BrfL2MdrqJ@FI)L|?pK(7K}a393z0of$38A-e)82RKKU@XM+GiX?Y$A|=p{IK)2ld$R?8M=wUPAOfgq5a^ zjXY_~fw7l{P`h&ljSQ;F(xYQzv?H8HuM}KNO-j0-M-5?t<^wZ3n@*m2ebd~#%i(;jkY@`es^)?`9scc%tlnlUq+;7p=^6Tm`D0W}i)}TQA0bY8fVJ@Ykj4dfDYvd{ zjmUxdb}M2@x@URp28`;9B~+Ps7g`6cP9NO%S~Zm#UzG*YLiaT_OPTB(e#8W+bIJtG zHSn{{R7Ah+{aGq1u=3L$M3#B`zC!tx`wk~q^gwlhT41Cwgft#({kId%-IMMflvHxU{(d)BqpTjk&WEXSBpAD;%Rb zpPV1>?v#`9OCL@2@8?GllEy7V6uP>x>Ajs}Hm)}I8R(EtQ*KD5B(Y*OBNM)%$`!A2 zv+I7!11vu%pSE=1PZ>w~XKP*NFI`|o?X0yNT(?y(-ta%=LQ4K;p%&07YKL*_5P7rr zR9kKx+y`tt(7_svRuxC8;j)miAfSX?M|9Ladf4zeO?n&EMM2JE0Q6A5#?^TU3!~Vl zs4+YvJ!u|5x*cR;3M`2ol(60+K1QomP+|c!3SOBpo`2I4Al%NaA;URpKsgTQ`um*) z$$0jzaM_tG3$^<%+IquqoA<(IXZhE{tm>@gJUP5p{A3J6+&@8B1Ab`k#pS#XQGLsb z!I1N@6}*sK$&^j&E9UfVBsE3Cy09vRRbP@j2V8RUyS}$EzQISB8Wob;L&@gXJ0-qG zN5LYq5L0xS_C~Y5ieq)J$^evnrYxC3DF5rA^JvXg$)Z@TW4d~?c5Fm1;n8+WV9n3A zREIvTfx}B&q+1AP^S|orTG__8w)t@5vGN(1#THk!CKLHp`#)!zH7z`x^Y2abdkC%N zVpo9#I|*N>2gAEStK;2nH>2-d8UgV}Ce62}O-VSnpIx1GP7~)f>xpwmI^Gq>t9ccs zdo47kYWW=g+WUVuK=)o4jL>*SZet8NcRq)42(#4#7K&?jfMC8bH5#uho9aCC9Sc8| z&eN*3Xr9L%twp11piU_UMd7cxt!d0dy(tNJ+3&*_^j;1X9gV|DYj@pg2={0^wT+T5 z?Xw4UzW(|>jIRGsKJaB+PUaLZQP!rJ26J1VGh%$e>x-WKf|{S*YSJH@EETJjsGMoT zZ6{6eiS3#V@YL5Z@Hgy9@bEpRGI;>Z_}tQa8IhV#m14ak_?Dm8Rb(N8c>8!Bbg8nV z@Fnflx2C1NJLL_>Z|O7~CV@n+T&C@vpgqh~t(`X)qaj=ypFdgdNZsNlmAwU7;S#iO z`J>RxSlI>*l1c;-I(BztG~ID9hmZHLX}mTDprkNW`2*nM`NCrG^K)aoYd*>U5=8}T zz<8Trh(5#1==-ntBY@u7k%!+X7o>B<{Wf^*@YzUESz$bKtAufemd7|St0063A+%|% YzFqsNg>|5V=vOIWZ|#VBZRvIQe;Bk#LI3~& literal 0 HcmV?d00001 diff --git a/preview/js/lib/jsColor_1_4_0/hv.png b/preview/js/lib/jsColor_1_4_0/hv.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5e01f8bcecc4cf835e8eeeaa43ef2c06789022 GIT binary patch literal 2865 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 E0LKozl>h($ literal 0 HcmV?d00001 diff --git a/preview/js/lib/jsColor_1_4_0/jscolor.js b/preview/js/lib/jsColor_1_4_0/jscolor.js new file mode 100644 index 00000000..61f59a8c --- /dev/null +++ b/preview/js/lib/jsColor_1_4_0/jscolor.js @@ -0,0 +1,927 @@ +/** + * jscolor, JavaScript Color Picker + * + * @version 1.4.0 + * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html + * @author Jan Odvarko, http://odvarko.cz + * @created 2008-06-15 + * @updated 2012-07-06 + * @link http://jscolor.com + */ + + +var jscolor = { + + + dir : '', // location of jscolor directory (leave empty to autodetect) + bindClass : 'color', // class name + binding : true, // automatic binding via + preloading : true, // use image preloading? + + + install : function() { + if (document.readyState === "complete") { + jscolor.init(); + } else { + jscolor.addEvent(window, 'load', jscolor.init); + } + }, + + + init : function() { + if(jscolor.binding) { + jscolor.bind(); + } + if(jscolor.preloading) { + jscolor.preload(); + } + }, + + + getDir : function() { + return "/" + window.location.pathname.split("/")[1] + "/js/lib/jsColor_1_4_0/"; + }, + + + bind : function() { + var matchClass = new RegExp('(^|\\s)('+jscolor.bindClass+')\\s*(\\{[^}]*\\})?', 'i'); + var e = document.getElementsByTagName('input'); + for(var i=0; i vs[a] ? + (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : + tp[a], + -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? + (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : + (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) + ]; + } + drawPicker(pp[a], pp[b]); + } + }; + + + this.importColor = function() { + if(!valueElement) { + this.exportColor(); + } else { + if(!this.adjust) { + if(!this.fromString(valueElement.value, leaveValue)) { + styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; + styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; + styleElement.style.color = styleElement.jscStyle.color; + this.exportColor(leaveValue | leaveStyle); + } + } else if(!this.required && /^\s*$/.test(valueElement.value)) { + valueElement.value = ''; + styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; + styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; + styleElement.style.color = styleElement.jscStyle.color; + this.exportColor(leaveValue | leaveStyle); + + } else if(this.fromString(valueElement.value)) { + // OK + } else { + this.exportColor(); + } + } + }; + + + this.exportColor = function(flags) { + if(!(flags & leaveValue) && valueElement) { + var value = this.toString(); + if(this.caps) { value = value.toUpperCase(); } + if(this.hash) { value = '#'+value; } + valueElement.value = value; + } + if(!(flags & leaveStyle) && styleElement) { + styleElement.style.backgroundImage = "none"; + styleElement.style.backgroundColor = + '#'+this.toString(); + styleElement.style.color = + 0.213 * this.rgb[0] + + 0.715 * this.rgb[1] + + 0.072 * this.rgb[2] + < 0.5 ? '#FFF' : '#000'; + } + if(!(flags & leavePad) && isPickerOwner()) { + redrawPad(); + } + if(!(flags & leaveSld) && isPickerOwner()) { + redrawSld(); + } + }; + + + this.fromHSV = function(h, s, v, flags) { // null = don't change + if(h !== null) { h = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, h)); } + if(s !== null) { s = Math.max(0.0, this.minS, Math.min(1.0, this.maxS, s)); } + if(v !== null) { v = Math.max(0.0, this.minV, Math.min(1.0, this.maxV, v)); } + + this.rgb = HSV_RGB( + h===null ? this.hsv[0] : (this.hsv[0]=h), + s===null ? this.hsv[1] : (this.hsv[1]=s), + v===null ? this.hsv[2] : (this.hsv[2]=v) + ); + + this.exportColor(flags); + }; + + + this.fromRGB = function(r, g, b, flags) { // null = don't change + if(r !== null) { r = Math.max(0.0, Math.min(1.0, r)); } + if(g !== null) { g = Math.max(0.0, Math.min(1.0, g)); } + if(b !== null) { b = Math.max(0.0, Math.min(1.0, b)); } + + var hsv = RGB_HSV( + r===null ? this.rgb[0] : r, + g===null ? this.rgb[1] : g, + b===null ? this.rgb[2] : b + ); + if(hsv[0] !== null) { + this.hsv[0] = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, hsv[0])); + } + if(hsv[2] !== 0) { + this.hsv[1] = hsv[1]===null ? null : Math.max(0.0, this.minS, Math.min(1.0, this.maxS, hsv[1])); + } + this.hsv[2] = hsv[2]===null ? null : Math.max(0.0, this.minV, Math.min(1.0, this.maxV, hsv[2])); + + // update RGB according to final HSV, as some values might be trimmed + var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); + this.rgb[0] = rgb[0]; + this.rgb[1] = rgb[1]; + this.rgb[2] = rgb[2]; + + this.exportColor(flags); + }; + + + this.fromString = function(hex, flags) { + var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i); + if(!m) { + return false; + } else { + if(m[1].length === 6) { // 6-char notation + this.fromRGB( + parseInt(m[1].substr(0,2),16) / 255, + parseInt(m[1].substr(2,2),16) / 255, + parseInt(m[1].substr(4,2),16) / 255, + flags + ); + } else { // 3-char notation + this.fromRGB( + parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255, + parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255, + parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255, + flags + ); + } + return true; + } + }; + + + this.toString = function() { + return ( + (0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) + + (0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) + + (0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1) + ); + }; + + + function RGB_HSV(r, g, b) { + var n = Math.min(Math.min(r,g),b); + var v = Math.max(Math.max(r,g),b); + var m = v - n; + if(m === 0) { return [ null, 0, v ]; } + var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); + return [ h===6?0:h, m/v, v ]; + } + + + function HSV_RGB(h, s, v) { + if(h === null) { return [ v, v, v ]; } + var i = Math.floor(h); + var f = i%2 ? h-i : 1-(h-i); + var m = v * (1 - s); + var n = v * (1 - s*f); + switch(i) { + case 6: + case 0: return [v,n,m]; + case 1: return [n,v,m]; + case 2: return [m,v,n]; + case 3: return [m,n,v]; + case 4: return [n,m,v]; + case 5: return [v,m,n]; + } + } + + + function removePicker() { + delete jscolor.picker.owner; + document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB); + } + + + function drawPicker(x, y) { + if(!jscolor.picker) { + jscolor.picker = { + box : document.createElement('div'), + boxB : document.createElement('div'), + pad : document.createElement('div'), + padB : document.createElement('div'), + padM : document.createElement('div'), + sld : document.createElement('div'), + sldB : document.createElement('div'), + sldM : document.createElement('div'), + btn : document.createElement('div'), + btnS : document.createElement('span'), + btnT : document.createTextNode(THIS.pickerCloseText) + }; + for(var i=0,segSize=4; i= 0 && row >= 0 && col < this.pixels.length && row < this.pixels[0].length; + }; + + ns.Frame.prototype.saveState = function () { + // remove all states past current state + this.previousStates.length = this.stateIndex + 1; + // push new state + this.previousStates.push(this.getPixels()); + // set the stateIndex to latest saved state + this.stateIndex = this.previousStates.length - 1; + }; + + ns.Frame.prototype.loadPreviousState = function () { + if (this.stateIndex > 0) { + this.stateIndex--; + this.setPixels(this.previousStates[this.stateIndex]); + } + }; + + ns.Frame.prototype.loadNextState = function () { + if (this.stateIndex < this.previousStates.length - 1) { + this.stateIndex++; + this.setPixels(this.previousStates[this.stateIndex]); + } + }; + + ns.Frame.prototype.isSameSize = function (otherFrame) { + return this.getHeight() == otherFrame.getHeight() && this.getWidth() == otherFrame.getWidth(); + }; +})(); \ No newline at end of file diff --git a/preview/js/model/Layer.js b/preview/js/model/Layer.js new file mode 100644 index 00000000..57b5f66b --- /dev/null +++ b/preview/js/model/Layer.js @@ -0,0 +1,82 @@ +(function () { + var ns = $.namespace('pskl.model'); + + ns.Layer = function (name) { + if (!name) { + throw 'Invalid arguments in Layer constructor : \'name\' is mandatory'; + } else { + this.name = name; + this.frames = []; + } + }; + + ns.Layer.prototype.getName = function () { + return this.name; + }; + + ns.Layer.prototype.getFrames = function () { + return this.frames; + }; + + ns.Layer.prototype.getFrameAt = function (index) { + return this.frames[index]; + }; + + ns.Layer.prototype.addFrame = function (frame) { + this.frames.push(frame); + }; + + ns.Layer.prototype.addFrameAt = function (frame, index) { + this.frames.splice(index, 0, frame); + }; + + ns.Layer.prototype.removeFrame = function (frame) { + var index = this.frames.indexOf(frame); + this.removeFrameAt(index); + }; + + ns.Layer.prototype.removeFrameAt = function (index) { + if (this.frames[index]) { + this.frames.splice(index, 1); + } else { + throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.length() + ')'; + } + }; + + ns.Layer.prototype.moveFrame = function (fromIndex, toIndex) { + var frame = this.frames.splice(fromIndex, 1)[0]; + this.frames.splice(toIndex, 0, frame); + }; + + ns.Layer.prototype.swapFramesAt = function (fromIndex, toIndex) { + var fromFrame = this.frames[fromIndex]; + var toFrame = this.frames[toIndex]; + if (fromFrame && toFrame) { + this.frames[toIndex] = fromFrame; + this.frames[fromIndex] = toFrame; + } else { + console.log('frames', this.frames); + console.log('fromIndex', fromIndex, 'toIndex', toIndex); + throw 'Frame not found in moveFrameAt'; + } + }; + + ns.Layer.prototype.duplicateFrame = function (frame) { + var index = this.frames.indexOf(frame); + this.duplicateFrameAt(); + }; + + ns.Layer.prototype.duplicateFrameAt = function (index) { + var frame = this.frames[index]; + if (frame) { + var clone = frame.clone(); + this.addFrameAt(clone, index); + } else { + throw 'Frame not found in duplicateFrameAt'; + } + }; + + ns.Layer.prototype.length = function () { + return this.frames.length; + }; +})(); \ No newline at end of file diff --git a/preview/js/model/Piskel.js b/preview/js/model/Piskel.js new file mode 100644 index 00000000..7748408f --- /dev/null +++ b/preview/js/model/Piskel.js @@ -0,0 +1,81 @@ +(function () { + var ns = $.namespace('pskl.model'); + + /** + * @constructor + * @param {Number} width + * @param {Number} height + */ + ns.Piskel = function (width, height) { + if (width && height) { + /** @type {Array} */ + this.layers = []; + + /** @type {Number} */ + this.width = width; + + /** @type {Number} */ + this.height = height; + } else { + throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ","); + } + }; + + ns.Piskel.prototype.getLayers = function () { + return this.layers; + }; + + ns.Piskel.prototype.getHeight = function () { + return this.height; + }; + + ns.Piskel.prototype.getWidth = function () { + return this.width; + }; + + ns.Piskel.prototype.getLayers = function () { + return this.layers; + }; + + ns.Piskel.prototype.getLayerAt = function (index) { + return this.layers[index]; + }; + + ns.Piskel.prototype.getLayersByName = function (name) { + return this.layers.filter(function (l) { + return l.getName() == name; + }); + }; + + ns.Piskel.prototype.addLayer = function (layer) { + this.layers.push(layer); + }; + + ns.Piskel.prototype.moveLayerUp = function (layer) { + var index = this.layers.indexOf(layer); + if (index > -1 && index < this.layers.length-1) { + this.layers[index] = this.layers[index+1]; + this.layers[index+1] = layer; + } + }; + + ns.Piskel.prototype.moveLayerDown = function (layer) { + var index = this.layers.indexOf(layer); + if (index > 0) { + this.layers[index] = this.layers[index-1]; + this.layers[index-1] = layer; + } + }; + + ns.Piskel.prototype.removeLayer = function (layer) { + var index = this.layers.indexOf(layer); + if (index != -1) { + this.layers.splice(index, 1); + } + }; + + ns.Piskel.prototype.removeLayerAt = function (index) { + this.layers.splice(index, 1); + }; + +})(); \ No newline at end of file diff --git a/preview/js/rendering/CanvasRenderer.js b/preview/js/rendering/CanvasRenderer.js new file mode 100644 index 00000000..2153f207 --- /dev/null +++ b/preview/js/rendering/CanvasRenderer.js @@ -0,0 +1,47 @@ +(function () { + + var ns = $.namespace("pskl.rendering"); + ns.CanvasRenderer = function (frame, dpi) { + this.frame = frame; + this.dpi = dpi; + this.transparentColor_ = 'white'; + }; + + /** + * Decide which color should be used to represent transparent pixels + * Default : white + * @param {String} color the color to use either as '#ABCDEF' or 'red' or 'rgb(x,y,z)' or 'rgba(x,y,z,a)' + */ + ns.CanvasRenderer.prototype.drawTransparentAs = function (color) { + this.transparentColor_ = color; + }; + + ns.CanvasRenderer.prototype.render = function () { + var canvas = this.createCanvas_(); + var context = canvas.getContext('2d'); + for(var col = 0, width = this.frame.getWidth(); col < width; col++) { + for(var row = 0, height = this.frame.getHeight(); row < height; row++) { + var color = this.frame.getPixel(col, row); + this.renderPixel_(color, col, row, context); + } + } + + return context; + }; + + ns.CanvasRenderer.prototype.renderPixel_ = function (color, col, row, context) { + + if(color == Constants.TRANSPARENT_COLOR) { + color = this.transparentColor_; + } + context.fillStyle = color; + + context.fillRect(col * this.dpi, row * this.dpi, this.dpi, this.dpi); + }; + + ns.CanvasRenderer.prototype.createCanvas_ = function () { + var width = this.frame.getWidth() * this.dpi; + var height = this.frame.getHeight() * this.dpi; + return pskl.CanvasUtils.createCanvas(width, height); + }; +})(); \ No newline at end of file diff --git a/preview/js/rendering/DrawingLoop.js b/preview/js/rendering/DrawingLoop.js new file mode 100644 index 00000000..e1816011 --- /dev/null +++ b/preview/js/rendering/DrawingLoop.js @@ -0,0 +1,61 @@ +(function () { + var ns = $.namespace("pskl.rendering"); + + ns.DrawingLoop = function () { + this.requestAnimationFrame = this.getRequestAnimationFrameShim_(); + this.isRunning = false; + this.previousTime = 0; + this.callbacks = []; + }; + + ns.DrawingLoop.prototype.addCallback = function (callback, scope, args) { + var callbackObj = { + fn : callback, + scope : scope, + args : args + }; + this.callbacks.push(callbackObj); + return callbackObj; + }; + + ns.DrawingLoop.prototype.removeCallback = function (callbackObj) { + var index = this.callbacks.indexOf(callbackObj); + if (index != -1) { + this.callbacks.splice(index, 1); + } + }; + + ns.DrawingLoop.prototype.start = function () { + this.isRunning = true; + this.loop_(); + }; + + ns.DrawingLoop.prototype.loop_ = function () { + var currentTime = Date.now(); + var delta = currentTime - this.previousTime; + this.executeCallbacks_(delta); + this.previousTime = currentTime; + this.requestAnimationFrame.call(window, this.loop_.bind(this)); + }; + + ns.DrawingLoop.prototype.executeCallbacks_ = function (deltaTime) { + for (var i = 0 ; i < this.callbacks.length ; i++) { + var cb = this.callbacks[i]; + cb.fn.call(cb.scope, deltaTime, cb.args); + } + }; + + ns.DrawingLoop.prototype.stop = function () { + this.isRunning = false; + }; + + ns.DrawingLoop.prototype.getRequestAnimationFrameShim_ = function () { + var requestAnimationFrame = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { window.setTimeout(callback, 1000/60); }; + + return requestAnimationFrame; + }; +})(); \ No newline at end of file diff --git a/preview/js/rendering/FrameRenderer.js b/preview/js/rendering/FrameRenderer.js new file mode 100644 index 00000000..2747d6fa --- /dev/null +++ b/preview/js/rendering/FrameRenderer.js @@ -0,0 +1,145 @@ +(function () { + var ns = $.namespace("pskl.rendering"); + + /** + * FrameRenderer will display a given frame inside a canvas element. + * @param {HtmlElement} container HtmlElement to use as parentNode of the Frame + * @param {Object} renderingOptions + * @param {Array} classes array of strings to use for css classes + */ + ns.FrameRenderer = function (container, renderingOptions, classes) { + this.defaultRenderingOptions = { + 'supportGridRendering' : false + }; + renderingOptions = $.extend(true, {}, this.defaultRenderingOptions, renderingOptions); + + if(container === undefined) { + throw 'Bad FrameRenderer initialization. undefined.'; + } + + if(isNaN(renderingOptions.dpi)) { + throw 'Bad FrameRenderer initialization. not well defined.'; + } + + this.container = container; + + this.dpi = renderingOptions.dpi; + this.supportGridRendering = renderingOptions.supportGridRendering; + + this.classes = classes || []; + this.classes.push('canvas'); + + this.canvas = null; + + this.enableGrid(pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)); + + // Flag to know if the config was altered + this.canvasConfigDirty = true; + this.updateBackgroundClass_(pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND)); + $.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); + }; + + ns.FrameRenderer.prototype.setDPI = function (dpi) { + this.dpi = dpi; + this.canvasConfigDirty = true; + }; + + /** + * @private + */ + ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) { + + if(settingName == pskl.UserSettings.SHOW_GRID) { + this.enableGrid(settingValue); + } + else if (settingName == pskl.UserSettings.CANVAS_BACKGROUND) { + this.updateBackgroundClass_(settingValue); + } + }; + + /** + * @private + */ + ns.FrameRenderer.prototype.updateBackgroundClass_ = function (newClass) { + var currentClass = this.container.data('current-background-class'); + if (currentClass) { + this.container.removeClass(currentClass); + } + this.container.addClass(newClass); + this.container.data('current-background-class', newClass); + }; + + ns.FrameRenderer.prototype.enableGrid = function (flag) { + this.gridStrokeWidth = (flag && this.supportGridRendering) ? Constants.GRID_STROKE_WIDTH : 0; + this.canvasConfigDirty = true; + }; + + ns.FrameRenderer.prototype.render = function (frame) { + if (frame) { + this.clear(); + var context = this.getCanvas_(frame).getContext('2d'); + for(var col = 0, width = frame.getWidth(); col < width; col++) { + for(var row = 0, height = frame.getHeight(); row < height; row++) { + var color = frame.getPixel(col, row); + this.renderPixel_(color, col, row, context); + } + } + this.lastRenderedFrame = frame; + } + }; + + ns.FrameRenderer.prototype.renderPixel_ = function (color, col, row, context) { + if(color != Constants.TRANSPARENT_COLOR) { + context.fillStyle = color; + context.fillRect(this.getFramePos_(col), this.getFramePos_(row), this.dpi, this.dpi); + } + }; + + ns.FrameRenderer.prototype.clear = function () { + if (this.canvas) { + this.canvas.getContext("2d").clearRect(0, 0, this.canvas.width, this.canvas.height); + } + }; + + /** + * Transform a screen pixel-based coordinate (relative to the top-left corner of the rendered + * frame) into a sprite coordinate in column and row. + * @public + */ + ns.FrameRenderer.prototype.convertPixelCoordinatesIntoSpriteCoordinate = function(coords) { + var cellSize = this.dpi + this.gridStrokeWidth; + return { + "col" : (coords.x - coords.x % cellSize) / cellSize, + "row" : (coords.y - coords.y % cellSize) / cellSize + }; + }; + + /** + * @private + */ + ns.FrameRenderer.prototype.getFramePos_ = function(index) { + return index * this.dpi + ((index - 1) * this.gridStrokeWidth); + }; + + /** + * @private + */ + ns.FrameRenderer.prototype.getCanvas_ = function (frame) { + if(this.canvasConfigDirty) { + $(this.canvas).remove(); + + var col = frame.getWidth(), + row = frame.getHeight(); + + var pixelWidth = col * this.dpi + this.gridStrokeWidth * (col - 1); + var pixelHeight = row * this.dpi + this.gridStrokeWidth * (row - 1); + + var canvas = pskl.CanvasUtils.createCanvas(pixelWidth, pixelHeight, this.classes); + this.container.append(canvas); + + this.canvas = canvas; + this.canvasConfigDirty = false; + } + return this.canvas; + }; +})(); \ No newline at end of file diff --git a/preview/js/rendering/SpritesheetRenderer.js b/preview/js/rendering/SpritesheetRenderer.js new file mode 100644 index 00000000..d634da0f --- /dev/null +++ b/preview/js/rendering/SpritesheetRenderer.js @@ -0,0 +1,44 @@ +(function () { + + var ns = $.namespace("pskl.rendering"); + + ns.SpritesheetRenderer = function (piskelController) { + this.piskelController = piskelController; + }; + + ns.SpritesheetRenderer.prototype.render = function () { + var canvas = this.createCanvas_(); + for (var i = 0 ; i < this.piskelController.getFrameCount() ; i++) { + var frame = this.piskelController.getFrameAt(i); + this.drawFrameInCanvas_(frame, canvas, i * this.piskelController.getWidth(), 0); + } + return canvas; + }; + + /** + * TODO(juliandescottes): Mutualize with code already present in FrameRenderer + */ + ns.SpritesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) { + var context = canvas.getContext('2d'); + for(var col = 0, width = frame.getWidth(); col < width; col++) { + for(var row = 0, height = frame.getHeight(); row < height; row++) { + var color = frame.getPixel(col, row); + if(color != Constants.TRANSPARENT_COLOR) { + context.fillStyle = color; + context.fillRect(col + offsetWidth, row + offsetHeight, 1, 1); + } + } + } + }; + + ns.SpritesheetRenderer.prototype.createCanvas_ = function () { + var frameCount = this.piskelController.getFrameCount(); + if (frameCount > 0){ + var width = frameCount * this.piskelController.getWidth(); + var height = this.piskelController.getHeight(); + return pskl.CanvasUtils.createCanvas(width, height); + } else { + throw "Cannot render empty Spritesheet"; + } + }; +})(); \ No newline at end of file diff --git a/preview/js/selection/BaseSelection.js b/preview/js/selection/BaseSelection.js new file mode 100644 index 00000000..74513b58 --- /dev/null +++ b/preview/js/selection/BaseSelection.js @@ -0,0 +1,34 @@ +(function () { + var ns = $.namespace("pskl.selection"); + + ns.BaseSelection = function () { + this.reset(); + }; + + ns.BaseSelection.prototype.reset = function () { + this.pixels = []; + this.hasPastedContent = false; + }; + + ns.BaseSelection.prototype.move = function (colDiff, rowDiff) { + var movedPixel, movedPixels = []; + + for(var i=0, l=this.pixels.length; i"; + } + this.piskelController = piskelController; + this.localStorageThrottler_ = null; + }; + + /** + * @public + */ + ns.LocalStorageService.prototype.init = function(piskelController) { + $.subscribe(Events.LOCALSTORAGE_REQUEST, $.proxy(this.persistToLocalStorageRequest_, this)); + }; + + /** + * @private + */ + ns.LocalStorageService.prototype.persistToLocalStorageRequest_ = function () { + // Persist to localStorage when drawing. We throttle localStorage accesses + // for high frequency drawing (eg mousemove). + if(this.localStorageThrottler_ !== null) { + window.clearTimeout(this.localStorageThrottler_); + } + this.localStorageThrottler_ = window.setTimeout($.proxy(function() { + this.persistToLocalStorage_(); + this.localStorageThrottler_ = null; + }, this), 1000); + }; + + /** + * @private + */ + ns.LocalStorageService.prototype.persistToLocalStorage_ = function() { + + console.log('[LocalStorage service]: Snapshot stored'); + window.localStorage.snapShot = this.piskelController.serialize(); + }; + + /** + * @private + */ + ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() { + + this.piskelController.deserialize(window.localStorage.snapShot); + this.piskelController.setCurrentFrameIndex(0); + }; + + /** + * @private + */ + ns.LocalStorageService.prototype.cleanLocalStorage_ = function() { + console.log('[LocalStorage service]: Snapshot removed'); + delete window.localStorage.snapShot; + }; + + /** + * @public + */ + ns.LocalStorageService.prototype.displayRestoreNotification = function() { + if(window.localStorage && window.localStorage.snapShot) { + var reloadLink = "reload"; + var discardLink = "discard"; + var content = "Non saved version found. " + reloadLink + " or " + discardLink; + + $.publish(Events.SHOW_NOTIFICATION, [{ + "content": content, + "behavior": $.proxy(function(rootNode) { + rootNode = $(rootNode); + rootNode.click($.proxy(function(evt) { + var target = $(evt.target); + if(target.hasClass("localstorage-restore")) { + this.restoreFromLocalStorage_(); + } + else if (target.hasClass("localstorage-discard")) { + this.cleanLocalStorage_(); + } + $.publish(Events.HIDE_NOTIFICATION); + }, this)); + }, this) + }]); + } + }; +})(); \ No newline at end of file diff --git a/preview/js/utils/CanvasUtils.js b/preview/js/utils/CanvasUtils.js new file mode 100644 index 00000000..dd03612b --- /dev/null +++ b/preview/js/utils/CanvasUtils.js @@ -0,0 +1,22 @@ +(function () { + var ns = $.namespace("pskl"); + + ns.CanvasUtils = { + createCanvas : function (width, height, classList) { + var canvas = document.createElement("canvas"); + canvas.setAttribute("width", width); + canvas.setAttribute("height", height); + + if (typeof classList == "string") { + classList = [classList]; + } + if (Array.isArray(classList)) { + for (var i = 0 ; i < classList.length ; i++) { + canvas.classList.add(classList[i]); + } + } + + return canvas; + } + }; +})(); \ No newline at end of file diff --git a/preview/js/utils/FileUtils.js b/preview/js/utils/FileUtils.js new file mode 100644 index 00000000..74f25892 --- /dev/null +++ b/preview/js/utils/FileUtils.js @@ -0,0 +1,13 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + ns.FileUtils = { + readFile : function (file, callback) { + var reader = new FileReader(); + reader.onload = function(event){ + callback(event.target.result); + }; + reader.readAsDataURL(file); + } + }; +})(); \ No newline at end of file diff --git a/preview/js/utils/FrameUtils.js b/preview/js/utils/FrameUtils.js new file mode 100644 index 00000000..ef14f935 --- /dev/null +++ b/preview/js/utils/FrameUtils.js @@ -0,0 +1,81 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + ns.FrameUtils = { + merge : function (frames) { + var merged = null; + if (frames.length) { + merged = frames[0].clone(); + var w = merged.getWidth(), h = merged.getHeight(); + for (var i = 1 ; i < frames.length ; i++) { + pskl.utils.FrameUtils.mergeFrames_(merged, frames[i]); + } + } + return merged; + }, + + mergeFrames_ : function (frameA, frameB) { + frameB.forEachPixel(function (p, col, row) { + if (p != Constants.TRANSPARENT_COLOR) { + frameA.setPixel(col, row, p); + } + }); + }, + + /** + * Create a pskl.model.Frame from an Image object. + * Transparent pixels will either be converted to completely opaque or completely transparent pixels. + * @param {Image} image source image + * @return {pskl.model.Frame} corresponding frame + */ + createFromImage : function (image) { + var w = image.width, + h = image.height; + var canvas = pskl.CanvasUtils.createCanvas(w, h); + var context = canvas.getContext('2d'); + + context.drawImage(image, 0,0,w,h,0,0,w,h); + var imgData = context.getImageData(0,0,w,h).data; + // Draw the zoomed-up pixels to a different canvas context + var frame = []; + for (var x=0;x top left corner - {x1, y1} => bottom right corner + * @private + */ + getOrderedRectangleCoordinates : function (x0, y0, x1, y1) { + return { + x0 : Math.min(x0, x1), + y0 : Math.min(y0, y1), + x1 : Math.max(x0, x1), + y1 : Math.max(y0, y1) + }; + }, + + /** + * Return the list of pixels that would have been filled by a paintbucket tool applied + * on pixel at coordinate (x,y). + * This function is not altering the Frame object argument. + * + * @param frame pskl.model.Frame The frame target in which we want to paintbucket + * @param col number Column coordinate in the frame + * @param row number Row coordinate in the frame + * + * @return an array of the pixel coordinates paint with the replacement color + */ + getSimilarConnectedPixelsFromFrame: function(frame, col, row) { + // To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm + // in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels + // and are as well connected. + var fakeFrame = frame.clone(); // We just want to + var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color. + var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor); + + return paintedPixels; + }, + + /** + * Apply the paintbucket tool in a frame at the (col, row) initial position + * with the replacement color. + * + * @param frame pskl.model.Frame The frame target in which we want to paintbucket + * @param col number Column coordinate in the frame + * @param row number Row coordinate in the frame + * @param replacementColor string Hexadecimal color used to fill the area + * + * @return an array of the pixel coordinates paint with the replacement color + */ + paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) { + /** + * Queue linear Flood-fill (node, target-color, replacement-color): + * 1. Set Q to the empty queue. + * 2. If the color of node is not equal to target-color, return. + * 3. Add node to Q. + * 4. For each element n of Q: + * 5. If the color of n is equal to target-color: + * 6. Set w and e equal to n. + * 7. Move w to the west until the color of the node to the west of w no longer matches target-color. + * 8. Move e to the east until the color of the node to the east of e no longer matches target-color. + * 9. Set the color of nodes between w and e to replacement-color. + * 10. For each node n between w and e: + * 11. If the color of the node to the north of n is target-color, add that node to Q. + * 12. If the color of the node to the south of n is target-color, add that node to Q. + * 13. Continue looping until Q is exhausted. + * 14. Return. + */ + var paintedPixels = []; + var queue = []; + var dy = [-1, 0, 1, 0]; + var dx = [0, 1, 0, -1]; + var targetColor; + try { + targetColor = frame.getPixel(col, row); + } catch(e) { + // Frame out of bound exception. + } + + if(targetColor == replacementColor) { + return; + } + + + queue.push({"col": col, "row": row}); + var loopCount = 0; + var cellCount = frame.getWidth() * frame.getHeight(); + while(queue.length > 0) { + loopCount ++; + + var currentItem = queue.pop(); + frame.setPixel(currentItem.col, currentItem.row, replacementColor); + paintedPixels.push({"col": currentItem.col, "row": currentItem.row }); + + for (var i = 0; i < 4; i++) { + var nextCol = currentItem.col + dx[i]; + var nextRow = currentItem.row + dy[i]; + try { + if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) { + queue.push({"col": nextCol, "row": nextRow }); + } + } catch(e) { + // Frame out of bound exception. + } + } + + // Security loop breaker: + if(loopCount > 10 * cellCount) { + console.log("loop breaker called"); + break; + } + } + return paintedPixels; + }, + + /** + * Calculate and return the maximal DPI to display a picture in a given container. + * + * @param container jQueryObject Container where the picture should be displayed + * @param number pictureHeight height in pixels of the picture to display + * @param number pictureWidth width in pixels of the picture to display + * @return number maximal dpi + */ + calculateDPIForContainer : function (container, pictureHeight, pictureWidth) { + return this.calculateDPI(container.height(), container.width(), pictureHeight, pictureWidth); + }, + + /** + * Calculate and return the maximal DPI to display a picture for a given height and width. + * + * @param height number Height available to display the picture + * @param width number Width available to display the picture + * @param number pictureHeight height in pixels of the picture to display + * @param number pictureWidth width in pixels of the picture to display + * @return number maximal dpi + */ + calculateDPI : function (height, width, pictureHeight, pictureWidth) { + var heightBoundDpi = Math.floor(height / pictureHeight), + widthBoundDpi = Math.floor(width / pictureWidth); + + return Math.min(heightBoundDpi, widthBoundDpi); + } + }; +})(); \ No newline at end of file diff --git a/preview/js/utils/Serializer.js b/preview/js/utils/Serializer.js new file mode 100644 index 00000000..0006acab --- /dev/null +++ b/preview/js/utils/Serializer.js @@ -0,0 +1,87 @@ +(function () { + var ns = $.namespace('pskl.utils'); + ns.Serializer = { + serializePiskel : function (piskel) { + var serializedLayers = piskel.getLayers().map(function (l) { + return pskl.utils.Serializer.serializeLayer(l); + }); + return JSON.stringify({ + modelVersion : Constants.MODEL_VERSION, + piskel : { + height : piskel.getHeight(), + width : piskel.getWidth(), + layers : serializedLayers + } + }); + }, + + serializeLayer : function (layer) { + var serializedFrames = layer.getFrames().map(function (f) { + return f.serialize(); + }); + return JSON.stringify({ + name : layer.getName(), + frames : serializedFrames + }); + }, + + deserializePiskel : function (piskelString) { + var piskelData = JSON.parse(piskelString); + return this.createPiskelFromData(piskelData); + }, + + /** + * Similar to deserializePiskel, but dealing directly with a parsed piskel + * @param {Object} piskelData JSON.parse of a serialized piskel + * @return {pskl.model.Piskel} a piskel + */ + createPiskel : function (piskelData) { + var piskel = null; + if (piskelData.modelVersion == Constants.MODEL_VERSION) { + var pData = piskelData.piskel; + piskel = new pskl.model.Piskel(pData.width, pData.height); + + pData.layers.forEach(function (serializedLayer) { + var layer = pskl.utils.Serializer.deserializeLayer(serializedLayer); + piskel.addLayer(layer); + }); + } else { + piskel = pskl.utils.Serializer.backwardDeserializer_(piskelData); + } + + return piskel; + }, + + deserializeLayer : function (layerString) { + var lData = JSON.parse(layerString); + var layer = new pskl.model.Layer(lData.name); + + lData.frames.forEach(function (serializedFrame) { + var frame = pskl.utils.Serializer.deserializeFrame(serializedFrame); + layer.addFrame(frame); + }); + + return layer; + }, + + deserializeFrame : function (frameString) { + var framePixelGrid = JSON.parse(frameString); + return pskl.model.Frame.fromPixelGrid(framePixelGrid); + }, + + /** + * Deserialize old piskel framesheets. Initially piskels were stored as arrays of frames : "[[pixelGrid],[pixelGrid],[pixelGrid]]". + */ + backwardDeserializer_ : function (frames) { + var layer = new pskl.model.Layer('Layer 1'); + frames.forEach(function (frame) { + layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); + }); + var width = layer.getFrameAt(0).getWidth(), height = layer.getFrameAt(0).getHeight(); + var piskel = new pskl.model.Piskel(width, height); + piskel.addLayer(layer); + + return piskel; + } + }; +})(); diff --git a/preview/js/utils/Template.js b/preview/js/utils/Template.js new file mode 100644 index 00000000..331ed824 --- /dev/null +++ b/preview/js/utils/Template.js @@ -0,0 +1,30 @@ +(function () { + var ns = $.namespace("pskl.utils"); + + ns.Template = { + get : function (templateId) { + var template = document.getElementById(templateId); + if (template) { + return template.innerHTML; + } else { + console.error("Could not find template for id :", templateId); + } + }, + + createFromHTML : function (html) { + var dummyEl = document.createElement("div"); + dummyEl.innerHTML = html; + return dummyEl.children[0]; + }, + + replace : function (template, dict) { + for (var key in dict) { + if (dict.hasOwnProperty(key)) { + var value = dict[key]; + template = template.replace(new RegExp('\\{\\{'+key+'\\}\\}', 'g'), value); + } + } + return template; + } + }; +})(); \ No newline at end of file diff --git a/preview/js/utils/UserSettings.js b/preview/js/utils/UserSettings.js new file mode 100644 index 00000000..79c264cb --- /dev/null +++ b/preview/js/utils/UserSettings.js @@ -0,0 +1,76 @@ +(function () { + var ns = $.namespace("pskl"); + + ns.UserSettings = { + + SHOW_GRID : 'SHOW_GRID', + CANVAS_BACKGROUND : 'CANVAS_BACKGROUND', + + KEY_TO_DEFAULT_VALUE_MAP_ : { + 'SHOW_GRID' : false, + 'CANVAS_BACKGROUND' : 'medium-canvas-background' + }, + + /** + * @private + */ + cache_ : {}, + + /** + * Static method to access a user defined settings value ot its default + * value if not defined yet. + */ + get : function (key) { + this.checkKeyValidity_(key); + if (!(key in this.cache_)) { + this.cache_[key] = + this.readFromLocalStorage_(key) || this.readFromDefaults_(key); + } + return this.cache_[key]; + }, + + set : function (key, value) { + this.checkKeyValidity_(key); + this.cache_[key] = value; + this.writeToLocalStorage_(key, value); + + $.publish(Events.USER_SETTINGS_CHANGED, [key, value]); + }, + + /** + * @private + */ + readFromLocalStorage_ : function(key) { + var value = window.localStorage[key]; + if (typeof value != "undefined") { + value = JSON.parse(value); + } + return value; + }, + + /** + * @private + */ + writeToLocalStorage_ : function(key, value) { + // TODO(grosbouddha): Catch storage exception here. + window.localStorage[key] = JSON.stringify(value); + }, + + /** + * @private + */ + readFromDefaults_ : function (key) { + return this.KEY_TO_DEFAULT_VALUE_MAP_[key]; + }, + + /** + * @private + */ + checkKeyValidity_ : function(key) { + if(!(key in this.KEY_TO_DEFAULT_VALUE_MAP_)) { + // TODO(grosbouddha): Define error catching strategy and throw exception from here. + console.log("UserSettings key <"+ key +"> not find in supported keys."); + } + } + }; +})(); \ No newline at end of file diff --git a/preview/js/utils/core.js b/preview/js/utils/core.js new file mode 100644 index 00000000..88795c35 --- /dev/null +++ b/preview/js/utils/core.js @@ -0,0 +1,51 @@ +jQuery.namespace = function() { + var a=arguments, o=null, i, j, d; + for (i=0; i 255 || g > 255 || b > 255) { + throw "Invalid color component"; + } + + return ((r << 16) | (g << 8) | b).toString(16); + }; + + ns.inherit = function(extendedObject, inheritFrom) { + extendedObject.prototype = Object.create(inheritFrom.prototype); + extendedObject.prototype.constructor = extendedObject; + extendedObject.prototype.superclass = inheritFrom.prototype; + }; + +})(); + diff --git a/preview/piskel-boot.js b/preview/piskel-boot.js new file mode 100644 index 00000000..1c8da006 --- /dev/null +++ b/preview/piskel-boot.js @@ -0,0 +1,42 @@ +(function () { + + var loadScript = function (src, callback) { + var script = window.document.createElement('script'); + script.setAttribute('src',src); + script.setAttribute('onload',callback); + window.document.body.appendChild(script); + }; + + if (window.location.href.indexOf("debug") != -1) { + window.exports = {}; + var scriptIndex = 0; + window.loadNextScript = function () { + if (scriptIndex == window.exports.scripts.length) { + pskl.app.init(); + // cleanup + delete window.exports; + delete window.loadDebugScripts; + delete window.done; + } else { + loadScript(window.exports.scripts[scriptIndex], "loadNextScript()"); + scriptIndex ++; + } + }; + loadScript("piskel-script-list.js", "loadNextScript()"); + } else { + var script; + if (window.location.href.indexOf("pack") != -1) { + script = "build/piskel-packaged.js"; + } else { + script = "build/piskel-packaged-min.js"; + } + var loaderInterval = window.setInterval(function () { + if (document.querySelectorAll("._ctl").length === 0) { + window.clearInterval(loaderInterval); + loadScript(script, "pskl.app.init()"); + } else { + console.log("waiting for templates to load ...."); + } + }, 100); + } +})(); \ No newline at end of file diff --git a/preview/piskel-closure-externs.js b/preview/piskel-closure-externs.js new file mode 100644 index 00000000..b5f3d789 --- /dev/null +++ b/preview/piskel-closure-externs.js @@ -0,0 +1,17 @@ +/** + * @fileoverview Externs for Piskel + * + * @externs + */ + +// Piskel externs. +var exports; +var $; +var console; +var pskl; + +// Piskel libs externs. +var define; +var jQuery; +var getComputedStyle; +var URL; \ No newline at end of file diff --git a/preview/piskel-script-list.js b/preview/piskel-script-list.js new file mode 100644 index 00000000..deeb069e --- /dev/null +++ b/preview/piskel-script-list.js @@ -0,0 +1,85 @@ +// This list is used both by the grunt build and index.html (in debug mode) + +exports.scripts = [ + // Core libraries + "js/lib/jquery-1.8.0.js","js/lib/jquery-ui-1.10.3.custom.js","js/lib/pubsub.js","js/lib/bootstrap/bootstrap.js", + // GIF Encoding libraries + "js/lib/gif/gif.worker.js", + "js/lib/gif/gif.js", + + // Application wide configuration + "js/Constants.js", + "js/Events.js", + + // Libraries + "js/utils/core.js", + "js/utils/CanvasUtils.js", + "js/utils/FileUtils.js", + "js/utils/FrameUtils.js", + "js/utils/ImageResizer.js", + "js/utils/PixelUtils.js", + "js/utils/Serializer.js", + "js/utils/Template.js", + "js/utils/UserSettings.js", + "js/lib/jsColor_1_4_0/jscolor.js", + + // Application libraries--> + "js/rendering/DrawingLoop.js", + + // Models + "js/model/Frame.js", + "js/model/Layer.js", + "js/model/Piskel.js", + + // Selection + "js/selection/SelectionManager.js", + "js/selection/BaseSelection.js", + "js/selection/RectangularSelection.js", + "js/selection/ShapeSelection.js", + + // Rendering + "js/rendering/CanvasRenderer.js", + "js/rendering/FrameRenderer.js", + "js/rendering/SpritesheetRenderer.js", + + // Controllers + "js/controller/PiskelController.js", + "js/controller/DrawingController.js", + "js/controller/PreviewFilmController.js", + "js/controller/LayersListController.js", + "js/controller/AnimatedPreviewController.js", + "js/controller/ToolController.js", + "js/controller/PaletteController.js", + "js/controller/NotificationController.js", + + // Settings sub-controllers + "js/controller/settings/ApplicationSettingsController.js", + "js/controller/settings/GifExportController.js", + "js/controller/settings/ImportController.js", + // Settings controller + "js/controller/settings/SettingsController.js", + + // Services + "js/service/LocalStorageService.js", + "js/service/HistoryService.js", + "js/service/KeyboardEventService.js", + "js/service/ImageUploadService.js", + + // Tools + "js/drawingtools/BaseTool.js", + "js/drawingtools/SimplePen.js", + "js/drawingtools/VerticalMirrorPen.js", + "js/drawingtools/Eraser.js", + "js/drawingtools/Stroke.js", + "js/drawingtools/PaintBucket.js", + "js/drawingtools/Rectangle.js", + "js/drawingtools/Circle.js", + "js/drawingtools/Move.js", + "js/drawingtools/selectiontools/BaseSelect.js", + "js/drawingtools/selectiontools/RectangleSelect.js", + "js/drawingtools/selectiontools/ShapeSelect.js", + "js/drawingtools/ColorPicker.js", + + // Application controller and initialization + "js/app.js" +]; \ No newline at end of file diff --git a/preview/templates/drawing-tools.html b/preview/templates/drawing-tools.html new file mode 100644 index 00000000..b1050445 --- /dev/null +++ b/preview/templates/drawing-tools.html @@ -0,0 +1,18 @@ +
    +
    +
    +
      +
      +
      + + +
      +
      +
      + +
      +
      +
      +
      +
      +
      \ No newline at end of file diff --git a/preview/templates/frames-list.html b/preview/templates/frames-list.html new file mode 100644 index 00000000..5312ecfd --- /dev/null +++ b/preview/templates/frames-list.html @@ -0,0 +1,7 @@ +
      +
      +
        +
        +
        +
        +
        \ No newline at end of file diff --git a/preview/templates/layers-list.html b/preview/templates/layers-list.html new file mode 100644 index 00000000..f9ccfec1 --- /dev/null +++ b/preview/templates/layers-list.html @@ -0,0 +1,13 @@ +
        +

        Layers

        +
        + + + + +
        + +
          +
          diff --git a/preview/templates/preview.html b/preview/templates/preview.html new file mode 100644 index 00000000..77851ee9 --- /dev/null +++ b/preview/templates/preview.html @@ -0,0 +1,9 @@ +
          +
          +
          +
          +
          + + +
          +
          \ No newline at end of file diff --git a/preview/templates/settings.html b/preview/templates/settings.html new file mode 100644 index 00000000..efb455eb --- /dev/null +++ b/preview/templates/settings.html @@ -0,0 +1,45 @@ +
          +
          +
          + +
          +
          + + + +
          +
          + +
          + GIF +
          + +
          + PNG +
          +
          \ No newline at end of file diff --git a/preview/templates/settings/application.html b/preview/templates/settings/application.html new file mode 100644 index 00000000..b65def23 --- /dev/null +++ b/preview/templates/settings/application.html @@ -0,0 +1,26 @@ +
          +
          + Canvas settings: +
          +
          + +
          +
          +
          +
          +
          +
          +
          +
          +
          +
          +
          +
          + +
          +
          + diff --git a/preview/templates/settings/export-gif.html b/preview/templates/settings/export-gif.html new file mode 100644 index 00000000..95e1587d --- /dev/null +++ b/preview/templates/settings/export-gif.html @@ -0,0 +1,18 @@ +
          +
          + Export to Animated GIF +
          +
          + +
          + +
          + + + +
          +
          +
          \ No newline at end of file diff --git a/preview/templates/settings/import.html b/preview/templates/settings/import.html new file mode 100644 index 00000000..405cd092 --- /dev/null +++ b/preview/templates/settings/import.html @@ -0,0 +1,31 @@ +
          +
          + Import Picture +
          +
          +
          +
          + File : + + + +
          +
          + Preview : +
          +
          +
          + Size : + x + +
          +
          + Smooth resize : + +
          + + +
          +
          \ No newline at end of file