diff --git a/wagtail/wagtailimages/admin_urls.py b/wagtail/wagtailimages/admin_urls.py
index 44bc61224..1d04bcbfe 100644
--- a/wagtail/wagtailimages/admin_urls.py
+++ b/wagtail/wagtailimages/admin_urls.py
@@ -1,6 +1,6 @@
from django.conf.urls import url
-from wagtail.wagtailimages.views import images, chooser, multiple
+from wagtail.wagtailimages.views import images, chooser, multiple, focal_point_chooser
urlpatterns = [
@@ -20,4 +20,6 @@ urlpatterns = [
url(r'^chooser/(\d+)/$', chooser.image_chosen, name='wagtailimages_image_chosen'),
url(r'^chooser/upload/$', chooser.chooser_upload, name='wagtailimages_chooser_upload'),
url(r'^chooser/(\d+)/select_format/$', chooser.chooser_select_format, name='wagtailimages_chooser_select_format'),
+
+ url(r'focal_point_chooser/(\d+)/$', focal_point_chooser.chooser, name='wagtailimages_focal_point_chooser')
]
diff --git a/wagtail/wagtailimages/forms.py b/wagtail/wagtailimages/forms.py
index 5f98fd1b0..af3b3ce83 100644
--- a/wagtail/wagtailimages/forms.py
+++ b/wagtail/wagtailimages/forms.py
@@ -12,12 +12,23 @@ def get_image_form():
# set the 'file' widget to a FileInput rather than the default ClearableFileInput
# so that when editing, we don't get the 'currently: ...' banner which is
# a bit pointless here
- widgets={'file': forms.FileInput()})
+ widgets={
+ 'file': forms.FileInput(),
+ 'focal_point_x': forms.HiddenInput(attrs={'class': 'focal_point_x'}),
+ 'focal_point_y': forms.HiddenInput(attrs={'class': 'focal_point_y'}),
+ 'focal_point_width': forms.HiddenInput(attrs={'class': 'focal_point_width'}),
+ 'focal_point_height': forms.HiddenInput(attrs={'class': 'focal_point_height'}),
+ })
def get_image_form_for_multi():
# exclude the file widget
- return modelform_factory(get_image_model(), exclude=('file',))
+ return modelform_factory(get_image_model(), exclude=('file',), widgets={
+ 'focal_point_x': forms.HiddenInput(attrs={'class': 'focal_point_x'}),
+ 'focal_point_y': forms.HiddenInput(attrs={'class': 'focal_point_y'}),
+ 'focal_point_width': forms.HiddenInput(attrs={'class': 'focal_point_width'}),
+ 'focal_point_height': forms.HiddenInput(attrs={'class': 'focal_point_height'}),
+ })
class ImageInsertionForm(forms.Form):
diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py
index 404cc7210..abd7c06a7 100644
--- a/wagtail/wagtailimages/models.py
+++ b/wagtail/wagtailimages/models.py
@@ -54,10 +54,10 @@ class AbstractImage(models.Model, TagSearchable):
tags = TaggableManager(help_text=None, blank=True, verbose_name=_('Tags'))
- focal_point_x = models.PositiveIntegerField(null=True, editable=False)
- focal_point_y = models.PositiveIntegerField(null=True, editable=False)
- focal_point_width = models.PositiveIntegerField(null=True, editable=False)
- focal_point_height = models.PositiveIntegerField(null=True, editable=False)
+ focal_point_x = models.PositiveIntegerField(null=True, blank=True)
+ focal_point_y = models.PositiveIntegerField(null=True, blank=True)
+ focal_point_width = models.PositiveIntegerField(null=True, blank=True)
+ focal_point_height = models.PositiveIntegerField(null=True, blank=True)
def get_usage(self):
return get_object_usage(self)
diff --git a/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js b/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js
index 05c72feb4..ce6d355fa 100644
--- a/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js
+++ b/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js
@@ -97,6 +97,10 @@ $(function(){
// run tagit enhancement
$('.tag_field input', itemElement).tagit(window.tagit_opts);
+
+ $('.focal-point-chooser', itemElement).each(function() {
+ createFocalPointCooser($(this));
+ });
} else {
itemElement.addClass('upload-failure');
$('.right .error_messages', itemElement).append(response.error_message);
diff --git a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js
new file mode 100644
index 000000000..6d0f385db
--- /dev/null
+++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js
@@ -0,0 +1,52 @@
+function createFocalPointCooser($chooser) {
+ var $chosenText = $('.chosen-text', $chooser);
+ var $focalPointX = $('input.focal_point_x', $chooser);
+ var $focalPointY = $('input.focal_point_y', $chooser);
+ var $focalPointWidth = $('input.focal_point_width', $chooser);
+ var $focalPointHeight = $('input.focal_point_height', $chooser);
+ var chooserUrl = $chooser.data('chooserUrl');
+
+ $('.action-choose', $chooser).click(function() {
+ if (!$chooser.hasClass('blank')) {
+ window.focalPointChooserInitial = {
+ x: $focalPointX.val(),
+ y: $focalPointY.val(),
+ w: $focalPointWidth.val(),
+ h: $focalPointHeight.val(),
+ }
+ } else {
+ window.focalPointChooserInitial = undefined;
+ }
+
+ ModalWorkflow({
+ 'url': chooserUrl,
+ 'responses': {
+ 'focalPointChosen': function(focalPointData) {
+ $focalPointX.val(focalPointData.x);
+ $focalPointY.val(focalPointData.y);
+ $focalPointWidth.val(focalPointData.w);
+ $focalPointHeight.val(focalPointData.h);
+
+ $chosenText.text(focalPointData.x + ", " + focalPointData.y + " " + focalPointData.w + "x" + focalPointData.h);
+
+ $chooser.removeClass('blank');
+ }
+ }
+ });
+ });
+
+ $('.action-clear', $chooser).click(function() {
+ $focalPointX.val('');
+ $focalPointY.val('');
+ $focalPointWidth.val('');
+ $focalPointHeight.val('');
+
+ $chooser.addClass('blank');
+ });
+}
+
+$(function() {
+ $('.focal-point-chooser').each(function() {
+ createFocalPointCooser($(this));
+ });
+});
diff --git a/wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js b/wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js
new file mode 100644
index 000000000..4c9c7adb6
--- /dev/null
+++ b/wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js
@@ -0,0 +1,22 @@
+/**
+ * jquery.Jcrop.min.js v0.9.12 (build:20130202)
+ * jQuery Image Cropping Plugin - released under MIT License
+ * Copyright (c) 2008-2013 Tapmodo Interactive LLC
+ * https://github.com/tapmodo/Jcrop
+ */
+(function(a){a.Jcrop=function(b,c){function i(a){return Math.round(a)+"px"}function j(a){return d.baseClass+"-"+a}function k(){return a.fx.step.hasOwnProperty("backgroundColor")}function l(b){var c=a(b).offset();return[c.left,c.top]}function m(a){return[a.pageX-e[0],a.pageY-e[1]]}function n(b){typeof b!="object"&&(b={}),d=a.extend(d,b),a.each(["onChange","onSelect","onRelease","onDblClick"],function(a,b){typeof d[b]!="function"&&(d[b]=function(){})})}function o(a,b,c){e=l(D),bc.setCursor(a==="move"?a:a+"-resize");if(a==="move")return bc.activateHandlers(q(b),v,c);var d=_.getFixed(),f=r(a),g=_.getCorner(r(f));_.setPressed(_.getCorner(f)),_.setCurrent(g),bc.activateHandlers(p(a,d),v,c)}function p(a,b){return function(c){if(!d.aspectRatio)switch(a){case"e":c[1]=b.y2;break;case"w":c[1]=b.y2;break;case"n":c[0]=b.x2;break;case"s":c[0]=b.x2}else switch(a){case"e":c[1]=b.y+1;break;case"w":c[1]=b.y+1;break;case"n":c[0]=b.x+1;break;case"s":c[0]=b.x+1}_.setCurrent(c),bb.update()}}function q(a){var b=a;return bd.watchKeys
+(),function(a){_.moveOffset([a[0]-b[0],a[1]-b[1]]),b=a,bb.update()}}function r(a){switch(a){case"n":return"sw";case"s":return"nw";case"e":return"nw";case"w":return"ne";case"ne":return"sw";case"nw":return"se";case"se":return"nw";case"sw":return"ne"}}function s(a){return function(b){return d.disabled?!1:a==="move"&&!d.allowMove?!1:(e=l(D),W=!0,o(a,m(b)),b.stopPropagation(),b.preventDefault(),!1)}}function t(a,b,c){var d=a.width(),e=a.height();d>b&&b>0&&(d=b,e=b/a.width()*a.height()),e>c&&c>0&&(e=c,d=c/a.height()*a.width()),T=a.width()/d,U=a.height()/e,a.width(d).height(e)}function u(a){return{x:a.x*T,y:a.y*U,x2:a.x2*T,y2:a.y2*U,w:a.w*T,h:a.h*U}}function v(a){var b=_.getFixed();b.w>d.minSelect[0]&&b.h>d.minSelect[1]?(bb.enableHandles(),bb.done()):bb.release(),bc.setCursor(d.allowSelect?"crosshair":"default")}function w(a){if(d.disabled)return!1;if(!d.allowSelect)return!1;W=!0,e=l(D),bb.disableHandles(),bc.setCursor("crosshair");var b=m(a);return _.setPressed(b),bb.update(),bc.activateHandlers(x,v,a.type.substring
+(0,5)==="touch"),bd.watchKeys(),a.stopPropagation(),a.preventDefault(),!1}function x(a){_.setCurrent(a),bb.update()}function y(){var b=a("
").addClass(j("tracker"));return g&&b.css({opacity:0,backgroundColor:"white"}),b}function be(a){G.removeClass().addClass(j("holder")).addClass(a)}function bf(a,b){function t(){window.setTimeout(u,l)}var c=a[0]/T,e=a[1]/U,f=a[2]/T,g=a[3]/U;if(X)return;var h=_.flipCoords(c,e,f,g),i=_.getFixed(),j=[i.x,i.y,i.x2,i.y2],k=j,l=d.animationDelay,m=h[0]-j[0],n=h[1]-j[1],o=h[2]-j[2],p=h[3]-j[3],q=0,r=d.swingSpeed;c=k[0],e=k[1],f=k[2],g=k[3],bb.animMode(!0);var s,u=function(){return function(){q+=(100-q)/r,k[0]=Math.round(c+q/100*m),k[1]=Math.round(e+q/100*n),k[2]=Math.round(f+q/100*o),k[3]=Math.round(g+q/100*p),q>=99.8&&(q=100),q<100?(bh(k),t()):(bb.done(),bb.animMode(!1),typeof b=="function"&&b.call(bs))}}();t()}function bg(a){bh([a[0]/T,a[1]/U,a[2]/T,a[3]/U]),d.onSelect.call(bs,u(_.getFixed())),bb.enableHandles()}function bh(a){_.setPressed([a[0],a[1]]),_.setCurrent([a[2],
+a[3]]),bb.update()}function bi(){return u(_.getFixed())}function bj(){return _.getFixed()}function bk(a){n(a),br()}function bl(){d.disabled=!0,bb.disableHandles(),bb.setCursor("default"),bc.setCursor("default")}function bm(){d.disabled=!1,br()}function bn(){bb.done(),bc.activateHandlers(null,null)}function bo(){G.remove(),A.show(),A.css("visibility","visible"),a(b).removeData("Jcrop")}function bp(a,b){bb.release(),bl();var c=new Image;c.onload=function(){var e=c.width,f=c.height,g=d.boxWidth,h=d.boxHeight;D.width(e).height(f),D.attr("src",a),H.attr("src",a),t(D,g,h),E=D.width(),F=D.height(),H.width(E).height(F),M.width(E+L*2).height(F+L*2),G.width(E).height(F),ba.resize(E,F),bm(),typeof b=="function"&&b.call(bs)},c.src=a}function bq(a,b,c){var e=b||d.bgColor;d.bgFade&&k()&&d.fadeTime&&!c?a.animate({backgroundColor:e},{queue:!1,duration:d.fadeTime}):a.css("backgroundColor",e)}function br(a){d.allowResize?a?bb.enableOnly():bb.enableHandles():bb.disableHandles(),bc.setCursor(d.allowSelect?"crosshair":"default"),bb
+.setCursor(d.allowMove?"move":"default"),d.hasOwnProperty("trueSize")&&(T=d.trueSize[0]/E,U=d.trueSize[1]/F),d.hasOwnProperty("setSelect")&&(bg(d.setSelect),bb.done(),delete d.setSelect),ba.refresh(),d.bgColor!=N&&(bq(d.shade?ba.getShades():G,d.shade?d.shadeColor||d.bgColor:d.bgColor),N=d.bgColor),O!=d.bgOpacity&&(O=d.bgOpacity,d.shade?ba.refresh():bb.setBgOpacity(O)),P=d.maxSize[0]||0,Q=d.maxSize[1]||0,R=d.minSize[0]||0,S=d.minSize[1]||0,d.hasOwnProperty("outerImage")&&(D.attr("src",d.outerImage),delete d.outerImage),bb.refresh()}var d=a.extend({},a.Jcrop.defaults),e,f=navigator.userAgent.toLowerCase(),g=/msie/.test(f),h=/msie [1-6]\./.test(f);typeof b!="object"&&(b=a(b)[0]),typeof c!="object"&&(c={}),n(c);var z={border:"none",visibility:"visible",margin:0,padding:0,position:"absolute",top:0,left:0},A=a(b),B=!0;if(b.tagName=="IMG"){if(A[0].width!=0&&A[0].height!=0)A.width(A[0].width),A.height(A[0].height);else{var C=new Image;C.src=A[0].src,A.width(C.width),A.height(C.height)}var D=A.clone().removeAttr("id").
+css(z).show();D.width(A.width()),D.height(A.height()),A.after(D).hide()}else D=A.css(z).show(),B=!1,d.shade===null&&(d.shade=!0);t(D,d.boxWidth,d.boxHeight);var E=D.width(),F=D.height(),G=a("
").width(E).height(F).addClass(j("holder")).css({position:"relative",backgroundColor:d.bgColor}).insertAfter(A).append(D);d.addClass&&G.addClass(d.addClass);var H=a("
"),I=a("
").width("100%").height("100%").css({zIndex:310,position:"absolute",overflow:"hidden"}),J=a("
").width("100%").height("100%").css("zIndex",320),K=a("
").css({position:"absolute",zIndex:600}).dblclick(function(){var a=_.getFixed();d.onDblClick.call(bs,a)}).insertBefore(D).append(I,J);B&&(H=a(" ").attr("src",D.attr("src")).css(z).width(E).height(F),I.append(H)),h&&K.css({overflowY:"hidden"});var L=d.boundary,M=y().width(E+L*2).height(F+L*2).css({position:"absolute",top:i(-L),left:i(-L),zIndex:290}).mousedown(w),N=d.bgColor,O=d.bgOpacity,P,Q,R,S,T,U,V=!0,W,X,Y;e=l(D);var Z=function(){function a(){var a={},b=["touchstart"
+,"touchmove","touchend"],c=document.createElement("div"),d;try{for(d=0;da+f&&(f-=f+a),0>b+g&&(g-=g+b),FE&&(r=E,u=Math.abs((r-a)/f),s=k<0?b-u:u+b)):(r=c,u=l/f,s=k<0?b-u:b+u,s<0?(s=0,t=Math.abs((s-b)*f),r=j<0?a-t:t+a):s>F&&(s=F,t=Math.abs(s-b)*f,r=j<0?a-t:t+a)),r>a?(r-ah&&(r=a+h),s>b?s=b+(r-a)/f:s=b-(r-a)/f):rh&&(r=a-h),s>b?s=b+(a-r)/f:s=b-(a-r)/f),r<0?(a-=r,r=0):r>E&&(a-=r-E,r=E),s<0?(b-=s,s=0):s>F&&(b-=s-F,s=F),q(o(a,b,r,s))}function n(a){return a[0]<0&&(a[0]=0),a[1]<0&&(a[1]=0),a[0]>E&&(a[0]=E),a[1]>F&&(a[1]=F),[Math.round(a[0]),Math.round(a[1])]}function o(a,b,c,d){var e=a,f=c,g=b,h=d;return c P&&(c=d>0?a+P:a-P),Q&&Math.abs
+(f)>Q&&(e=f>0?b+Q:b-Q),S/U&&Math.abs(f)0?b+S/U:b-S/U),R/T&&Math.abs(d)0?a+R/T:a-R/T),a<0&&(c-=a,a-=a),b<0&&(e-=b,b-=b),c<0&&(a-=c,c-=c),e<0&&(b-=e,e-=e),c>E&&(g=c-E,a-=g,c-=g),e>F&&(g=e-F,b-=g,e-=g),a>E&&(g=a-F,e-=g,b-=g),b>F&&(g=b-F,e-=g,b-=g),q(o(a,b,c,e))}function q(a){return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]}}var a=0,b=0,c=0,e=0,f,g;return{flipCoords:o,setPressed:h,setCurrent:i,getOffset:j,moveOffset:k,getCorner:l,getFixed:m}}(),ba=function(){function f(a,b){e.left.css({height:i(b)}),e.right.css({height:i(b)})}function g(){return h(_.getFixed())}function h(a){e.top.css({left:i(a.x),width:i(a.w),height:i(a.y)}),e.bottom.css({top:i(a.y2),left:i(a.x),width:i(a.w),height:i(F-a.y2)}),e.right.css({left:i(a.x2),width:i(E-a.x2)}),e.left.css({width:i(a.x)})}function j(){return a("
").css({position:"absolute",backgroundColor:d.shadeColor||d.bgColor}).appendTo(c)}function k(){b||(b=!0,c.insertBefore(D),g(),bb.setBgOpacity(1,0,1),H.hide(),l(d.shadeColor||d.bgColor,1),bb.
+isAwake()?n(d.bgOpacity,1):n(1,1))}function l(a,b){bq(p(),a,b)}function m(){b&&(c.remove(),H.show(),b=!1,bb.isAwake()?bb.setBgOpacity(d.bgOpacity,1,1):(bb.setBgOpacity(1,1,1),bb.disableHandles()),bq(G,0,1))}function n(a,e){b&&(d.bgFade&&!e?c.animate({opacity:1-a},{queue:!1,duration:d.fadeTime}):c.css({opacity:1-a}))}function o(){d.shade?k():m(),bb.isAwake()&&n(d.bgOpacity)}function p(){return c.children()}var b=!1,c=a("
").css({position:"absolute",zIndex:240,opacity:0}),e={top:j(),left:j().height(F),right:j().height(F),bottom:j()};return{update:g,updateRaw:h,getShades:p,setBgColor:l,enable:k,disable:m,resize:f,refresh:o,opacity:n}}(),bb=function(){function k(b){var c=a("
").css({position:"absolute",opacity:d.borderOpacity}).addClass(j(b));return I.append(c),c}function l(b,c){var d=a("
").mousedown(s(b)).css({cursor:b+"-resize",position:"absolute",zIndex:c}).addClass("ord-"+b);return Z.support&&d.bind("touchstart.jcrop",Z.createDragger(b)),J.append(d),d}function m(a){var b=d.handleSize,e=l(a,c++
+).css({opacity:d.handleOpacity}).addClass(j("handle"));return b&&e.width(b).height(b),e}function n(a){return l(a,c++).addClass("jcrop-dragbar")}function o(a){var b;for(b=0;b ').css({position:"fixed",left:"-120px",width:"12px"}).addClass("jcrop-keymgr"),c=a("
").css({position:"absolute",overflow:"hidden"}).append(b);return d.keySupport&&(b.keydown(i).blur(f),h||!d.fixedSupport?(b.css({position:"absolute",left:"-20px"}),c.append(b).insertBefore(D)):b.insertBefore(D)),{watchKeys:e}}();Z.support&&M.bind("touchstart.jcrop",Z.newSelection),J.hide(),br(!0);var bs={setImage:bp,animateTo:bf,setSelect:bg,setOptions:bk,tellSelect:bi,tellScaled:bj,setClass:be,disable:bl,enable:bm,cancel:bn,release:bb.release,destroy:bo,focus:bd.watchKeys,getBounds:function(){return[E*T,F*U]},getWidgetSize:function(){return[E,F]},getScaleFactor:function(){return[T,U]},getOptions:function(){return d},ui:{holder:G,selection:K}};return g&&G.bind("selectstart",function(){return!1}),A.data("Jcrop",bs),bs},a.fn.Jcrop=function(b,c){var d;return this.each(function(){if(a(this).data("Jcrop")){if(
+b==="api")return a(this).data("Jcrop");a(this).data("Jcrop").setOptions(b)}else this.tagName=="IMG"?a.Jcrop.Loader(this,function(){a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d)}):(a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d))}),this},a.Jcrop.Loader=function(b,c,d){function g(){f.complete?(e.unbind(".jcloader"),a.isFunction(c)&&c.call(f)):window.setTimeout(g,50)}var e=a(b),f=e[0];e.bind("load.jcloader",g).bind("error.jcloader",function(b){e.unbind(".jcloader"),a.isFunction(d)&&d.call(f)}),f.complete&&a.isFunction(c)&&(e.unbind(".jcloader"),c.call(f))},a.Jcrop.defaults={allowSelect:!0,allowMove:!0,allowResize:!0,trackDocument:!0,baseClass:"jcrop",addClass:null,bgColor:"black",bgOpacity:.6,bgFade:!1,borderOpacity:.4,handleOpacity:.5,handleSize:null,aspectRatio:0,keySupport:!0,createHandles:["n","s","e","w","nw","ne","se","sw"],createDragbars:["n","s","e","w"],createBorders:["n","s","e","w"],drawBorders:!0,dragEdges
+:!0,fixedSupport:!0,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}}})(jQuery);
\ No newline at end of file
diff --git a/wagtail/wagtailimages/static/wagtailimages/scss/vendor/Jcrop.gif b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/Jcrop.gif
new file mode 100644
index 000000000..72ea7ccb5
Binary files /dev/null and b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/Jcrop.gif differ
diff --git a/wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css
new file mode 100644
index 000000000..edc76b2b3
--- /dev/null
+++ b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css
@@ -0,0 +1,29 @@
+/* jquery.Jcrop.min.css v0.9.12 (build:20130126) */
+.jcrop-holder{direction:ltr;text-align:left;}
+.jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;}
+.jcrop-vline{height:100%;width:1px!important;}
+.jcrop-vline.right{right:0;}
+.jcrop-hline{height:1px!important;width:100%;}
+.jcrop-hline.bottom{bottom:0;}
+.jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;}
+.jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;}
+.jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;}
+.jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;}
+.jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;}
+.jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;}
+.jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;}
+.jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;}
+.jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;}
+.jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;}
+.jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;}
+.jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;}
+.jcrop-dragbar.ord-n{margin-top:-4px;}
+.jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;}
+.jcrop-dragbar.ord-e{margin-right:-4px;right:0;}
+.jcrop-dragbar.ord-w{margin-left:-4px;}
+.jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;}
+.jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;}
+.jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;}
+.jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;}
+.solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;}
+.jcrop-holder img,img.jcrop-preview{max-width:none;}
diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html
new file mode 100644
index 000000000..dcb13504f
--- /dev/null
+++ b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html
@@ -0,0 +1,11 @@
+{% load wagtailimages_tags i18n %}
+
+{% trans "Choose focal point" as title_str %}
+{% include "wagtailadmin/shared/header.html" with title=title_str %}
+
+
+
+ {% image image max-800x600 %}
+
+
Done
+
\ No newline at end of file
diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js
new file mode 100644
index 000000000..a01807b14
--- /dev/null
+++ b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js
@@ -0,0 +1,67 @@
+function(modal) {
+ var jcapi;
+
+ function ajaxifyLinks (context) {
+ $('.listing a', context).click(function() {
+ modal.loadUrl(this.href);
+ return false;
+ });
+
+ $('.pagination a', context).click(function() {
+ var page = this.getAttribute("data-page");
+ setPage(page);
+ return false;
+ });
+ }
+
+ ajaxifyLinks(modal.body);
+
+ // Find image element
+ var $image = $('.focal-point-chooser-image img');
+
+ // Switch on Jcrop
+ $image.Jcrop({}, function() {
+ jcapi = this;
+ });
+
+ // Set initial select box
+ if (window.focalPointChooserInitial) {
+ var scaleX = {{ image.width }} / $image.width();
+ var scaleY = {{ image.height }} / $image.height();
+
+ var x = window.focalPointChooserInitial.x / scaleX;
+ var y = window.focalPointChooserInitial.y / scaleY;
+ var w = window.focalPointChooserInitial.w / scaleX;
+ var h = window.focalPointChooserInitial.h / scaleY;
+
+ jcapi.setSelect([
+ x - w / 2,
+ y - h / 2,
+ x + w / 2,
+ y + h / 2,
+ ]);
+ }
+
+ $('a.choose-focal-point', modal.body).click(function() {
+ var selectBox = jcapi.tellSelect();
+ var scaleX = {{ image.width }} / $image.width();
+ var scaleY = {{ image.height }} / $image.height();
+
+ modal.respond('focalPointChosen', {
+ x: Math.floor(scaleX * (selectBox.x + selectBox.x2) / 2),
+ y: Math.floor(scaleY * (selectBox.y + selectBox.y2) / 2),
+ w: Math.floor(scaleX * selectBox.w),
+ h: Math.floor(scaleY * selectBox.h),
+ });
+
+ modal.close();
+
+ return false;
+ });
+
+ $('#id_q').on('input', function() {
+ clearTimeout($.data(this, 'timer'));
+ var wait = setTimeout(search, 200);
+ $(this).data('timer', wait);
+ });
+}
\ No newline at end of file
diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html b/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html
new file mode 100644
index 000000000..797444947
--- /dev/null
+++ b/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html
@@ -0,0 +1,41 @@
+{% load wagtailadmin_tags i18n %}
+
+
+
{% trans "Focal point" %}
+
+
+
+
+
+ {% if field.errors %}
+
+ Invalid input
+
+ {% endif %}
+
+
diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html
index e904bd426..1a91959fb 100644
--- a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html
+++ b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html
@@ -5,10 +5,18 @@
{% block bodyclass %}menu-images{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/shared/tag_field_css.html" %}
+
+
+
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/shared/tag_field_js.html" %}
+
+
+
+
+
{% endblock %}
{% block content %}
@@ -25,6 +33,12 @@
{% if field.name == 'file' %}
{% include "wagtailimages/images/_file_field.html" %}
+ {% elif field.name == 'focal_point_x' %}
+ {% include "wagtailimages/images/_focal_point_chooser.html" %}
+ {% elif field.name == 'focal_point_y' or field.name == 'focal_point_width' or field.name == 'focal_point_height' %}
+ {# These fields are included in the focal point chooser above #}
+ {% elif field.is_hidden %}
+ {{ field }}
{% else %}
{% include "wagtailadmin/shared/field_as_li.html" %}
{% endif %}
diff --git a/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html b/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html
index 9bde36d09..426ef04fe 100644
--- a/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html
+++ b/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html
@@ -7,6 +7,9 @@
{% endcompress %}
{% include "wagtailadmin/shared/tag_field_css.html" %}
+
+
+
{% endblock %}
{% block content %}
@@ -62,6 +65,11 @@
+
+
+
+
+
diff --git a/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html b/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html
index 9fdebacc1..a07fc0d9e 100644
--- a/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html
+++ b/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html
@@ -4,7 +4,15 @@
{% csrf_token %}
{% for field in form %}
- {% include "wagtailadmin/shared/field_as_li.html" %}
+ {% if field.name == 'focal_point_x' %}
+ {% include "wagtailimages/images/_focal_point_chooser.html" %}
+ {% elif field.name == 'focal_point_y' or field.name == 'focal_point_width' or field.name == 'focal_point_height' %}
+ {# These fields are included in the focal point chooser above #}
+ {% elif field.is_hidden %}
+ {{ field }}
+ {% else %}
+ {% include "wagtailadmin/shared/field_as_li.html" %}
+ {% endif %}
{% endfor %}
diff --git a/wagtail/wagtailimages/views/focal_point_chooser.py b/wagtail/wagtailimages/views/focal_point_chooser.py
new file mode 100644
index 000000000..21ca26c79
--- /dev/null
+++ b/wagtail/wagtailimages/views/focal_point_chooser.py
@@ -0,0 +1,21 @@
+import json
+
+from django.shortcuts import get_object_or_404, render
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
+from django.contrib.auth.decorators import permission_required
+
+from wagtail.wagtailadmin.modal_workflow import render_modal_workflow
+from wagtail.wagtailadmin.forms import SearchForm
+
+from wagtail.wagtailimages.models import get_image_model
+from wagtail.wagtailimages.forms import get_image_form, ImageInsertionForm
+from wagtail.wagtailimages.formats import get_image_format
+
+
+@permission_required('wagtailadmin.access_admin')
+def chooser(request, image_id):
+ image = get_object_or_404(get_image_model(), id=image_id)
+
+ return render_modal_workflow(request, 'wagtailimages/focal_point_chooser/chooser.html', 'wagtailimages/focal_point_chooser/chooser.js', {
+ 'image': image,
+ })