Added default "portrait" thumbnail (and added generic classes)
authorThierry Florac <thierry.florac@onf.fr>
Fri, 08 Jun 2018 14:58:57 +0200
changeset 100 7add759206bc
parent 99 bf6607bda112
child 101 0c7bead9f062
Added default "portrait" thumbnail (and added generic classes)
src/pyams_file/image.py
src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.mo
src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po
src/pyams_file/locales/pyams_file.pot
src/pyams_file/zmi/image.py
src/pyams_file/zmi/templates/image-pano-thumbnail.pt
src/pyams_file/zmi/templates/image-selection.pt
src/pyams_file/zmi/templates/image-square-thumbnail.pt
--- a/src/pyams_file/image.py	Tue May 29 09:51:18 2018 +0200
+++ b/src/pyams_file/image.py	Fri Jun 08 14:58:57 2018 +0200
@@ -148,56 +148,59 @@
         return width, height
 
 
+class ImageRatioThumbnailer(ImageSelectionThumbnailer):
+    """Image thumbnailer with specific ratio"""
+
+    ratio = (None, None)  # (width, height) ratio tuple
+
+    def get_default_geometry(self, options=None):
+        geometry = ThumbnailGeometry()
+        width, height = self.context.get_image_size()
+        thumb_max_height = width * self.ratio[1] / self.ratio[0]
+        if thumb_max_height >= height:
+            # image wider
+            thumb_width = height * self.ratio[0] / self.ratio[1]
+            geometry.x1 = round((width / 2) - (thumb_width / 2))
+            geometry.y1 = 0
+            geometry.x2 = round((width / 2) + (thumb_width / 2))
+            geometry.y2 = height
+        else:
+            thumb_height = thumb_max_height
+            geometry.x1 = 0
+            geometry.y1 = round((height / 2) - (thumb_height / 2))
+            geometry.x2 = width
+            geometry.y2 = round((height / 2) + (thumb_height / 2))
+        return geometry
+
+
+@adapter_config(name='portrait', context=IImage, provides=IThumbnailer)
+class ImagePortraitThumbnailer(ImageRatioThumbnailer):
+    """Image portrait thumbnail adapter"""
+
+    label = _("Portrait thumbnail")
+    weight = 5
+
+    ratio = (3, 4)
+
+
 @adapter_config(name='square', context=IImage, provides=IThumbnailer)
-class ImageSquareThumbnailer(ImageSelectionThumbnailer):
+class ImageSquareThumbnailer(ImageRatioThumbnailer):
     """Image square thumbnail adapter"""
 
     label = _("Square thumbnail")
-    weight = 5
+    weight = 6
 
-    def get_default_geometry(self, options=None):
-        """Default square thumbnail geometry"""
-        geometry = ThumbnailGeometry()
-        width, height = self.context.get_image_size()
-        if width >= height:
-            geometry.x1 = round((width / 2) - (height / 2))
-            geometry.y1 = 0
-            geometry.x2 = round((width / 2) + (height / 2))
-            geometry.y2 = height
-        else:
-            geometry.x1 = 0
-            geometry.y1 = round((height / 2) - (width / 2))
-            geometry.x2 = width
-            geometry.y2 = round((height / 2) + (width / 2))
-        return geometry
+    ratio = (1, 1)
 
 
 @adapter_config(name='pano', context=IImage, provides=IThumbnailer)
-class ImagePanoThumbnailer(ImageSelectionThumbnailer):
+class ImagePanoThumbnailer(ImageRatioThumbnailer):
     """Image panoramic thumbnail adapter"""
 
     label = _("Panoramic thumbnail")
-    weight = 6
+    weight = 7
 
-    def get_default_geometry(self, options=None):
-        """Default panoramic thumbnail geometry"""
-        geometry = ThumbnailGeometry()
-        width, height = self.context.get_image_size()
-        pano_max_height = width * 9 / 16
-        if pano_max_height >= height:
-            # image wider
-            pano_width = height * 16 / 9
-            geometry.x1 = round((width / 2) - (pano_width / 2))
-            geometry.y1 = 0
-            geometry.x2 = round((width / 2) + (pano_width / 2))
-            geometry.y2 = height
-        else:
-            pano_height = pano_max_height
-            geometry.x1 = 0
-            geometry.y1 = round((height / 2) - (pano_height / 2))
-            geometry.x2 = width
-            geometry.y2 = round((height / 2) + (pano_height / 2))
-        return geometry
+    ratio = (16, 9)
 
     def get_thumb_size(self, width, height, geometry):
         thumb_size = abs(geometry.x2 - geometry.x1), abs(geometry.y2 - geometry.y1)
@@ -231,7 +234,7 @@
 
 @adapter_config(name='md', context=IResponsiveImage, provides=IThumbnailer)
 class MdImageThumbnailer(ResponsiveImageThumbnailer):
-    """MeDiumresponsive image thumbnailer"""
+    """MeDium responsive image thumbnailer"""
 
     label = _("Medium screen thumbnail")
     weight = 12
Binary file src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.mo has changed
--- a/src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po	Tue May 29 09:51:18 2018 +0200
+++ b/src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po	Fri Jun 08 14:58:57 2018 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2017-09-07 15:18+0200\n"
+"POT-Creation-Date: 2018-06-08 12:31+0200\n"
 "PO-Revision-Date: 2015-02-06 21:39+0100\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -20,84 +20,85 @@
 msgid "Default thumbnail"
 msgstr "Vignette par défaut"
 
-#: src/pyams_file/image.py:100
+#: src/pyams_file/image.py:103
 msgid "Custom selections"
 msgstr "Sélections spécifiques"
 
-#: src/pyams_file/image.py:149
+#: src/pyams_file/image.py:180
+msgid "Portrait thumbnail"
+msgstr "Vignette portrait"
+
+#: src/pyams_file/image.py:190
 msgid "Square thumbnail"
 msgstr "Vignette carrée"
 
-#: src/pyams_file/image.py:173
+#: src/pyams_file/image.py:200
 msgid "Panoramic thumbnail"
 msgstr "Vignette panoramique"
 
-#: src/pyams_file/image.py:207
+#: src/pyams_file/image.py:216
 msgid "Responsive selections"
 msgstr "Sélections responsives"
 
-#: src/pyams_file/image.py:214
+#: src/pyams_file/image.py:223
 msgid "Smartphone thumbnail"
 msgstr "Smartphone"
 
