001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.galleries.client.preview; 029 030import org.opencms.ade.galleries.client.preview.ui.CmsImagePreviewDialog; 031import org.opencms.ade.galleries.shared.CmsImageInfoBean; 032import org.opencms.gwt.client.CmsCoreProvider; 033import org.opencms.gwt.client.util.CmsClientStringUtil; 034import org.opencms.gwt.client.util.I_CmsSimpleCallback; 035import org.opencms.util.CmsStringUtil; 036 037import java.util.ArrayList; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Map; 041 042import com.google.gwt.event.logical.shared.ValueChangeEvent; 043import com.google.gwt.event.logical.shared.ValueChangeHandler; 044import com.google.gwt.user.client.ui.Image; 045import com.google.gwt.user.client.ui.Widget; 046 047import elemental2.dom.HTMLImageElement; 048import jsinterop.base.Js; 049 050/** 051 * Image preview dialog controller handler.<p> 052 * 053 * Delegates the actions of the preview controller to the preview dialog. 054 * 055 * @since 8.0.0 056 */ 057public class CmsImagePreviewHandler extends A_CmsPreviewHandler<CmsImageInfoBean> 058implements ValueChangeHandler<CmsCroppingParamBean> { 059 060 /** Enumeration of image tag attribute names. */ 061 public enum Attribute { 062 /** Image align attribute. */ 063 align, 064 /** Image alt attribute. */ 065 alt, 066 /** Image class attribute. */ 067 clazz, 068 /** Image copyright info. */ 069 copyright, 070 /** Image direction attribute. */ 071 dir, 072 /** No image selected if this attribute is present. */ 073 emptySelection, 074 /** The image hash. */ 075 hash, 076 /** Image height attribute. */ 077 height, 078 /** Image hspace attribute. */ 079 hspace, 080 /** Image id attribute. */ 081 id, 082 /** Image copyright flag. */ 083 insertCopyright, 084 /** Image link original flag. */ 085 insertLinkOrig, 086 /** Image spacing flag. */ 087 insertSpacing, 088 /** Image subtitle flag. */ 089 insertSubtitle, 090 /** Image language attribute. */ 091 lang, 092 /** Image link path. */ 093 linkPath, 094 /** Image link target. */ 095 linkTarget, 096 /** Image longDesc attribute. */ 097 longDesc, 098 /** Image style attribute. */ 099 style, 100 /** Image title attribute. */ 101 title, 102 /** Image vspace attribute. */ 103 vspace, 104 /** Image width attribute. */ 105 width 106 } 107 108 /** 109 * Encapsulates information used to update the preview image's scaling parameters. 110 */ 111 public static class PreviewImageUpdate { 112 113 /** Normal height. */ 114 private int m_height; 115 116 /** High resolution scaling parameters. */ 117 private String m_highResPreview; 118 119 /** Normal preview scaling parameters. */ 120 private String m_preview; 121 122 /** Normal width. */ 123 private int m_width; 124 125 /** 126 * Creates a new instance. 127 * 128 * @param preview the normal preview scaling parameters 129 * @param highResPreview the high resolution scaling parameters 130 * @param width the normal width 131 * @param height the normal height 132 */ 133 public PreviewImageUpdate(String preview, String highResPreview, int width, int height) { 134 135 super(); 136 m_preview = preview; 137 m_highResPreview = highResPreview; 138 m_width = width; 139 m_height = height; 140 } 141 142 /** 143 * Updates the given image with information from this object. 144 * 145 * @param image the image to update 146 * @param src the image base URL 147 * @param isSvg true if the image is an SVG 148 */ 149 public void applyToImage(Image image, String src, boolean isSvg, Widget container) { 150 151 HTMLImageElement imgElement = Js.cast(image.getElement()); 152 long time = System.currentTimeMillis(); 153 if (!isSvg) { 154 int parentWidth = container.getElement().getClientWidth(); 155 int parentHeight = container.getElement().getClientHeight(); 156 int effectiveWidth = m_width; 157 if ((parentWidth > effectiveWidth) && ((parentWidth - effectiveWidth) <= 3)) { 158 effectiveWidth = parentWidth; 159 } 160 int effectiveHeight = m_height; 161 if ((parentHeight > effectiveHeight) && ((parentHeight - effectiveHeight) <= 3)) { 162 effectiveHeight = parentHeight; 163 } 164 165 imgElement.setAttribute("width", "" + effectiveWidth); 166 imgElement.setAttribute("height", "" + effectiveHeight); 167 } 168 imgElement.src = src + "?" + appendQuality(m_preview) + "&time=" + time; 169 imgElement.removeAttribute("srcset"); 170 if (!isSvg) { 171 if (m_highResPreview != null) { 172 imgElement.srcset = src + "?" + appendQuality(m_highResPreview) + "&time=" + time + " 2x"; 173 } 174 } 175 176 } 177 178 } 179 180 /** The image container height. */ 181 private int m_containerHeight; 182 183 /** The image container width. */ 184 private int m_containerWidth; 185 186 /** List of handlers for cropping changes. */ 187 private List<Runnable> m_croppingHandlers = new ArrayList<>(); 188 189 /** The cropping parameter. */ 190 private CmsCroppingParamBean m_croppingParam; 191 192 /** The image format handler. */ 193 private CmsImageFormatHandler m_formatHandler; 194 195 /** List of handlers for focal point changes. */ 196 private List<Runnable> m_imagePointHandlers = new ArrayList<>(); 197 198 /** The focal point controller. */ 199 private CmsFocalPointController m_pointController; 200 201 /** The preview dialog. */ 202 private CmsImagePreviewDialog m_previewDialog; 203 204 /** 205 * Constructor.<p> 206 * 207 * @param resourcePreview the resource preview instance 208 */ 209 public CmsImagePreviewHandler(CmsImageResourcePreview resourcePreview) { 210 211 super(resourcePreview); 212 m_previewDialog = resourcePreview.getPreviewDialog(); 213 m_pointController = new CmsFocalPointController( 214 () -> m_croppingParam, 215 this::getImageInfo, 216 this::onImagePointChanged); 217 } 218 219 /** 220 * Appends quality parameter to a set of scaling parameters, unless the input is the empty string or already contains a quality parameter. 221 * 222 * @param text the input scaling parameters 223 * @return the modified scaling parameters 224 */ 225 public static final String appendQuality(String text) { 226 227 if (CmsStringUtil.isEmpty(text) || text.contains("q:")) { 228 return text; 229 } else { 230 return text + ",q:85"; 231 } 232 } 233 234 /** 235 * Adds a handler for cropping changes.<p> 236 * 237 * @param action the handler to add 238 */ 239 public void addCroppingChangeHandler(Runnable action) { 240 241 m_croppingHandlers.add(action); 242 } 243 244 /** 245 * Adds a handler for focal point changes.<p> 246 * 247 * @param onImagePointChanged the handler to add 248 */ 249 public void addImagePointChangeHandler(Runnable onImagePointChanged) { 250 251 m_imagePointHandlers.add(onImagePointChanged); 252 } 253 254 /** 255 * Returns the image cropping parameter bean.<p> 256 * 257 * @return the image cropping parameter bean 258 */ 259 public CmsCroppingParamBean getCroppingParam() { 260 261 return m_croppingParam; 262 } 263 264 /** 265 * Gets the focal point controller.<p> 266 * 267 * @return the focal point controller 268 */ 269 public CmsFocalPointController getFocalPointController() { 270 271 return m_pointController; 272 } 273 274 /** 275 * Gets the format handler.<p> 276 * 277 * @return the format handler 278 */ 279 public CmsImageFormatHandler getFormatHandler() { 280 281 return m_formatHandler; 282 283 } 284 285 /** 286 * Returns the name of the currently selected image format.<p> 287 * 288 * @return the format name 289 */ 290 public String getFormatName() { 291 292 String result = ""; 293 if ((m_formatHandler != null) && (m_formatHandler.getCurrentFormat() != null)) { 294 result = m_formatHandler.getCurrentFormat().getName(); 295 } 296 return result; 297 } 298 299 /** 300 * Returns image tag attributes to set for editor plugins.<p> 301 * 302 * @param callback the callback to execute 303 */ 304 public void getImageAttributes(I_CmsSimpleCallback<Map<String, String>> callback) { 305 306 Map<String, String> result = new HashMap<String, String>(); 307 result.put(Attribute.hash.name(), String.valueOf(getImageIdHash())); 308 m_formatHandler.getImageAttributes(result); 309 m_previewDialog.getImageAttributes(result, callback); 310 } 311 312 /** 313 * Returns the structure id hash of the previewed image.<p> 314 * 315 * @return the structure id hash 316 */ 317 public int getImageIdHash() { 318 319 return m_resourceInfo.getHash(); 320 } 321 322 /** 323 * Gets the image information.<p> 324 * 325 * @return the image information 326 */ 327 public CmsImageInfoBean getImageInfo() { 328 329 return m_resourceInfo; 330 } 331 332 /** 333 * Gets the information to update the preview image. 334 * 335 * @param imageHeight the original image height 336 * @param imageWidth the original image width 337 * @return the preview update information 338 */ 339 public PreviewImageUpdate getPreviewImageUpdate(int imageHeight, int imageWidth) { 340 341 String lowRes = getPreviewScaleParam(imageHeight, imageWidth, 1); 342 String highRes = getPreviewScaleParam(imageHeight, imageWidth, 2); 343 Map<String, String> lowResMap = parseScalingParams(lowRes); 344 int wLow = getScalerParameter(lowResMap, "w", imageWidth); 345 int hLow = getScalerParameter(lowResMap, "h", imageHeight); 346 return new PreviewImageUpdate(lowRes, highRes, wLow, hLow); 347 348 } 349 350 /** 351 * Returns the cropping parameter.<p> 352 * 353 * @param imageHeight the original image height 354 * @param imageWidth the original image width 355 * @param density the pixel density (acts as a multiplier for available space) 356 * 357 * @return the cropping parameter 358 */ 359 public String getPreviewScaleParam(int imageHeight, int imageWidth, int density) { 360 361 int maxHeight = m_containerHeight * density; 362 int maxWidth = m_containerWidth * density; 363 364 if ((m_croppingParam != null) && (m_croppingParam.isCropped() || m_croppingParam.isScaled())) { 365 // NOTE: getREstrictedSizeScaleParam does not work correctly if there isn't actually any cropping/scaling, so we explicitly don't use it in this case 366 return m_croppingParam.getRestrictedSizeScaleParam(maxHeight, maxWidth); 367 } 368 if ((imageHeight <= maxHeight) && (imageWidth <= maxWidth)) { 369 return ""; // dummy parameter, doesn't actually do anything 370 } 371 CmsCroppingParamBean restricted = new CmsCroppingParamBean(); 372 373 boolean tooHigh = imageHeight > maxHeight; 374 boolean tooWide = imageWidth > maxWidth; 375 double shrinkX = (1.0 * imageWidth) / maxWidth; 376 double shrinkY = (1.0 * imageHeight) / maxHeight; 377 double aspectRatio = (1.0 * imageWidth) / imageHeight; 378 if (tooHigh && tooWide) { 379 if (shrinkX > shrinkY) { 380 restricted.setTargetWidth(maxWidth); 381 restricted.setTargetHeight((int)(maxWidth / aspectRatio)); 382 } else { 383 restricted.setTargetHeight(maxHeight); 384 restricted.setTargetWidth((int)(maxHeight * aspectRatio)); 385 } 386 } else if (tooWide) { 387 restricted.setTargetWidth(maxWidth); 388 restricted.setTargetHeight((int)(maxWidth / aspectRatio)); 389 } else if (tooHigh) { 390 restricted.setTargetHeight(maxHeight); 391 restricted.setTargetWidth((int)(maxHeight * aspectRatio)); 392 } else { 393 restricted.setTargetWidth(imageWidth); 394 restricted.setTargetHeight(imageHeight); 395 } 396 return restricted.toString(); 397 } 398 399 /** 400 * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent) 401 */ 402 public void onValueChange(ValueChangeEvent<CmsCroppingParamBean> event) { 403 404 m_croppingParam = event.getValue(); 405 String viewLink = m_resourcePreview.getViewLink(); 406 if (viewLink == null) { 407 viewLink = CmsCoreProvider.get().link(m_resourcePreview.getResourcePath()); 408 } 409 PreviewImageUpdate previewUpdate = getPreviewImageUpdate( 410 m_croppingParam.getOrgHeight(), 411 m_croppingParam.getOrgWidth()); 412 boolean isSvg = CmsClientStringUtil.checkIsPathOrLinkToSvg(m_resourcePreview.getResourcePath()); 413 previewUpdate.applyToImage( 414 m_previewDialog.getPreviewImage(), 415 viewLink, 416 isSvg, 417 m_previewDialog.getPreviewImage().getParent()); 418 onCroppingChanged(); 419 } 420 421 /** 422 * Sets the image format handler.<p> 423 * 424 * @param formatHandler the format handler 425 */ 426 public void setFormatHandler(CmsImageFormatHandler formatHandler) { 427 428 m_formatHandler = formatHandler; 429 m_croppingParam = m_formatHandler.getCroppingParam(); 430 m_formatHandler.addValueChangeHandler(this); 431 onCroppingChanged(); 432 } 433 434 /** 435 * 436 * Sets the dimensions of the area the image is going to be placed in. 437 * 438 * @param offsetWidth the container width 439 * @param offsetHeight the container height 440 */ 441 public void setImageContainerSize(int offsetWidth, int offsetHeight) { 442 443 m_containerWidth = offsetWidth; 444 m_containerHeight = offsetHeight; 445 } 446 447 /** 448 * Helper method for getting an integer-valued scaler parameter from a map of parameters, with a default value that should be returned if the map doesn't contain the parameter. 449 * 450 * @param scalerParams the map of scaler parameters 451 * @param key the map key 452 * @param defaultValue the value to return if the map doesn't contain a value for the key 453 * 454 * @return the value of the scaler parameter 455 */ 456 private int getScalerParameter(Map<String, String> scalerParams, String key, int defaultValue) { 457 458 String value = scalerParams.get(key); 459 if (value != null) { 460 return Integer.parseInt(value); 461 } else { 462 return defaultValue; 463 } 464 } 465 466 /** 467 * Calls all cropping change handlers. 468 */ 469 private void onCroppingChanged() { 470 471 for (Runnable action : m_croppingHandlers) { 472 action.run(); 473 } 474 } 475 476 /** 477 * Calls all focal point change handlers.<p> 478 */ 479 private void onImagePointChanged() { 480 481 for (Runnable handler : m_imagePointHandlers) { 482 handler.run(); 483 } 484 485 } 486 487 /** 488 * Parse scaling parameters as a map. 489 * 490 * @param params the scaling parameters 491 * @return the scaling parameters as a map 492 */ 493 private Map<String, String> parseScalingParams(String params) { 494 495 final String prefix = "__scale="; 496 if (params.startsWith(prefix)) { 497 params = params.substring(prefix.length()); 498 } 499 return CmsStringUtil.splitAsMap(params, ",", ":"); 500 } 501 502}