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}