-#: src/pyams_file/image.py:222
+#: src/pyams_file/image.py:231
 msgid "Tablet thumbnail"
 msgstr "Tablette"
 
-#: src/pyams_file/image.py:230
+#: src/pyams_file/image.py:239
 msgid "Medium screen thumbnail"
 msgstr "Terminaux moyens"
 
-#: src/pyams_file/image.py:238
+#: src/pyams_file/image.py:247
 msgid "Large screen thumbnail"
 msgstr "Grands terminaux"
 
-#: src/pyams_file/widget/templates/image-display.pt:12
-#: src/pyams_file/widget/templates/image-input.pt:29
-msgid "Zoom image"
-msgstr "Agrandir l'image"
-
-#: src/pyams_file/widget/templates/image-display.pt:18
-#: src/pyams_file/widget/templates/image-input.pt:35
-#: src/pyams_file/widget/templates/file-input.pt:27
-#: src/pyams_file/widget/templates/file-display.pt:10
-msgid "Current value:"
-msgstr "Contenu actuel :"
-
-#: src/pyams_file/widget/templates/image-display.pt:23
-#: src/pyams_file/widget/templates/image-input.pt:40
-#: src/pyams_file/widget/templates/file-input.pt:30
-#: src/pyams_file/widget/templates/file-display.pt:13
-msgid "${size} Kb"
-msgstr "${size} Ko"
-
-#: src/pyams_file/widget/templates/image-display.pt:28
-#: src/pyams_file/widget/templates/image-input.pt:51
-#: src/pyams_file/widget/templates/file-input.pt:41
-#: src/pyams_file/widget/templates/file-display.pt:17
-msgid "Download"
-msgstr "Enregistrer sous..."
-
-#: src/pyams_file/widget/templates/image-input.pt:7
+#: src/pyams_file/widget/templates/media-input.pt:7
 #: src/pyams_file/widget/templates/file-input.pt:7
 msgid "Browse..."
 msgstr "Parcourir..."
 
-#: src/pyams_file/widget/templates/image-input.pt:8
+#: src/pyams_file/widget/templates/media-input.pt:8
 #: src/pyams_file/widget/templates/file-input.pt:8
 msgid "Please select a file..."
 msgstr "Veuillez sélectionner un fichier..."
 
-#: src/pyams_file/widget/templates/image-input.pt:16
-#: src/pyams_file/widget/templates/file-input.pt:16
+#: src/pyams_file/widget/templates/media-input.pt:19
+#: src/pyams_file/widget/templates/file-input.pt:19
 msgid "Delete content"
 msgstr "Supprimer ce contenu"
 
+#: src/pyams_file/widget/templates/media-input.pt:30
+#: src/pyams_file/widget/templates/media-display.pt:12
+msgid "Zoom image"
+msgstr "Agrandir l'image"
+
+#: src/pyams_file/widget/templates/media-input.pt:36
+#: src/pyams_file/widget/templates/media-input.pt:68
+#: src/pyams_file/widget/templates/file-input.pt:31
+#: src/pyams_file/widget/templates/file-display.pt:14
+#: src/pyams_file/widget/templates/media-display.pt:20
+#: src/pyams_file/widget/templates/media-display.pt:37
+msgid "Current value:"
+msgstr "Contenu actuel :"
+
+#: src/pyams_file/widget/templates/media-input.pt:52
+#: src/pyams_file/widget/templates/media-input.pt:82
+#: src/pyams_file/widget/templates/file-input.pt:45
+#: src/pyams_file/widget/templates/file-display.pt:21
+#: src/pyams_file/widget/templates/media-display.pt:30
+#: src/pyams_file/widget/templates/media-display.pt:44
+msgid "Download"
+msgstr "Enregistrer sous..."
+
 #: src/pyams_file/zmi/file.py:42
 msgid "Properties..."
 msgstr "Propriétés"
 
-#: src/pyams_file/zmi/file.py:54
+#: src/pyams_file/zmi/file.py:56
 msgid "Update file properties"
 msgstr "Mise à jour des propriétés"
 
@@ -105,11 +106,11 @@
 msgid "Crop image..."
 msgstr "Recadrer l'image"
 
-#: src/pyams_file/zmi/image.py:80
+#: src/pyams_file/zmi/image.py:83
 msgid "Crop image"
 msgstr "Recadrer l'image"
 
-#: src/pyams_file/zmi/image.py:129
+#: src/pyams_file/zmi/image.py:126
 msgid ""
 "You can use this form to crop an image.\n"
 "\n"
@@ -127,7 +128,7 @@
 "ou les vignettes déjà sélectionnées sur la base de l'ancienne image sont "
 "réinitialisées !"
 
-#: src/pyams_file/zmi/image.py:143
+#: src/pyams_file/zmi/image.py:140
 msgid "You can use this form to make a selection on an image."
 msgstr ""
 "Par défaut, l'image est affichée dans son intégralité quel que soit le type "
@@ -140,15 +141,39 @@
 "**ATTENTION** : si l'image d'origine est recadrée ou rechargée, la fonction "
 "est réinitialisée et il faut procéder à un nouveau choix."
 
-#: src/pyams_file/zmi/image.py:159
+#: src/pyams_file/zmi/image.py:160
+msgid "Select portrait thumbnail..."
+msgstr "Vignette portrait"
+
+#: src/pyams_file/zmi/image.py:182
+msgid "Select portrait thumbnail"
+msgstr "Emprise de la vignette en mode portrait"
+
+#: src/pyams_file/zmi/image.py:225
+msgid ""
+"You can use this form to select a portrait thumbnail of this image.\n"
+"\n"
+"**WARNING**: cropping or resizing an image will reset all selected "
+"thumbnails and adaptive images!!"
+msgstr ""
+"L'utilisation d'une vignette en mode portrait n'est pas systématique, elle dépend "
+"du modèle de présentation qui peut ou non y faire appel. Par défaut, la "
+"vignette est positionnée au centre de l'image, la sélection de "
+"son emprise ne modifie pas l'image d'origine.\n"
+"\n"
+"**ATTENTION** : lorsqu'une image est recadrée, redimentionnée ou rechargée, "
+"la sélection est réinitialisée sur sa position par défaut (centrée) ; s'il y "
+"a lieu, vous devez procéder à une nouvelle sélection personnalisée."
+
+#: src/pyams_file/zmi/image.py:240
 msgid "Select square thumbnail..."
 msgstr "Vignette carrée"
 
