001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.Messages; 031import org.opencms.ade.galleries.client.preview.ui.CmsCroppingDialog; 032import org.opencms.ade.galleries.client.preview.ui.CmsImageFormatsForm; 033import org.opencms.ade.galleries.client.ui.CmsGalleryDialog; 034import org.opencms.ade.galleries.shared.I_CmsGalleryProviderConstants.GalleryMode; 035import org.opencms.gwt.client.util.CmsClientStringUtil; 036import org.opencms.util.CmsStringUtil; 037 038import java.util.Collections; 039import java.util.LinkedHashMap; 040import java.util.Map; 041import java.util.Map.Entry; 042 043import com.google.gwt.event.logical.shared.HasValueChangeHandlers; 044import com.google.gwt.event.logical.shared.ValueChangeEvent; 045import com.google.gwt.event.logical.shared.ValueChangeHandler; 046import com.google.gwt.event.shared.EventHandler; 047import com.google.gwt.event.shared.GwtEvent; 048import com.google.gwt.event.shared.HandlerRegistration; 049import com.google.gwt.event.shared.SimpleEventBus; 050 051/** 052 * Image format form handler.<p> 053 * 054 * @since 8.0.0 055 */ 056public class CmsImageFormatHandler implements HasValueChangeHandlers<CmsCroppingParamBean> { 057 058 /** Default image formats. */ 059 private enum DefaultRestriction { 060 /** Big image format. */ 061 big, 062 /** Free image format. */ 063 free, 064 /** Original format. */ 065 original, 066 /** Small image format. */ 067 small, 068 /** User defined image format. */ 069 user 070 } 071 072 /** Default format configuration. */ 073 private static final String[] DEFAULT_FORMAT_NAMES = { 074 DefaultRestriction.original.name() + ":" + Messages.get().key(Messages.GUI_IMAGE_ORIGINAL_FORMAT_LABEL_0), 075 DefaultRestriction.user.name() + ":" + Messages.get().key(Messages.GUI_IMAGE_USER_FORMAT_LABEL_0), 076 DefaultRestriction.free.name() + ":" + Messages.get().key(Messages.GUI_IMAGE_FREE_FORMAT_LABEL_0), 077 DefaultRestriction.small.name() + ":" + Messages.get().key(Messages.GUI_IMAGE_SMALL_FORMAT_LABEL_0), 078 DefaultRestriction.big.name() + ":" + Messages.get().key(Messages.GUI_IMAGE_BIG_FORMAT_LABEL_0)}; 079 080 /** Default format configuration. */ 081 private static final String[] DEFAULT_FORMAT_VALUES = { 082 DefaultRestriction.original.name(), 083 DefaultRestriction.user.name(), 084 DefaultRestriction.free.name(), 085 DefaultRestriction.small.name(), 086 DefaultRestriction.big.name()}; 087 088 /** The cropping dialog instance. */ 089 private CmsCroppingDialog m_croppingDialog; 090 091 /** The current cropping parameter. */ 092 private CmsCroppingParamBean m_croppingParam; 093 094 /** The current image format restriction. */ 095 private I_CmsFormatRestriction m_currentFormat; 096 097 /** The event bus. */ 098 private SimpleEventBus m_eventBus; 099 100 /** The format form. */ 101 private CmsImageFormatsForm m_formatForm; 102 103 /** The format names and labels configuration. */ 104 private String[] m_formatNames; 105 106 /** The map of available format restrictions. */ 107 private Map<String, I_CmsFormatRestriction> m_formats = Collections.emptyMap(); 108 109 /** The Format configuration. */ 110 private String[] m_formatValues; 111 112 /** Flag to indicate the handler has been initialized. */ 113 private boolean m_initialized; 114 115 /** The image height. */ 116 private int m_originalHeight = -1; 117 118 /** The image width. */ 119 private int m_originalWidth = -1; 120 121 /** Flag indicating if the height / width ratio is locked. */ 122 private boolean m_ratioLocked; 123 124 /** Flag to indicate if image format may be changed. */ 125 private boolean m_useFormats; 126 127 /** The user format key, if available. */ 128 private String m_userFormatKey; 129 130 /** 131 * Constructor.<p> 132 * 133 * @param galleryMode the gallery mode 134 * @param dialog the gallery dialog 135 * @param selectedPath the selected gallery path 136 * @param imageHeight the image height 137 * @param imageWidth the image width 138 */ 139 public CmsImageFormatHandler( 140 GalleryMode galleryMode, 141 CmsGalleryDialog dialog, 142 String selectedPath, 143 int imageHeight, 144 int imageWidth) { 145 146 m_originalHeight = imageHeight; 147 m_originalWidth = imageWidth; 148 m_croppingParam = CmsCroppingParamBean.parseImagePath(selectedPath); 149 m_croppingParam.setOrgHeight(imageHeight); 150 m_croppingParam.setOrgWidth(imageWidth); 151 m_ratioLocked = true; 152 m_useFormats = dialog.isUseFormats(); 153 if (m_useFormats) { 154 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(dialog.getImageFormats())) { 155 m_formatValues = dialog.getImageFormats().split(","); 156 } 157 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(dialog.getImageFormatNames())) { 158 m_formatNames = dialog.getImageFormatNames().split(","); 159 } 160 } 161 readFormatsConfig(galleryMode, dialog.isNativeWidget(), dialog.isOverrideFormats()); 162 if (m_useFormats) { 163 generateFormats(); 164 } 165 } 166 167 /** 168 * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) 169 */ 170 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<CmsCroppingParamBean> handler) { 171 172 return addHandler(handler, ValueChangeEvent.getType()); 173 } 174 175 /** 176 * @see com.google.gwt.event.shared.HasHandlers#fireEvent(com.google.gwt.event.shared.GwtEvent) 177 */ 178 public void fireEvent(GwtEvent<?> event) { 179 180 ensureHandlers().fireEventFromSource(event, this); 181 } 182 183 /** 184 * Returns the current cropping parameter.<p> 185 * 186 * @return the current cropping parameter 187 */ 188 public CmsCroppingParamBean getCroppingParam() { 189 190 return m_croppingParam; 191 } 192 193 /** 194 * Returns the current format.<p> 195 * 196 * @return the current format 197 */ 198 public I_CmsFormatRestriction getCurrentFormat() { 199 200 return m_currentFormat; 201 } 202 203 /** 204 * Returns the formats.<p> 205 * 206 * @return the formats 207 */ 208 public Map<String, I_CmsFormatRestriction> getFormats() { 209 210 return m_formats; 211 } 212 213 /** 214 * Adds necessary attributes to the map.<p> 215 * 216 * @param attributes the attribute map 217 * @return the attribute map 218 */ 219 public Map<String, String> getImageAttributes(Map<String, String> attributes) { 220 221 attributes.put("height", String.valueOf(m_croppingParam.getResultingHeight())); 222 attributes.put("width", String.valueOf(m_croppingParam.getResultingWidth())); 223 return attributes; 224 } 225 226 /** 227 * Returns the original height.<p> 228 * 229 * @return the original height 230 */ 231 public int getOriginalHeight() { 232 233 return m_originalHeight; 234 } 235 236 /** 237 * Returns the original width.<p> 238 * 239 * @return the original width 240 */ 241 public int getOriginalWidth() { 242 243 return m_originalWidth; 244 } 245 246 /** 247 * Initializes the format form handler.<p> 248 * 249 * @param formatForm the format form 250 * @param croppingDialog the cropping dialog 251 */ 252 public void init(CmsImageFormatsForm formatForm, CmsCroppingDialog croppingDialog) { 253 254 m_croppingDialog = croppingDialog; 255 m_formatForm = formatForm; 256 if (m_useFormats) { 257 for (Entry<String, I_CmsFormatRestriction> entry : m_formats.entrySet()) { 258 m_formatForm.addFormatSelectOption(entry.getKey(), entry.getValue().getLabel()); 259 } 260 I_CmsFormatRestriction match = getMatchingFormat(m_croppingParam, true); 261 if (match != null) { 262 m_currentFormat = match; 263 m_formatForm.setFormatSelectValue(match.getName()); 264 adjustToCurrentFormat(); 265 } else { 266 onResetSize(); 267 } 268 } else { 269 m_formatForm.addFormatSelectOption("--", "--"); 270 m_formatForm.setFormEnabled(m_useFormats); 271 } 272 if (m_croppingParam.isCropped()) { 273 setCropping(m_croppingParam); 274 } 275 m_croppingDialog.addValueChangeHandler(new ValueChangeHandler<CmsCroppingParamBean>() { 276 277 /** 278 * Executed on value change. Sets the returned cropping parameters.<p> 279 * 280 * @param event the value change event 281 */ 282 public void onValueChange(ValueChangeEvent<CmsCroppingParamBean> event) { 283 284 setCropping(event.getValue()); 285 } 286 }); 287 m_initialized = true; 288 } 289 290 /** 291 * Returns if scaling formats may be selected for the image.<p> 292 * 293 * @return <code>true</code> if scaling formats may be selected for the image 294 */ 295 public boolean isUseFormats() { 296 297 return m_useFormats; 298 } 299 300 /** 301 * Execute on format change.<p> 302 * 303 * @param formatKey the new format value 304 */ 305 public void onFormatChange(String formatKey) { 306 307 // setting the selected format restriction 308 m_currentFormat = m_formats.get(formatKey); 309 m_currentFormat.adjustCroppingParam(m_croppingParam); 310 adjustToCurrentFormat(); 311 if (m_initialized) { 312 // fire change only if initialized 313 fireValueChangedEvent(); 314 } 315 } 316 317 /** 318 * Execute on height change.<p> 319 * 320 * @param height the new height 321 */ 322 public void onHeightChange(String height) { 323 324 int value = CmsClientStringUtil.parseInt(height); 325 if ((m_croppingParam.getTargetHeight() == value) || (value == 0)) { 326 // the value has not changed, ignore'0' 327 return; 328 } 329 m_croppingParam.setTargetHeight(value); 330 if (m_ratioLocked) { 331 m_croppingParam.setTargetWidth((value * m_originalWidth) / m_originalHeight); 332 m_formatForm.setWidthInput(m_croppingParam.getTargetWidth()); 333 } 334 // in case the width and height parameter don't match the current format any longer, switch to user defined format 335 if ((!m_currentFormat.isHeightEditable() || (m_ratioLocked && !m_currentFormat.isWidthEditable())) 336 && hasUserFormatRestriction()) { 337 m_formatForm.setFormatSelectValue(m_userFormatKey); 338 } else { 339 fireValueChangedEvent(); 340 } 341 } 342 343 /** 344 * Execute when the lock image ratio is clicked.<p> 345 * 346 * @param locked <code>true</code> if ratio is locked 347 */ 348 public void onLockRatio(boolean locked) { 349 350 m_ratioLocked = locked; 351 } 352 353 /** 354 * Execute when cropping is removed.<p> 355 */ 356 public void onRemoveCropping() { 357 358 m_formatForm.setCropped(false); 359 onResetSize(); 360 } 361 362 /** 363 * Execute to reset image format and size input.<p> 364 */ 365 public void onResetSize() { 366 367 String restrictionKey; 368 if (m_formats.containsKey(DefaultRestriction.original.name())) { 369 restrictionKey = DefaultRestriction.original.name(); 370 } else { 371 restrictionKey = m_formats.keySet().iterator().next(); 372 } 373 m_formatForm.setFormatSelectValue(restrictionKey); 374 m_croppingParam.reset(); 375 onFormatChange(restrictionKey); 376 } 377 378 /** 379 * Execute on width change.<p> 380 * 381 * @param width the new width 382 */ 383 public void onWidthChange(String width) { 384 385 int value = CmsClientStringUtil.parseInt(width); 386 if ((m_croppingParam.getTargetWidth() == value) || (value == 0)) { 387 // the value has not changed, ignore'0' 388 return; 389 } 390 m_croppingParam.setTargetWidth(value); 391 if (m_ratioLocked) { 392 m_croppingParam.setTargetHeight((value * m_originalHeight) / m_originalWidth); 393 m_formatForm.setHeightInput(m_croppingParam.getTargetHeight()); 394 } 395 // in case the width and height parameter don't match the current format any longer, switch to user defined format 396 if ((!m_currentFormat.isWidthEditable() || (m_ratioLocked && !m_currentFormat.isHeightEditable())) 397 && hasUserFormatRestriction()) { 398 m_formatForm.setFormatSelectValue(m_userFormatKey); 399 } else { 400 fireValueChangedEvent(); 401 } 402 } 403 404 /** 405 * Shows the image cropping dialog.<p> 406 */ 407 public void openCropping() { 408 409 CmsCroppingParamBean param = new CmsCroppingParamBean(m_croppingParam); 410 m_currentFormat.adjustCroppingParam(param); 411 m_croppingDialog.show(param); 412 } 413 414 /** 415 * Sets the given cropping parameter.<p> 416 * 417 * @param croppingParam the cropping parameter 418 */ 419 public void setCropping(CmsCroppingParamBean croppingParam) { 420 421 m_croppingParam = croppingParam; 422 m_formatForm.setHeightInput(m_croppingParam.getTargetHeight()); 423 m_formatForm.setWidthInput(m_croppingParam.getTargetWidth()); 424 425 // only in case of the original-format-restriction, the cropping dialog may be opened to override the selected format 426 if (m_currentFormat instanceof CmsOriginalFormatRestriction) { 427 I_CmsFormatRestriction format = getMatchingFormat(m_croppingParam, false); 428 if (format != null) { 429 m_currentFormat = format; 430 m_formatForm.setFormatSelectValue(format.getName()); 431 } 432 } 433 m_formatForm.setCropped(true); 434 fireValueChangedEvent(); 435 } 436 437 /** 438 * Sets the original width.<p> 439 * 440 * @param originalWidth the original width to set 441 */ 442 public void setOriginalWidth(int originalWidth) { 443 444 m_originalWidth = originalWidth; 445 } 446 447 /** 448 * Adds this handler to the widget. 449 * 450 * @param <H> the type of handler to add 451 * @param type the event type 452 * @param handler the handler 453 * @return {@link HandlerRegistration} used to remove the handler 454 */ 455 protected final <H extends EventHandler> HandlerRegistration addHandler(final H handler, GwtEvent.Type<H> type) { 456 457 return ensureHandlers().addHandlerToSource(type, this, handler); 458 } 459 460 /** 461 * Helper method for firing a 'value changed' event.<p> 462 */ 463 protected void fireValueChangedEvent() { 464 465 ValueChangeEvent.fire(this, m_croppingParam); 466 } 467 468 /** 469 * Adjusts the current format to the cropping parameter.<p> 470 */ 471 private void adjustToCurrentFormat() { 472 473 // in case of a locked or fixed image ratio height and width need to be reset 474 int height = m_croppingParam.getOrgHeight(); 475 int width = m_croppingParam.getOrgWidth(); 476 if (m_croppingParam.isScaled()) { 477 if (m_croppingParam.getTargetHeight() == -1) { 478 height = (int)Math.floor( 479 ((1.00 * m_croppingParam.getOrgHeight()) / m_croppingParam.getOrgWidth()) 480 * m_croppingParam.getTargetWidth()); 481 } else { 482 height = m_croppingParam.getTargetHeight(); 483 } 484 if (m_croppingParam.getTargetWidth() == -1) { 485 width = (int)Math.floor( 486 ((1.00 * m_croppingParam.getOrgWidth()) / m_croppingParam.getOrgHeight()) 487 * m_croppingParam.getTargetHeight()); 488 } else { 489 width = m_croppingParam.getTargetWidth(); 490 } 491 492 } else { 493 m_croppingParam.setTargetHeight(height); 494 m_croppingParam.setTargetWidth(width); 495 } 496 m_formatForm.setHeightInput(height); 497 m_formatForm.setWidthInput(width); 498 // enabling/disabling ratio lock button 499 if (m_currentFormat.isFixedRatio()) { 500 m_formatForm.setRatioButton(false, false, Messages.get().key(Messages.GUI_PRIVIEW_BUTTON_RATIO_FIXED_0)); 501 m_ratioLocked = true; 502 } else { 503 if (!m_currentFormat.isHeightEditable() && !m_currentFormat.isWidthEditable()) { 504 // neither height nor width are editable, disable ratio lock button 505 m_formatForm.setRatioButton( 506 false, 507 false, 508 Messages.get().key(Messages.GUI_PRIVIEW_BUTTON_NOT_EDITABLE_0)); 509 } else { 510 m_formatForm.setRatioButton(false, true, null); 511 } 512 m_ratioLocked = true; 513 } 514 // enabling/disabling height and width input 515 m_formatForm.setHeightInputEnabled(m_currentFormat.isHeightEditable() || hasUserFormatRestriction()); 516 m_formatForm.setWidthInputEnabled(m_currentFormat.isWidthEditable() || hasUserFormatRestriction()); 517 } 518 519 /** 520 * Lazy initializing the handler manager.<p> 521 * 522 * @return the handler manager 523 */ 524 private SimpleEventBus ensureHandlers() { 525 526 if (m_eventBus == null) { 527 m_eventBus = new SimpleEventBus(); 528 } 529 return m_eventBus; 530 } 531 532 /** 533 * Generates the format restriction objects.<p> 534 */ 535 private void generateFormats() { 536 537 m_formats = new LinkedHashMap<String, I_CmsFormatRestriction>(); 538 for (int i = 0; i < m_formatValues.length; i++) { 539 String value = m_formatValues[i].trim(); 540 541 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 542 String label = value; 543 String key = value; 544 if ((m_formatNames != null) 545 && (m_formatNames.length > i) 546 && CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_formatNames[i])) { 547 int pos = m_formatNames[i].indexOf(":"); 548 if (pos > 0) { 549 label = m_formatNames[i].substring(pos + 1, m_formatNames[i].length()); 550 key = m_formatNames[i].substring(0, pos); 551 } else { 552 label = m_formatNames[i]; 553 key = m_formatNames[i]; 554 } 555 } 556 557 DefaultRestriction restrictionType = null; 558 try { 559 restrictionType = DefaultRestriction.valueOf(value); 560 } catch (Exception e) { 561 // happens with user defined restriction settings 562 } 563 if (restrictionType != null) { 564 switch (restrictionType) { 565 case original: 566 m_formats.put(key, new CmsOriginalFormatRestriction(key, label)); 567 break; 568 case user: 569 m_userFormatKey = key; 570 m_formats.put(key, new CmsUserFormatRestriction(key, label)); 571 break; 572 case free: 573 m_formats.put(key, new CmsFreeFormatRestriction(key, label)); 574 break; 575 case small: 576 m_formats.put(key, new CmsImageFormatRestriction(key, label, "200x?")); 577 break; 578 case big: 579 m_formats.put(key, new CmsImageFormatRestriction(key, label, "500x?")); 580 break; 581 default: 582 } 583 } else { 584 if (CmsImageFormatRestriction.isValidConfig(value)) { 585 m_formats.put(key, new CmsImageFormatRestriction(key, label, value)); 586 } 587 } 588 } 589 } 590 } 591 592 /** 593 * Checks the format restrictions if the match the giving cropping parameter.<p> 594 * 595 * @param croppingParam the cropping parameter 596 * @param forceByName force format match by name within cropping parameter 597 * 598 * @return the matching format restriction 599 */ 600 private I_CmsFormatRestriction getMatchingFormat(CmsCroppingParamBean croppingParam, boolean forceByName) { 601 602 I_CmsFormatRestriction result = null; 603 if (forceByName && m_formats.containsKey(croppingParam.getFormatName())) { 604 result = m_formats.get(croppingParam.getFormatName()); 605 if (!result.matchesCroppingParam(croppingParam)) { 606 result.adjustCroppingParam(croppingParam); 607 } 608 return result; 609 } 610 for (I_CmsFormatRestriction format : m_formats.values()) { 611 612 if (format.matchesCroppingParam(croppingParam)) { 613 result = format; 614 if (format.getName().equals(croppingParam.getFormatName())) { 615 break; 616 } 617 } 618 } 619 return result; 620 } 621 622 /** 623 * Returns if the user defined format restriction is available.<p> 624 * 625 * @return <code>true</code> if the user defined format restriction is available 626 */ 627 private boolean hasUserFormatRestriction() { 628 629 return m_userFormatKey != null; 630 } 631 632 /** 633 * Reads the format configuration for the given gallery mode.<p> 634 * 635 * @param mode the gallery mode 636 * @param isNativeWidget if the dialog is used as a native widget 637 * @param overrideFormats true if the formats from the gallery dialog should take priority in 'editor' mode 638 */ 639 private void readFormatsConfig(GalleryMode mode, boolean isNativeWidget, boolean overrideFormats) { 640 641 switch (mode) { 642 case editor: 643 if (!overrideFormats) { 644 m_useFormats = true; 645 m_formatNames = DEFAULT_FORMAT_NAMES; 646 m_formatValues = DEFAULT_FORMAT_VALUES; 647 } else { 648 if ((m_formatNames == null) && m_useFormats) { 649 m_formatNames = DEFAULT_FORMAT_NAMES; 650 } 651 if ((m_formatValues == null) && m_useFormats) { 652 m_formatValues = DEFAULT_FORMAT_VALUES; 653 } 654 } 655 break; 656 case widget: 657 if (!isNativeWidget) { 658 m_useFormats = CmsPreviewUtil.isShowFormats(); 659 if (m_useFormats) { 660 m_formatValues = CmsPreviewUtil.getFormats(); 661 if (m_formatValues == null) { 662 m_formatNames = DEFAULT_FORMAT_NAMES; 663 m_formatValues = DEFAULT_FORMAT_VALUES; 664 } else { 665 m_formatNames = CmsPreviewUtil.getFormatNames(); 666 } 667 } 668 } else if (m_useFormats && (m_formatValues == null)) { 669 m_formatNames = DEFAULT_FORMAT_NAMES; 670 m_formatValues = DEFAULT_FORMAT_VALUES; 671 } 672 break; 673 case ade: 674 case view: 675 case adeView: 676 m_useFormats = false; 677 break; 678 default: 679 } 680 } 681}