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