-#: src/pyams_file/zmi/image.py:178
+#: src/pyams_file/zmi/image.py:262
 msgid "Select square thumbnail"
 msgstr "Emprise de la vignette carrée"
 
-#: src/pyams_file/zmi/image.py:227
+#: src/pyams_file/zmi/image.py:305
 msgid ""
 "You can use this form to select a square thumbnail of this image.\n"
 "\n"
@@ -164,71 +189,79 @@
 "la sélection est réinitialisée sur sa position par défaut (centrée) ; s'il y "
 "a lieu, vous devez procéder à une nouvelle sélection personnalisée."
 
-#: src/pyams_file/zmi/image.py:242
+#: src/pyams_file/zmi/image.py:320
 msgid "Select panoramic thumbnail..."
 msgstr "Vignette panoramique"
 
-#: src/pyams_file/zmi/image.py:261
+#: src/pyams_file/zmi/image.py:342
 msgid "Select panoramic thumbnail"
 msgstr "Emprise de la vignette panoramique"
 
-#: src/pyams_file/zmi/image.py:310
+#: src/pyams_file/zmi/image.py:385
 msgid ""
 "You can use this form to select a panoramic thumbnail of this image.\n"
 "\n"
 "**WARNING**: cropping or resizing an image will reset all selected "
 "thumbnails and adaptive images!!"
 msgstr ""
-"L'utilisation d'une vignette panoramique n'est pas systématique, elle dépend du "
-"modèle de présentation qui peut ou non y faire appel. Par défaut, la "
-"vignette panoramique est positionnée au centre de l'image, la sélection de son "
-"emprise ne modifie pas l'image d'origine.\n"
+"L'utilisation d'une vignette panoramique n'est pas systématique, elle dépend "
+"du modèle de présentation qui peut ou non y faire appel. Par défaut, la "
+"vignette panoramique est positionnée au centre de l'image, la sélection de "
+"son emprise ne modifie pas l'image d'origine.\n"
 "\n"
 "**ATTENTION** : lorsqu'une image est recadrée, redimentionnée ou rechargée, "
 "la sélection est réinitialisée sur sa position par défaut (centrée) ; s'il y "
 "a lieu, vous devez procéder à une nouvelle sélection personnalisée."
 
-#: src/pyams_file/zmi/image.py:380
+#: src/pyams_file/zmi/image.py:458
 msgid "Select responsive XS image..."
 msgstr "Image adaptative pour smartphones"
 
-#: src/pyams_file/zmi/image.py:399
+#: src/pyams_file/zmi/image.py:479
 msgid "Select image for extra-small (XS) devices"
 msgstr "Portion de l'image affichée sur les smartphones (taille XS)"
 
-#: src/pyams_file/zmi/image.py:421
+#: src/pyams_file/zmi/image.py:494
 msgid "Select responsive SM image..."
 msgstr "Image adaptative pour tablettes"
 
-#: src/pyams_file/zmi/image.py:440
+#: src/pyams_file/zmi/image.py:515
 msgid "Select image for small (SM) devices"
 msgstr "Portion de l'image affichée sur les tablettes (taille SM)"
 
-#: src/pyams_file/zmi/image.py:462
+#: src/pyams_file/zmi/image.py:530
 msgid "Select responsive MD image..."
 msgstr "Image adaptative pour terminaux moyens"
 
-#: src/pyams_file/zmi/image.py:481
+#: src/pyams_file/zmi/image.py:551
 msgid "Select image for medium (MD) devices"
 msgstr "Portion de l'image affichée sur les terminaux moyens (taille MD)"
 
-#: src/pyams_file/zmi/image.py:503
+#: src/pyams_file/zmi/image.py:566
 msgid "Select responsive LG image..."
 msgstr "Image adaptative pour grands terminaux"
 
-#: src/pyams_file/zmi/image.py:522
+#: src/pyams_file/zmi/image.py:587
 msgid "Select image for large (LG) devices"
 msgstr "Portion de l'image affichée sur les grands terminaux (taille LG)"
 
-#: src/pyams_file/zmi/image.py:544
+#: src/pyams_file/zmi/image.py:602
+msgid "Display all thumbnails"
+msgstr "Voir toutes les vignettes"
+
+#: src/pyams_file/zmi/image.py:619
+msgid "Display all image thumbnails"
+msgstr "Récapitulatif des vignettes associées"
+
+#: src/pyams_file/zmi/image.py:664
 msgid "Resize image..."
 msgstr "Redimensionner l'image"
 
-#: src/pyams_file/zmi/image.py:595 src/pyams_file/zmi/image.py:555
+#: src/pyams_file/zmi/image.py:718 src/pyams_file/zmi/image.py:675
 msgid "Resize image"
 msgstr "Redimensionner l'image"
 
-#: src/pyams_file/zmi/image.py:631
+#: src/pyams_file/zmi/image.py:749
 msgid ""
 "You can use this form to change image dimensions.\n"
 "\n"
@@ -240,16 +273,8 @@
 "Une nouvelle image ne sera générée que si les dimensions indiquées sont "
 "inférieures à la taille du fichier actuel."
 
-#: src/pyams_file/zmi/image.py:646
-msgid "Display all thumbnails"
-msgstr "Voir toutes les vignettes"
-
-#: src/pyams_file/zmi/image.py:663
-msgid "Display all image thumbnails"
-msgstr "Récapitulatif des vignettes associées"
-
-#: src/pyams_file/zmi/image.py:71 src/pyams_file/zmi/image.py:150
-#: src/pyams_file/zmi/image.py:323 src/pyams_file/zmi/image.py:554
+#: src/pyams_file/zmi/image.py:71 src/pyams_file/zmi/image.py:147
+#: src/pyams_file/zmi/image.py:398 src/pyams_file/zmi/image.py:674
 msgid "Close"
 msgstr "Fermer"
 
@@ -257,23 +282,23 @@
 msgid "Crop"
 msgstr "Recadrer l'image"
 
-#: src/pyams_file/zmi/image.py:151 src/pyams_file/zmi/image.py:324
+#: src/pyams_file/zmi/image.py:148 src/pyams_file/zmi/image.py:399
 msgid "Select thumbnail"
 msgstr "Sélectionner cette portion d'image"
 
-#: src/pyams_file/zmi/image.py:561
+#: src/pyams_file/zmi/image.py:681
 msgid "New image width"
 msgstr "Largeur de l'image"
 
-#: src/pyams_file/zmi/image.py:563
+#: src/pyams_file/zmi/image.py:683
 msgid "New image height"
 msgstr "Hauteur de l'image"
 
-#: src/pyams_file/zmi/image.py:565
+#: src/pyams_file/zmi/image.py:685
 msgid "Keep aspect ratio"
 msgstr "Ne pas déformer l'image"
 
-#: src/pyams_file/zmi/image.py:566
+#: src/pyams_file/zmi/image.py:686
 msgid ""
 "Check to keep original aspect ratio; image will be resized as large as "
 "possible within given limits"
@@ -306,5 +331,8 @@
 msgid "File's content language"
 msgstr "Langue du contenu du fichier"
 
+#~ msgid "${size} Kb"
+#~ msgstr "${size} Ko"
+
 #~ msgid "Select image"
 #~ msgstr "Sélectionner l'image"
--- a/src/pyams_file/locales/pyams_file.pot	Tue May 29 09:51:18 2018 +0200
+++ b/src/pyams_file/locales/pyams_file.pot	Fri Jun 08 14:58:57 2018 +0200
@@ -1,12 +1,12 @@
 #
 # SOME DESCRIPTIVE TITLE
 # This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2017-09-07 15:18+0200\n"
+"POT-Creation-Date: 2018-06-08 12:31+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -20,84 +20,85 @@
 msgid "Default thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/image.py:100
+#: ./src/pyams_file/image.py:103
 msgid "Custom selections"
 msgstr ""
 
-#: ./src/pyams_file/image.py:149
+#: ./src/pyams_file/image.py:180
+msgid "Portrait thumbnail"
+msgstr ""
+
+#: ./src/pyams_file/image.py:190
 msgid "Square thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/image.py:173
+#: ./src/pyams_file/image.py:200
 msgid "Panoramic thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/image.py:207
+#: ./src/pyams_file/image.py:216
 msgid "Responsive selections"
 msgstr ""
 
-#: ./src/pyams_file/image.py:214
+#: ./src/pyams_file/image.py:223
 msgid "Smartphone thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/image.py:222
+#: ./src/pyams_file/image.py:231
 msgid "Tablet thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/image.py:230
+#: ./src/pyams_file/image.py:239
 msgid "Medium screen thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/image.py:238
+#: ./src/pyams_file/image.py:247
 msgid "Large screen thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/widget/templates/image-display.pt:12
-#: ./src/pyams_file/widget/templates/image-input.pt:29
-msgid "Zoom image"
-msgstr ""
-
-#: ./src/pyams_file/widget/templates/image-display.pt:18
-#: ./src/pyams_file/widget/templates/image-input.pt:35
-#: ./src/pyams_file/widget/templates/file-input.pt:27
-#: ./src/pyams_file/widget/templates/file-display.pt:10
-msgid "Current value:"
-msgstr ""
-
-#: ./src/pyams_file/widget/templates/image-display.pt:23
-#: ./src/pyams_file/widget/templates/image-input.pt:40
-#: ./src/pyams_file/widget/templates/file-input.pt:30
-#: ./src/pyams_file/widget/templates/file-display.pt:13
-msgid "${size} Kb"
-msgstr ""
-
-#: ./src/pyams_file/widget/templates/image-display.pt:28
-#: ./src/pyams_file/widget/templates/image-input.pt:51
-#: ./src/pyams_file/widget/templates/file-input.pt:41
-#: ./src/pyams_file/widget/templates/file-display.pt:17
-msgid "Download"
-msgstr ""
-
-#: ./src/pyams_file/widget/templates/image-input.pt:7
+#: ./src/pyams_file/widget/templates/media-input.pt:7
 #: ./src/pyams_file/widget/templates/file-input.pt:7
 msgid "Browse..."
 msgstr ""
 
-#: ./src/pyams_file/widget/templates/image-input.pt:8
+#: ./src/pyams_file/widget/templates/media-input.pt:8
 #: ./src/pyams_file/widget/templates/file-input.pt:8
 msgid "Please select a file..."
 msgstr ""
 
-#: ./src/pyams_file/widget/templates/image-input.pt:16
-#: ./src/pyams_file/widget/templates/file-input.pt:16
+#: ./src/pyams_file/widget/templates/media-input.pt:19
+#: ./src/pyams_file/widget/templates/file-input.pt:19
 msgid "Delete content"
 msgstr ""
 
+#: ./src/pyams_file/widget/templates/media-input.pt:30
+#: ./src/pyams_file/widget/templates/media-display.pt:12
+msgid "Zoom image"
+msgstr ""
+
+#: ./src/pyams_file/widget/templates/media-input.pt:36
+#: ./src/pyams_file/widget/templates/media-input.pt:68
+#: ./src/pyams_file/widget/templates/file-input.pt:31
+#: ./src/pyams_file/widget/templates/file-display.pt:14
+#: ./src/pyams_file/widget/templates/media-display.pt:20
+#: ./src/pyams_file/widget/templates/media-display.pt:37
+msgid "Current value:"
+msgstr ""
+
+#: ./src/pyams_file/widget/templates/media-input.pt:52
+#: ./src/pyams_file/widget/templates/media-input.pt:82
+#: ./src/pyams_file/widget/templates/file-input.pt:45
+#: ./src/pyams_file/widget/templates/file-display.pt:21
+#: ./src/pyams_file/widget/templates/media-display.pt:30
+#: ./src/pyams_file/widget/templates/media-display.pt:44
+msgid "Download"
+msgstr ""
+
 #: ./src/pyams_file/zmi/file.py:42
 msgid "Properties..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/file.py:54
+#: ./src/pyams_file/zmi/file.py:56
 msgid "Update file properties"
 msgstr ""
 
@@ -105,108 +106,123 @@
 msgid "Crop image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:80
+#: ./src/pyams_file/zmi/image.py:83
 msgid "Crop image"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:129
+#: ./src/pyams_file/zmi/image.py:126
 msgid ""
 "You can use this form to crop an image.\n"
 "\n"
 "**WARNING**: cropping an image will reset all selected thumbnails and adaptive images!!"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:143
+#: ./src/pyams_file/zmi/image.py:140
 msgid "You can use this form to make a selection on an image."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:159
+#: ./src/pyams_file/zmi/image.py:160
+msgid "Select portrait thumbnail..."
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:182
+msgid "Select portrait thumbnail"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:225
+msgid ""
+"You can use this form to select a portrait thumbnail of this image.\n"
+"\n"
+"**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:240
 msgid "Select square thumbnail..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:178
+#: ./src/pyams_file/zmi/image.py:262
 msgid "Select square thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:227
+#: ./src/pyams_file/zmi/image.py:305
 msgid ""
 "You can use this form to select a square thumbnail of this image.\n"
 "\n"
 "**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:242
+#: ./src/pyams_file/zmi/image.py:320
 msgid "Select panoramic thumbnail..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:261
+#: ./src/pyams_file/zmi/image.py:342
 msgid "Select panoramic thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:310
+#: ./src/pyams_file/zmi/image.py:385
 msgid ""
 "You can use this form to select a panoramic thumbnail of this image.\n"
 "\n"
 "**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:380
+#: ./src/pyams_file/zmi/image.py:458
 msgid "Select responsive XS image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:399
+#: ./src/pyams_file/zmi/image.py:479
 msgid "Select image for extra-small (XS) devices"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:421
+#: ./src/pyams_file/zmi/image.py:494
 msgid "Select responsive SM image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:440
+#: ./src/pyams_file/zmi/image.py:515
 msgid "Select image for small (SM) devices"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:462
+#: ./src/pyams_file/zmi/image.py:530
 msgid "Select responsive MD image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:481
+#: ./src/pyams_file/zmi/image.py:551
 msgid "Select image for medium (MD) devices"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:503
+#: ./src/pyams_file/zmi/image.py:566
 msgid "Select responsive LG image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:522
+#: ./src/pyams_file/zmi/image.py:587
 msgid "Select image for large (LG) devices"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:544
+#: ./src/pyams_file/zmi/image.py:602
+msgid "Display all thumbnails"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:619
+msgid "Display all image thumbnails"
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:664
 msgid "Resize image..."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:595 ./src/pyams_file/zmi/image.py:555
+#: ./src/pyams_file/zmi/image.py:718 ./src/pyams_file/zmi/image.py:675
 msgid "Resize image"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:631
+#: ./src/pyams_file/zmi/image.py:749
 msgid ""
 "You can use this form to change image dimensions.\n"
 "\n"
 "This will generate a new image only if requested size is smaller than the original one."
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:646
-msgid "Display all thumbnails"
-msgstr ""
-
-#: ./src/pyams_file/zmi/image.py:663
-msgid "Display all image thumbnails"
-msgstr ""
-
-#: ./src/pyams_file/zmi/image.py:71 ./src/pyams_file/zmi/image.py:150
-#: ./src/pyams_file/zmi/image.py:323 ./src/pyams_file/zmi/image.py:554
+#: ./src/pyams_file/zmi/image.py:71 ./src/pyams_file/zmi/image.py:147
+#: ./src/pyams_file/zmi/image.py:398 ./src/pyams_file/zmi/image.py:674
 msgid "Close"
 msgstr ""
 
@@ -214,23 +230,23 @@
 msgid "Crop"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:151 ./src/pyams_file/zmi/image.py:324
+#: ./src/pyams_file/zmi/image.py:148 ./src/pyams_file/zmi/image.py:399
 msgid "Select thumbnail"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:561
+#: ./src/pyams_file/zmi/image.py:681
 msgid "New image width"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:563
+#: ./src/pyams_file/zmi/image.py:683
 msgid "New image height"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:565
+#: ./src/pyams_file/zmi/image.py:685
 msgid "Keep aspect ratio"
 msgstr ""
 
-#: ./src/pyams_file/zmi/image.py:566
+#: ./src/pyams_file/zmi/image.py:686
 msgid ""
 "Check to keep original aspect ratio; image will be resized as large as "
 "possible within given limits"
--- a/src/pyams_file/zmi/image.py	Tue May 29 09:51:18 2018 +0200
+++ b/src/pyams_file/zmi/image.py	Fri Jun 08 14:58:57 2018 +0200
@@ -31,7 +31,7 @@
 # import packages
 from pyams_file.image import ThumbnailGeometry
 from pyams_file.zmi import FileModifierAction
-from pyams_form.form import AJAXEditForm, DialogDisplayForm
+from pyams_form.form import DialogDisplayForm, ajax_config
 from pyams_form.help import FormHelp
 from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
@@ -41,7 +41,6 @@
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config, Viewlet
 from pyams_zmi.form import AdminDialogEditForm
-from pyramid.view import view_config
 from z3c.form import field, button
 from zope.interface import implementer, Interface
 from zope.schema import Int, Bool
@@ -74,6 +73,7 @@
 
 
 @pagelet_config(name='crop.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@ajax_config(name='crop.json', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
 @implementer(IFileModifierForm)
 class ImageCropForm(AdminDialogEditForm):
     """Image crop form"""
@@ -86,7 +86,6 @@
 
     fields = field.Fields(Interface)
     buttons = button.Buttons(ICropFormButtons)
-    ajax_handler = 'crop.json'
     edit_permission = None
 
     @property
@@ -98,12 +97,6 @@
         if 'crop' in self.actions:
             self.actions['crop'].addClass('btn-primary')
 
-
-@view_config(name='crop.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImageCropAJAXForm(AJAXEditForm, ImageCropForm):
-    """Image crop form, AJAX renderer"""
-
     def update_content(self, content, data):
         image_size = self.context.get_image_size()
         x1 = int(self.request.params.get('selection.x1', 0))
@@ -137,7 +130,7 @@
 
 
 #
-# Image square thumbnail selection
+# Generic thumbnails selection forms components
 #
 
 @adapter_config(context=(IImage, IAdminLayer, IThumbnailForm), provides=IFormHelp)
@@ -155,6 +148,101 @@
     crop = button.Button(name='crop', title=_("Select thumbnail"))
 
 
+class ImageSelectionThumbnailEditForm(AdminDialogEditForm):
+    """Image portrait thumbnail edit form"""
+
+    dialog_class = 'modal-large'
+
+    fields = field.Fields(Interface)
+    buttons = button.Buttons(IThumbnailFormButtons)
+    edit_permission = None
+
+    selection_name = None
+    selection_ratio = None
+
+    @property
+    def title(self):
+        return self.context.title or self.context.filename
+
+    def updateActions(self):
+        super(ImageSelectionThumbnailEditForm, self).updateActions()
+        if 'crop' in self.actions:
+            self.actions['crop'].addClass('btn-primary')
+
+    def update_content(self, content, data):
+        geometry = ThumbnailGeometry()
+        geometry.x1 = int(self.request.params.get('selection.x1'))
+        geometry.y1 = int(self.request.params.get('selection.y1'))
+        geometry.x2 = int(self.request.params.get('selection.x2'))
+        geometry.y2 = int(self.request.params.get('selection.y2'))
+        IThumbnail(self.context).set_geometry(self.selection_name, geometry)
+
+    def get_ajax_output(self, changes):
+        return {
+            'status': 'success',
+            'message': self.request.localizer.translate(self.successMessage)
+        }
+
+
+@viewlet_config(name='thumbnail.selection.widgets-prefix', context=IImage, layer=IAdminLayer,
+                view=ImageSelectionThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
+@template_config(template='templates/image-selection.pt')
+class ImageSelectionThumbnailViewletsPrefix(Viewlet):
+    """Image square thumbnail viewlets prefix"""
+
+
+#
+# Image portrait thumbnail selection
+#
+
+@viewlet_config(name='image.thumb.portrait.action', context=IImage, layer=IAdminLayer, view=IMediaWidget,
+                manager=IContextActions, weight=55)
+class ImagePortraitThumbnailAction(FileModifierAction):
+    """Portrait thumbnail image selection"""
+
+    _label = _("Select portrait thumbnail...")
+
+    @property
+    def label(self):
+        return '<div>{label}</div><div class="padding-y-5"><img src="{url}/++thumb++portrait:200x128?_=" ' \
+               '/></div>'.format(label=self.request.localizer.translate(self._label),
+                                 url=absolute_url(self.context, self.request))
+
+    label_css_class = 'fa fa-fw-md fa-address-book-o'
+
+    url = 'portrait-thumbnail.html'
+    modal_target = True
+
+
+@pagelet_config(name='portrait-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@ajax_config(name='portrait-thumbnail.json', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@implementer(IThumbnailForm)
+class ImagePortraitThumbnailEditForm(ImageSelectionThumbnailEditForm):
+    """Image portrait thumbnail edit form"""
+
+    prefix = 'image_portrait.'
+
+    legend = _("Select portrait thumbnail")
+    icon_css_class = 'fa fa-fw fa-address-book-o'
+
+    selection_name = 'portrait'
+    selection_ratio = '3:4'
+
+
+@adapter_config(context=(IImage, IAdminLayer, ImagePortraitThumbnailEditForm), provides=IFormHelp)
+class ImagePortraitThumbnailEditFormHelpAdapter(FormHelp):
+    """Portraitramic image thumbnail edit form help adapter"""
+
+    message = _("""You can use this form to select a portrait thumbnail of this image.
+
+**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!""")
+    message_format = 'rest'
+
+
+#
+# Image square thumbnail selection
+#
+
 @viewlet_config(name='image.thumb.square.action', context=IImage, layer=IPyAMSLayer, view=IMediaWidget,
                 manager=IContextActions, weight=60)
 class ImageSquareThumbnailAction(FileModifierAction):
@@ -175,56 +263,18 @@
 
 
 @pagelet_config(name='square-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@ajax_config(name='square-thumbnail.json', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
 @implementer(IThumbnailForm)
-class ImageSquareThumbnailEditForm(AdminDialogEditForm):
+class ImageSquareThumbnailEditForm(ImageSelectionThumbnailEditForm):
     """Image square thumbnail edit form"""
 
     prefix = 'image_square.'
 
     legend = _("Select square thumbnail")
     icon_css_class = 'fa fa-fw fa-instagram'
-    dialog_class = 'modal-large'
 
-    fields = field.Fields(Interface)
-    buttons = button.Buttons(IThumbnailFormButtons)
-    ajax_handler = 'square-thumbnail.json'
-    edit_permission = None
-
-    @property
-    def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ImageSquareThumbnailEditForm, self).updateActions()
-        if 'crop' in self.actions:
-            self.actions['crop'].addClass('btn-primary')
-
-
-@view_config(name='square-thumbnail.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImageSquareThumbnailAJAXEditForm(AJAXEditForm, ImageSquareThumbnailEditForm):
-    """Image square thumbnail edit form, AJAX renderer"""
-
-    def update_content(self, content, data):
-        geometry = ThumbnailGeometry()
-        geometry.x1 = int(self.request.params.get('selection.x1'))
-        geometry.y1 = int(self.request.params.get('selection.y1'))
-        geometry.x2 = int(self.request.params.get('selection.x2'))
-        geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_geometry('square', geometry)
-
-    def get_ajax_output(self, changes):
-        return {
-            'status': 'success',
-            'message': self.request.localizer.translate(self.successMessage)
-        }
-
-
-@viewlet_config(name='square-thumbnail.widgets-prefix', context=IImage, layer=IAdminLayer,
-                view=ImageSquareThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-square-thumbnail.pt')
-class ImageSquareThumbnailViewletsPrefix(Viewlet):
-    """Image square thumbnail viewlets prefix"""
+    selection_name = 'square'
+    selection_ratio = '1:1'
 
 
 @adapter_config(context=(IImage, IAdminLayer, ImageSquareThumbnailEditForm), provides=IFormHelp)
@@ -261,56 +311,18 @@
 
 
 @pagelet_config(name='pano-thumbnail.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@ajax_config(name='pano-thumbnail.json', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
 @implementer(IThumbnailForm)
-class ImagePanoThumbnailEditForm(AdminDialogEditForm):
+class ImagePanoThumbnailEditForm(ImageSelectionThumbnailEditForm):
     """Image panoramic thumbnail edit form"""
 
     prefix = 'image_pano.'
 
     legend = _("Select panoramic thumbnail")
     icon_css_class = 'fa fa-fw fa-youtube-play'
-    dialog_class = 'modal-large'
 
-    fields = field.Fields(Interface)
-    buttons = button.Buttons(IThumbnailFormButtons)
-    ajax_handler = 'pano-thumbnail.json'
-    edit_permission = None
-
-    @property
-    def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ImagePanoThumbnailEditForm, self).updateActions()
-        if 'crop' in self.actions:
-            self.actions['crop'].addClass('btn-primary')
-
-
-@view_config(name='pano-thumbnail.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImagePanoThumbnailAJAXEditForm(AJAXEditForm, ImagePanoThumbnailEditForm):
-    """Image panoramic thumbnail edit form, AJAX renderer"""
-
-    def update_content(self, content, data):
-        geometry = ThumbnailGeometry()
-        geometry.x1 = int(self.request.params.get('selection.x1'))
-        geometry.y1 = int(self.request.params.get('selection.y1'))
-        geometry.x2 = int(self.request.params.get('selection.x2'))
-        geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_geometry('pano', geometry)
-
-    def get_ajax_output(self, changes):
-        return {
-            'status': 'success',
-            'message': self.request.localizer.translate(self.successMessage)
-        }
-
-
-@viewlet_config(name='pano-thumbnail.widgets-prefix', context=IImage, layer=IAdminLayer,
-                view=ImagePanoThumbnailEditForm, manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-pano-thumbnail.pt')
-class ImagePanoThumbnailViewletsPrefix(Viewlet):
-    """Image panoramic thumbnail viewlets prefix"""
+    selection_name = 'pano'
+    selection_ratio = '16:9'
 
 
 @adapter_config(context=(IImage, IAdminLayer, ImagePanoThumbnailEditForm), provides=IFormHelp)
@@ -324,64 +336,6 @@
 
 
 #
-# Image responsive selections
-#
-
-class IResponsiveImageSelectionFormButtons(Interface):
-    """Responsive image selection form buttons"""
-
-    close = CloseButton(name='close', title=_("Close"))
-    select = button.Button(name='select', title=_("Select thumbnail"))
-
-
-@implementer(IThumbnailForm)
-class ResponsiveImageSelectionForm(AdminDialogEditForm):
-    """Base responsive image selection edit form"""
-
-    prefix = 'image_responsive.'
-
-    dialog_class = 'modal-large'
-
-    fields = field.Fields(Interface)
-    buttons = button.Buttons(IResponsiveImageSelectionFormButtons)
-    edit_permission = None
-
-    @property
-    def title(self):
-        return self.context.title or self.context.filename
-
-    def updateActions(self):
-        super(ResponsiveImageSelectionForm, self).updateActions()
-        if 'select' in self.actions:
-            self.actions['select'].addClass('btn-primary')
-
-
-class ResponsiveImageSelectionAJAXForm(AJAXEditForm):
-    """Base responsive image selection edit form, JSON renderer"""
-
-    def update_content(self, content, data):
-        geometry = ThumbnailGeometry()
-        geometry.x1 = int(self.request.params.get('selection.x1'))
-        geometry.y1 = int(self.request.params.get('selection.y1'))
-        geometry.x2 = int(self.request.params.get('selection.x2'))
-        geometry.y2 = int(self.request.params.get('selection.y2'))
-        IThumbnail(self.context).set_geometry(self.selection_size, geometry)
-
-    def get_ajax_output(self, changes):
-        return {
-            'status': 'success',
-            'message': self.request.localizer.translate(self.successMessage)
-        }
-
-
-@viewlet_config(name='responsive-image.selection.widgets-prefix', context=IResponsiveImage, layer=IAdminLayer,
-                view=ResponsiveImageSelectionForm, manager=IWidgetsPrefixViewletsManager)
-@template_config(template='templates/image-selection.pt')
-class ResponsiveImageSelectionViewletsPrefix(Viewlet):
-    """Responsive image selection viewlets prefix"""
-
-
-#
 # Extra-small devices selection
 #
 
@@ -406,20 +360,15 @@
 
 @pagelet_config(name='selection-xs.html', context=IResponsiveImage, layer=IPyAMSLayer,
                 permission=VIEW_PERMISSION)
-class ResponsiveImageXsSelectionForm(ResponsiveImageSelectionForm):
+@ajax_config(name='selection-xs.json', context=IResponsiveImage, layer=IPyAMSLayer,
+             permission=VIEW_PERMISSION)
+class ResponsiveImageXsSelectionForm(ImageSelectionThumbnailEditForm):
     """Responsive image XS selection edit form"""
 
     legend = _("Select image for extra-small (XS) devices")
     icon_css_class = 'fa fa-fw fa-mobile'
 
-    selection_size = 'xs'
-    ajax_handler = 'selection-xs.json'
-
-
-@view_config(name='selection-xs.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageXsSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageXsSelectionForm):
-    """Responsive image XS selection edit form, JSON renderer"""
+    selection_name = 'xs'
 
 
 #
@@ -447,20 +396,15 @@
 
 @pagelet_config(name='selection-sm.html', context=IResponsiveImage, layer=IPyAMSLayer,
                 permission=VIEW_PERMISSION)
-class ResponsiveImageSmSelectionForm(ResponsiveImageSelectionForm):
+@ajax_config(name='selection-sm.json', context=IResponsiveImage, layer=IPyAMSLayer,
+             permission=VIEW_PERMISSION)
+class ResponsiveImageSmSelectionForm(ImageSelectionThumbnailEditForm):
     """Responsive image SM selection edit form"""
 
     legend = _("Select image for small (SM) devices")
     icon_css_class = 'fa fa-fw fa-tablet'
 
-    selection_size = 'sm'
-    ajax_handler = 'selection-sm.json'
-
-
-@view_config(name='selection-sm.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageSmSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageSmSelectionForm):
-    """Responsive image SM selection edit form, JSON renderer"""
+    selection_name = 'sm'
 
 
 #
@@ -488,20 +432,15 @@
 
 @pagelet_config(name='selection-md.html', context=IResponsiveImage, layer=IPyAMSLayer,
                 permission=VIEW_PERMISSION)
-class ResponsiveImageMdSelectionForm(ResponsiveImageSelectionForm):
+@ajax_config(name='selection-md.json', context=IResponsiveImage, layer=IPyAMSLayer,
+             permission=VIEW_PERMISSION)
+class ResponsiveImageMdSelectionForm(ImageSelectionThumbnailEditForm):
     """Responsive image MD selection edit form"""
 
     legend = _("Select image for medium (MD) devices")
     icon_css_class = 'fa fa-fw fa-desktop'
 
-    selection_size = 'md'
-    ajax_handler = 'selection-md.json'
-
-
-@view_config(name='selection-md.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageMdSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageMdSelectionForm):
-    """Responsive image MD selection edit form, JSON renderer"""
+    selection_name = 'md'
 
 
 #
@@ -529,20 +468,15 @@
 
 @pagelet_config(name='selection-lg.html', context=IResponsiveImage, layer=IPyAMSLayer,
                 permission=VIEW_PERMISSION)
-class ResponsiveImageLgSelectionForm(ResponsiveImageSelectionForm):
+@ajax_config(name='selection-lg.json', context=IResponsiveImage, layer=IPyAMSLayer,
+             permission=VIEW_PERMISSION)
+class ResponsiveImageLgSelectionForm(ImageSelectionThumbnailEditForm):
     """Responsive image LG selection edit form"""
 
     legend = _("Select image for large (LG) devices")
     icon_css_class = 'fa fa-fw fa-television'
 
-    selection_size = 'lg'
-    ajax_handler = 'selection-lg.json'
-
-
-@view_config(name='selection-lg.json', context=IResponsiveImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ResponsiveImageLgSelectionAJAXEditForm(ResponsiveImageSelectionAJAXForm, ResponsiveImageLgSelectionForm):
-    """Responsive image LG selection edit form, JSON renderer"""
+    selection_name = 'lg'
 
 
 #
@@ -663,6 +597,7 @@
 
 
 @pagelet_config(name='resize.html', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
+@ajax_config(name='resize.json', context=IImage, layer=IPyAMSLayer, permission=VIEW_PERMISSION)
 @implementer(IFileModifierForm)
 class ImageResizeForm(AdminDialogEditForm):
     """Image resize form"""
@@ -674,7 +609,6 @@
 
     fields = field.Fields(IImageResizeInfo)
     buttons = button.Buttons(IResizeFormButtons)
-    ajax_handler = 'resize.json'
     edit_permission = None
 
     @property
@@ -686,12 +620,6 @@
         if 'resize' in self.actions:
             self.actions['resize'].addClass('btn-primary')
 
-
-@view_config(name='resize.json', context=IImage, request_type=IPyAMSLayer,
-             permission=VIEW_PERMISSION, renderer='json', xhr=True)
-class ImageResizeAJAXForm(AJAXEditForm, ImageResizeForm):
-    """Image resize form, AJAX renderer"""
-
     def update_content(self, content, data):
         data = data.get(self, data)
         self.context.resize(data.get('width'), data.get('height'), data.get('keep_ratio'))
--- a/src/pyams_file/zmi/templates/image-pano-thumbnail.pt	Tue May 29 09:51:18 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-<tal:var define="thumbnails extension:thumbnails(context);
-				 image thumbnails.get_thumbnail('800x480');
-				 size context.get_image_size();
-				 thumb_size image.get_image_size();
-				 geometry thumbnails.get_geometry('pano');">
-	<input type="hidden" name="selection.x1" tal:attributes="value geometry.x1" />
-	<input type="hidden" name="selection.y1" tal:attributes="value geometry.x2" />
-	<input type="hidden" name="selection.x2" tal:attributes="value geometry.y1" />
-	<input type="hidden" name="selection.y2" tal:attributes="value geometry.y2" />
- 	<img class="imgareaselect"
-		 data-ams-imgareaselect-parent=".modal-dialog"
-		 data-ams-imgareaselect-target-field="selection."
-		 data-ams-imgareaselect-ratio="16:9"
-		 tal:attributes="src extension:absolute_url(image);
-						 width thumb_size[0];
-						 height thumb_size[1];
-						 data-ams-imgareaselect-image-width size[0];
-						 data-ams-imgareaselect-image-height size[1];
-						 data-ams-imgareaselect-x1 geometry.x1;
-						 data-ams-imgareaselect-y1 geometry.y1;
-						 data-ams-imgareaselect-x2 geometry.x2;
-						 data-ams-imgareaselect-y2 geometry.y2;" />
-</tal:var>
--- a/src/pyams_file/zmi/templates/image-selection.pt	Tue May 29 09:51:18 2018 +0200
+++ b/src/pyams_file/zmi/templates/image-selection.pt	Fri Jun 08 14:58:57 2018 +0200
@@ -2,7 +2,7 @@
 				 image thumbnails.get_thumbnail('800x480');
 				 size context.get_image_size();
 				 thumb_size image.get_image_size();
-				 geometry thumbnails.get_geometry(view.__parent__.selection_size);">
+				 geometry thumbnails.get_geometry(view.__parent__.selection_name);">
 	<input type="hidden" name="selection.x1" tal:attributes="value geometry.x1" />
 	<input type="hidden" name="selection.y1" tal:attributes="value geometry.x2" />
 	<input type="hidden" name="selection.x2" tal:attributes="value geometry.y1" />
@@ -13,6 +13,7 @@
 		 tal:attributes="src extension:absolute_url(image);
 						 width thumb_size[0];
 						 height thumb_size[1];
+						 data-ams-imgareaselect-ratio view.__parent__.selection_ratio;
 						 data-ams-imgareaselect-image-width size[0];
 						 data-ams-imgareaselect-image-height size[1];
 						 data-ams-imgareaselect-x1 geometry.x1;
--- a/src/pyams_file/zmi/templates/image-square-thumbnail.pt	Tue May 29 09:51:18 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-<tal:var define="thumbnails extension:thumbnails(context);
-				 image thumbnails.get_thumbnail('800x480');
-				 size context.get_image_size();
-				 thumb_size image.get_image_size();
-				 geometry thumbnails.get_geometry('square');">
-	<input type="hidden" name="selection.x1" tal:attributes="value geometry.x1" />
-	<input type="hidden" name="selection.y1" tal:attributes="value geometry.x2" />
-	<input type="hidden" name="selection.x2" tal:attributes="value geometry.y1" />
-	<input type="hidden" name="selection.y2" tal:attributes="value geometry.y2" />
- 	<img class="imgareaselect"
-		 data-ams-imgareaselect-parent=".modal-dialog"
-		 data-ams-imgareaselect-target-field="selection."
-		 data-ams-imgareaselect-ratio="1:1"
-		 tal:attributes="src extension:absolute_url(image);
-						 width thumb_size[0];
-						 height thumb_size[1];
-						 data-ams-imgareaselect-image-width size[0];
-						 data-ams-imgareaselect-image-height size[1];
-						 data-ams-imgareaselect-x1 geometry.x1;
-						 data-ams-imgareaselect-y1 geometry.y1;
-						 data-ams-imgareaselect-x2 geometry.x2;
-						 data-ams-imgareaselect-y2 geometry.y2;" />
-</tal:var>