View Javadoc
1   package fr.ifremer.quadrige3.ui.swing;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 UI Common
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.collect.Lists;
27  import com.google.common.collect.Maps;
28  import com.google.common.collect.Sets;
29  import fr.ifremer.quadrige3.core.config.QuadrigeCoreConfiguration;
30  import fr.ifremer.quadrige3.core.dao.technical.Assert;
31  import fr.ifremer.quadrige3.core.dao.technical.decorator.Decorator;
32  import fr.ifremer.quadrige3.core.service.ClientServiceLocator;
33  import fr.ifremer.quadrige3.core.service.decorator.DecoratorService;
34  import fr.ifremer.quadrige3.ui.swing.action.AbstractChangeScreenAction;
35  import fr.ifremer.quadrige3.ui.swing.action.ActionUI;
36  import fr.ifremer.quadrige3.ui.swing.component.ActionComboBoxModel;
37  import fr.ifremer.quadrige3.ui.swing.component.ActionListCellRenderer;
38  import fr.ifremer.quadrige3.ui.swing.component.ColoredFilteredListCellRenderer;
39  import fr.ifremer.quadrige3.ui.swing.component.ExtendedDecoratorListCellRenderer;
40  import fr.ifremer.quadrige3.ui.swing.component.bean.ExtendedBeanDoubleList;
41  import fr.ifremer.quadrige3.ui.swing.component.date.*;
42  import fr.ifremer.quadrige3.ui.swing.component.number.NumberEditor;
43  import fr.ifremer.quadrige3.ui.swing.component.time.LocalTimeEditor;
44  import fr.ifremer.quadrige3.ui.swing.content.AbstractMainUIHandler;
45  import fr.ifremer.quadrige3.ui.swing.content.MainUI;
46  import fr.ifremer.quadrige3.ui.swing.model.AbstractBeanUIModel;
47  import fr.ifremer.quadrige3.ui.swing.plaf.HintSynthTextFieldUI;
48  import jaxx.runtime.swing.editor.bean.BeanDoubleList;
49  import jaxx.runtime.swing.editor.bean.BeanFilterableComboBox;
50  import jaxx.runtime.swing.model.JaxxFilterableListModel;
51  import jaxx.runtime.swing.renderer.FilteredDecoratorListCellRenderer;
52  import jaxx.runtime.validator.swing.SwingValidator;
53  import org.apache.commons.collections4.CollectionUtils;
54  import org.apache.commons.lang3.StringUtils;
55  import org.apache.commons.lang3.time.DateUtils;
56  import org.apache.commons.logging.Log;
57  import org.apache.commons.logging.LogFactory;
58  import org.jdesktop.jxlayer.JXLayer;
59  import org.jdesktop.swingx.JXDatePicker;
60  import org.jdesktop.swingx.JXPanel;
61  import org.nuiton.decorator.MultiJXPathDecorator;
62  import org.nuiton.jaxx.application.bean.JavaBeanObjectUtil;
63  import org.nuiton.jaxx.application.swing.AbstractApplicationUIHandler;
64  import org.nuiton.jaxx.application.swing.action.ApplicationActionUIHandler;
65  import org.nuiton.jaxx.application.swing.action.ApplicationUIAction;
66  import org.nuiton.jaxx.application.swing.util.Cancelable;
67  import org.nuiton.validator.bean.simple.SimpleBeanValidator;
68  
69  import javax.swing.*;
70  import javax.swing.event.DocumentEvent;
71  import javax.swing.event.DocumentListener;
72  import javax.swing.event.ListDataEvent;
73  import javax.swing.event.ListDataListener;
74  import javax.swing.plaf.nimbus.NimbusLookAndFeel;
75  import javax.swing.text.AbstractDocument;
76  import javax.swing.text.JTextComponent;
77  import java.awt.*;
78  import java.awt.event.*;
79  import java.beans.PropertyChangeEvent;
80  import java.beans.PropertyChangeListener;
81  import java.io.File;
82  import java.io.Serializable;
83  import java.time.LocalDate;
84  import java.time.LocalTime;
85  import java.util.List;
86  import java.util.*;
87  
88  import static org.nuiton.i18n.I18n.t;
89  
90  /**
91   * Contract of any UI handler.
92   *
93   * @param <M>
94   * @param <UI>
95   * @author Lionel Touseau <lionel.touseau@e-is.pro>
96   * @since 1.0
97   */
98  public abstract class AbstractUIHandler<M, UI extends ApplicationUI<M, ?>>
99      extends AbstractApplicationUIHandler<M, UI>
100     implements UIMessageNotifier {
101 
102     /**
103      * Logger.
104      */
105     private static final Log LOG = LogFactory.getLog(AbstractUIHandler.class);
106 
107     private final Map<AbstractBeanUIModel, PropertyChangeListener> modelModifyListenerMap = Maps.newHashMap();
108     private final Map<SimpleBeanValidator, PropertyChangeListener> validatorValidListenerMap = Maps.newHashMap();
109 
110     // Map of previous body content by full screen panel
111     private Map<JComponent, Container> previousBodyContentByFullScreenPanel = new HashMap<>();
112     // Map of previous parent by full screen panel
113     private Map<JComponent, Container> previousParentByFullScreenPanel = new HashMap<>();
114 
115     /**
116      * Toggle full screen view of th the panel
117      *
118      * @param panel        the panel to set/unset on full screen
119      * @param toggleButton the toggle button controlling the full screen state
120      */
121     @SuppressWarnings("unchecked")
122     public void toggleFullScreen(JPanel panel, JToggleButton toggleButton) {
123 
124         if (toggleButton.isSelected()) {
125 
126             Assert.isTrue(!previousBodyContentByFullScreenPanel.containsKey(panel) && !previousParentByFullScreenPanel.containsKey(panel), "this panel is already full screen");
127             Assert.isTrue(SwingUtilities.isDescendingFrom(toggleButton, panel), "the toggle full screen button must be inside the panel to full screen");
128             Assert.notNull(panel.getParent(), "the panel to full screen has no parent");
129             // keep previous body content and panel parent
130             previousParentByFullScreenPanel.put(panel, panel.getParent());
131             previousBodyContentByFullScreenPanel.put(panel, getContext().getMainUI().getBody().getContentContainer());
132             // set this panel as only content on main UI
133             getContext().getMainUI().getBody().setContentContainer(panel);
134 
135         } else {
136 
137             Assert.isTrue(previousBodyContentByFullScreenPanel.containsKey(panel), "this full screen panel has no previous body content");
138             Assert.isTrue(previousParentByFullScreenPanel.containsKey(panel), "this full screen panel has no previous parent");
139             // restore previous body content
140             getContext().getMainUI().getBody().setContentContainer(previousBodyContentByFullScreenPanel.remove(panel));
141             // get the previous parent
142             Container previousParent = previousParentByFullScreenPanel.remove(panel);
143             // add the panel back to the previous parent (depending on type)
144             if (previousParent instanceof JLayer) {
145                 ((JLayer<JPanel>) previousParent).setView(panel);
146             } else if (previousParent instanceof JXLayer) {
147                 ((JXLayer<JPanel>) previousParent).setView(panel);
148             } else {
149                 // basic container
150                 previousParent.add(panel);
151             }
152 
153         }
154 
155         getContext().getMainUI().revalidate();
156         getContext().getMainUI().repaint();
157     }
158 
159     // ------------------------------------------------------------------------//
160     // -- Public methods --//
161     // ------------------------------------------------------------------------//
162 
163     /**
164      * <p>changeScreenAction.</p>
165      *
166      * @param screenActionClass a {@link java.lang.Class} object.
167      */
168     public void changeScreenAction(Class<? extends AbstractChangeScreenAction> screenActionClass) {
169         ApplicationUIAction<? extends AbstractChangeScreenAction> screenAction;
170         screenAction = getContext().getActionFactory().createUIAction(getContext().getMainUI().getHandler(), screenActionClass);
171         getContext().getActionEngine().runAction(screenAction.getLogicAction());
172     }
173 
174     /**
175      * {@inheritDoc}
176      */
177     @Override
178     public void showInformationMessage(String message) {
179         getContext().showInformationMessage(message);
180     }
181 
182     /**
183      * <p>showNotImplementedFunctionality.</p>
184      */
185     public void showNotImplementedFunctionality() {
186         getContext().getDialogHelper().showMessageDialog(t("quadrige3.error.notImplemented"));
187     }
188 
189     /**
190      * {@inheritDoc}
191      */
192     @Override
193     public ApplicationUIContext getContext() {
194         return (ApplicationUIContext) super.getContext();
195     }
196 
197     /**
198      * get the screen title
199      *
200      * @return the screen title
201      */
202     public String getTitle() {
203         return t("quadrige3.screen." + getUI().getClass().getSimpleName() + ".title");
204     }
205 
206     /**
207      * <p>getConfig.</p>
208      *
209      * @return a {@link QuadrigeCoreConfiguration} object.
210      */
211     public QuadrigeCoreConfiguration getConfig() {
212         return getContext().getConfiguration();
213     }
214 
215     /**
216      * {@inheritDoc}
217      */
218     @Override
219     public Component getTopestUI() {
220         return getContext().getActionUI();
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     public void onCloseUI() {
228         // default close actions
229         if (LOG.isDebugEnabled()) {
230             LOG.debug("closing: " + getUI());
231         }
232     }
233 
234     /**
235      * <p>clearValidators.</p>
236      */
237     public void clearValidators() {
238         MainUI main = getContext().getMainUI();
239         Assert.notNull(main, "No mainUI registered in application context");
240         AbstractMainUIHandler handler = (AbstractMainUIHandler) main.getHandler();
241         handler.clearValidators();
242     }
243 
244     /**
245      * {@inheritDoc}
246      */
247     @Override
248     protected JComponent getComponentToFocus() {
249         return null;
250     }
251 
252     /**
253      * {@inheritDoc}
254      */
255     @Override
256     public SwingValidator<M> getValidator() {
257         return null;
258     }
259 
260     /**
261      * Method called by AbstractMainUIHandler#setScreen() after the ui has been added to main frame
262      */
263     public void afterViewInit() {
264         // propagate event to children ui
265         if (getUI() instanceof Container) {
266             propagateAfterViewInit((Container) getUI());
267         }
268     }
269 
270     private void propagateAfterViewInit(Container container) {
271         for (Component component : container.getComponents()) {
272             if (component instanceof ApplicationUI) {
273                 ((ApplicationUI) component).getHandler().afterViewInit();
274             } else if (component instanceof Container) {
275                 propagateAfterViewInit((Container) component);
276             }
277         }
278     }
279 
280     /**
281      * <p>forceRevalidateModel.</p>
282      */
283     public void forceRevalidateModel() {
284 
285         if (getValidator() != null) {
286             getValidator().doValidate();
287         }
288         if (getModel() instanceof AbstractBeanUIModel) {
289             ((AbstractBeanUIModel) getModel()).firePropertyChanged(AbstractBeanUIModel.PROPERTY_VALID, null, null);
290         }
291     }
292 
293     /**
294      * {@inheritDoc}
295      */
296     @Override
297     public <O> Decorator<O> getDecorator(Class<O> type, String name) {
298         DecoratorService decoratorService = ClientServiceLocator.instance().getDecoratorService();
299 
300         Assert.notNull(type);
301 
302         return decoratorService.getDecoratorByType(type, name);
303     }
304 
305     /**
306      * {@inheritDoc}
307      */
308     @Override
309     public String decorate(Serializable object, String context) {
310         return super.decorate(object, context);
311     }
312 
313     /**
314      * {@inheritDoc}
315      */
316     @Override
317     public String decorate(Serializable object) {
318         return super.decorate(object);
319     }
320 
321     /**
322      * <p>listenModelModify.</p>
323      *
324      * @param model a {@link AbstractBeanUIModel} object.
325      */
326     protected void listenModelModify(AbstractBeanUIModel model) {
327         Assert.isTrue(getModel() != model, "model can't listen to itself");
328 
329         stopListenModelModify(model);
330 
331         PropertyChangeListener listener = evt -> {
332             Boolean modify = (Boolean) evt.getNewValue();
333             if (modify != null) {
334                 ((AbstractBeanUIModel) getModel()).setModify(modify);
335             }
336         };
337 
338         model.addPropertyChangeListener(AbstractBeanUIModel.PROPERTY_MODIFY, listener);
339         modelModifyListenerMap.put(model, listener);
340     }
341 
342     /**
343      * <p>stopListenModelModify.</p>
344      *
345      * @param model a {@link AbstractBeanUIModel} object.
346      */
347     protected void stopListenModelModify(AbstractBeanUIModel model) {
348         PropertyChangeListener listener = modelModifyListenerMap.get(model);
349         if (listener != null) {
350             model.removePropertyChangeListener(AbstractBeanUIModel.PROPERTY_MODIFY, listener);
351             modelModifyListenerMap.remove(model);
352         }
353     }
354 
355     /**
356      * <p>listenModelValid.</p>
357      *
358      * @param model a {@link AbstractBeanUIModel} object.
359      */
360     protected void listenModelValid(AbstractBeanUIModel model) {
361         model.addPropertyChangeListener(AbstractBeanUIModel.PROPERTY_VALID, evt -> {
362             Boolean valid = (Boolean) evt.getNewValue();
363             if (valid != null) {
364                 ((AbstractBeanUIModel) getModel()).setValid(valid);
365             }
366         });
367     }
368 
369     /**
370      * <p>setEnabled.</p>
371      *
372      * @param container a {@link java.awt.Container} object.
373      * @param enabled   a boolean.
374      */
375     public void setEnabled(Container container, boolean enabled) {
376         Component[] components = container.getComponents();
377         for (Component component : components) {
378             component.setEnabled(enabled);
379             if (component instanceof Container) {
380                 setEnabled((Container) component, enabled);
381             }
382         }
383     }
384 
385     /**
386      * {@inheritDoc}
387      */
388     @Override
389     public void setDate(ActionEvent event, String property) {
390         Date value = null;
391         if (event.getSource() instanceof JXDatePicker) {
392             value = ((JXDatePicker) event.getSource()).getDate();
393         } else if (event.getSource() instanceof JDatePanel) {
394             value = ((JDatePanel) event.getSource()).getDate();
395         } else if (event.getSource() instanceof JDatePicker) {
396             value = ((JDatePicker) event.getSource()).getDate();
397         }
398 
399         Date date = (Date) JavaBeanObjectUtil.getProperty(getModel(), property);
400         if (value != null && date != null) {
401             Calendar cal = DateUtils.toCalendar(date);
402             value = DateUtils.setHours(value, cal.get(Calendar.HOUR_OF_DAY));
403             value = DateUtils.setMinutes(value, cal.get(Calendar.MINUTE));
404             value = DateUtils.setSeconds(value, cal.get(Calendar.SECOND));
405         }
406         JavaBeanObjectUtil.setProperty(getModel(), property, value);
407     }
408 
409     public void setLocalDate(ActionEvent event, String property) {
410         LocalDate value = null;
411         if (event.getSource() instanceof JLocalDatePanel) {
412             value = ((JLocalDatePanel) event.getSource()).getLocalDate();
413         } else if (event.getSource() instanceof JLocalDatePicker) {
414             value = ((JLocalDatePicker) event.getSource()).getLocalDate();
415         }
416         JavaBeanObjectUtil.setProperty(getModel(), property, value);
417     }
418 
419     public void setLocalTime(ActionEvent event, String property) {
420         LocalTime value = null;
421         if (event.getSource() instanceof LocalTimeEditor) {
422             value = ((LocalTimeEditor) event.getSource()).getLocalTime();
423         }
424 
425         JavaBeanObjectUtil.setProperty(this.getModel(), property, value);
426     }
427 
428     public void setTimeInSecond(ActionEvent event, String property) {
429         Integer value = null;
430         if (event.getSource() instanceof LocalTimeEditor) {
431             value = ((LocalTimeEditor) event.getSource()).getTimeInSecond();
432         }
433 
434         JavaBeanObjectUtil.setProperty(this.getModel(), property, value);
435     }
436 
437     /**
438      * Consume key event to avoid repeated events (cf Mantis #47698)
439      *
440      * @param event    key event
441      * @param property property
442      */
443     @Override
444     public void setText(KeyEvent event, String property) {
445         JTextComponent field = (JTextComponent) event.getSource();
446         String value = field.getText();
447         if (value != null && value.startsWith(" ")) {
448             // Remove heading space (Mantis #52677)
449             value = value.trim();
450             field.setText(value);
451         }
452         JavaBeanObjectUtil.setProperty(getModel(), property, value);
453         event.consume();
454     }
455 
456     // ------------------------------------------------------------------------//
457     // -- Init methods --//
458     // ------------------------------------------------------------------------//
459 
460     /**
461      * {@inheritDoc}
462      */
463     @Override
464     protected void initUIComponent(Object component) {
465         super.initUIComponent(component);
466 
467         if (component instanceof NumberEditor) {
468             initNumberEditor((NumberEditor) component);
469 
470         } else if (component instanceof JDatePicker) {
471             initDatePicker((JDatePicker) component);
472 
473         } else if (component instanceof JLocalDatePicker) {
474             initLocalDatePicker((JLocalDatePicker) component);
475         }
476     }
477 
478     private void initLocalDatePicker(JLocalDatePicker datePicker) {
479 
480         String datePattern = getContext().getDateFormat();
481         datePicker.setDateFormat(datePattern, ApplicationUIUtil.getDefault2DigitYearStart());
482 
483         // colors
484         datePicker.setColor(AbstractComponentColor.Key.FG_MONTH_SELECTOR, Color.black);
485         datePicker.setColor(AbstractComponentColor.Key.BG_MONTH_SELECTOR, UIManager.getColor("background"));
486         datePicker.setColor(AbstractComponentColor.Key.FG_GRID_HEADER, UIManager.getColor("thematicLabelColor"));
487         datePicker.setColor(AbstractComponentColor.Key.BG_GRID_SELECTED, UIManager.getColor("nimbusSelectionBackground"));
488         datePicker.setColor(AbstractComponentColor.Key.BG_GRID_TODAY_SELECTED, UIManager.getColor("nimbusSelectionBackground"));
489 
490         datePicker.setToolTipText(t("jaxx.application.common.datefield.tip", datePattern));
491         datePicker.setTextEditable(true);
492         datePicker.setShowYearButtons(true);
493 
494         if (isAutoSelectOnFocus(datePicker)) {
495             addAutoSelectOnFocus(datePicker.getEditor());
496         }
497 
498     }
499 
500     private void initDatePicker(JDatePicker datePicker) {
501 
502         String datePattern = getContext().getDateFormat();
503         datePicker.setDateFormat(datePattern, ApplicationUIUtil.getDefault2DigitYearStart());
504 
505         // colors
506         datePicker.setColor(AbstractComponentColor.Key.FG_MONTH_SELECTOR, Color.black);
507         datePicker.setColor(AbstractComponentColor.Key.BG_MONTH_SELECTOR, UIManager.getColor("background"));
508         datePicker.setColor(AbstractComponentColor.Key.FG_GRID_HEADER, UIManager.getColor("thematicLabelColor"));
509         datePicker.setColor(AbstractComponentColor.Key.BG_GRID_SELECTED, UIManager.getColor("nimbusSelectionBackground"));
510         datePicker.setColor(AbstractComponentColor.Key.BG_GRID_TODAY_SELECTED, UIManager.getColor("nimbusSelectionBackground"));
511 
512         datePicker.setToolTipText(t("jaxx.application.common.datefield.tip", datePattern));
513         datePicker.setTextEditable(true);
514         datePicker.setShowYearButtons(true);
515 
516         if (isAutoSelectOnFocus(datePicker)) {
517             addAutoSelectOnFocus(datePicker.getEditor());
518         }
519 
520     }
521 
522     private void initNumberEditor(NumberEditor numberEditor) {
523 
524         // Init the org.nuiton.jaxx.widgets.number.NumberEditor
525         numberEditor.init();
526 
527         // Fix Mantis #37194 - missing text on 0 button
528         numberEditor.getNumber0().setText(t("numbereditor.0"));
529     }
530 
531     /**
532      * {@inheritDoc}
533      */
534     @Override
535     protected void initTextField(JTextField jTextField) {
536         super.initTextField(jTextField);
537 
538         String hint = (String) jTextField.getClientProperty("hint");
539         if (hint != null && UIManager.getLookAndFeel() instanceof NimbusLookAndFeel) {
540             jTextField.setUI(new HintSynthTextFieldUI(hint, false));
541         }
542     }
543 
544     /**
545      * {@inheritDoc}
546      */
547     @Override
548     protected void initButton(AbstractButton abstractButton) {
549         super.initButton(abstractButton);
550 
551         // Add ENTER key to input map and affect the same action than SPACE
552         abstractButton.getInputMap().put(KeyStroke.getKeyStroke("pressed ENTER"), "pressed");
553         abstractButton.getInputMap().put(KeyStroke.getKeyStroke("released ENTER"), "released");
554 
555         // configure non blocking action
556         Class actionName = (Class) abstractButton.getClientProperty("applicationWorkerAction");
557         if (actionName != null) {
558             if (abstractButton.getAction() == null) {
559                 @SuppressWarnings("unchecked") Action action = getContext().getActionFactory().createNonBlockingUIAction(this, actionName, abstractButton);
560                 abstractButton.setAction(action);
561             } else {
562                 if (LOG.isErrorEnabled()) {
563                     LOG.error(String.format("button '%s' has already an action. the action '%s' will be ignored", abstractButton.getName(), actionName.getName()));
564                 }
565             }
566         }
567     }
568 
569     /**
570      * {@inheritDoc}
571      */
572     @Override
573     protected <E> void initBeanList(BeanDoubleList<E> list, List<E> data, List<E> selectedData) {
574         initBeanList(list, data, selectedData, -1);
575     }
576 
577     /**
578      * {@inheritDoc}
579      */
580     @Override
581     @Deprecated
582     protected <E> void initBeanList(BeanDoubleList<E> list, List<E> data, List<E> selectedData, org.nuiton.decorator.Decorator<E> selectedDecorator) {
583         if (selectedDecorator instanceof Decorator) {
584             initBeanList(list, data, selectedData, (Decorator<E>) selectedDecorator);
585         }
586         LOG.warn("Use of deprecated method : fr.ifremer.quadrige3.ui.swing.AbstractUIHandler.initBeanList(jaxx.runtime.swing.editor.bean.BeanDoubleList<E>, java.util.List<E>, java.util.List<E>, org.nuiton.decorator.Decorator<E>)");
587         super.initBeanList(list, data, selectedData, selectedDecorator);
588     }
589 
590     protected <E> void initBeanList(BeanDoubleList<E> list, List<E> data, List<E> selectedData, Decorator<E> selectedDecorator) {
591         initBeanList(list, data, selectedData, -1);
592     }
593 
594     /**
595      * <p>initBeanList.</p>
596      *
597      * @param list           a {@link jaxx.runtime.swing.editor.bean.BeanDoubleList} object.
598      * @param data           a {@link java.util.List} object.
599      * @param selectedData   a {@link java.util.List} object.
600      * @param preferredWidth a int.
601      * @param <E>            a E object.
602      */
603     @SuppressWarnings("unchecked")
604     protected <E> void initBeanList(final BeanDoubleList<E> list, List<E> data, List<E> selectedData, int preferredWidth) {
605 
606         Assert.notNull(list, "No list!");
607 
608         Class<E> beanType = list.getBeanType();
609         Assert.notNull(beanType, "No beanType on the double list!");
610 
611         String decoratorContext = null;
612         if (list instanceof ExtendedBeanDoubleList) {
613             decoratorContext = ((ExtendedBeanDoubleList) list).getDecoratorContext();
614         }
615 
616         Decorator<E> decorator = getDecorator(beanType, decoratorContext);
617 
618         if (CollectionUtils.isNotEmpty(data)) {
619             if (decorator != null) {
620                 data.sort(decorator.getCurrentComparator());
621                 if (selectedData != null) {
622                     selectedData.sort(decorator.getCurrentComparator());
623                 }
624             }
625         }
626 
627         if (LOG.isDebugEnabled()) {
628             LOG.debug("entity list [" + beanType.getName() + "] : " + (data == null ? 0 : data.size()));
629         }
630 
631         list.setI18nPrefix(getContext().getI18nPrefix());
632 
633         // DON'T call super.initBeanList and create decorators correctly
634 //        super.initBeanList(list, data, selectedData, null);
635 
636         // Initialize with decorator and add data list
637         list.init(decorator, null, data, selectedData);
638 
639         if (LOG.isDebugEnabled()) {
640             LOG.debug("Jlist [" + beanType.getName() + "] : " + list.getUniverseList().getModel().getSize());
641         }
642 
643         if (preferredWidth > -1) {
644             fixBeanListWidth(list, preferredWidth);
645         }
646 
647         // init custom renderer
648         final FilteredDecoratorListCellRenderer universeRenderer = newFilteredListCellRenderer(list.getHandler().getDecorator());
649         list.getUniverseList().setCellRenderer(universeRenderer);
650         list.getSelectedList().setCellRenderer(newListCellRender(list.getHandler().getDecorator()));
651 
652         // override filter field listener and add custom document listener
653         // because JAXX v2.8.7 don't use the filter text highlighter and don't support list cell renderer override
654         final JTextField filterField = list.getFilterField();
655         AbstractDocument document = (AbstractDocument) list.getFilterField().getDocument();
656         DocumentListener[] dls = document.getDocumentListeners();
657         if (dls != null) {
658             for (DocumentListener dl : dls) {
659                 if (dl.getClass().getName().contains("BeanDoubleListHandler")) {
660                     document.removeDocumentListener(dl);
661                     break;
662                 }
663             }
664         }
665         final JaxxFilterableListModel<E> filterModel = (JaxxFilterableListModel<E>) list.getModel().getUniverseModel();
666         document.addDocumentListener(new DocumentListener() {
667 
668             @Override
669             public void insertUpdate(DocumentEvent e) {
670                 String text = filterField.getText();
671                 universeRenderer.setFilterText(text);
672                 filterModel.setFilterText(text);
673             }
674 
675             @Override
676             public void removeUpdate(DocumentEvent e) {
677                 String text = filterField.getText();
678                 universeRenderer.setFilterText(text);
679                 filterModel.setFilterText(text);
680             }
681 
682             @Override
683             public void changedUpdate(DocumentEvent e) {
684                 String text = filterField.getText();
685                 universeRenderer.setFilterText(text);
686                 filterModel.setFilterText(text);
687             }
688         });
689 
690         filterModel.addListDataListener(new ListDataListener() {
691             @Override
692             public void intervalAdded(ListDataEvent e) {
693             }
694 
695             @Override
696             public void intervalRemoved(ListDataEvent e) {
697             }
698 
699             @Override
700             public void contentsChanged(ListDataEvent e) {
701                 // fix Mantis #37930
702                 boolean empty = e.getIndex0() == 0 && e.getIndex1() == 0;
703                 list.getAddButton().setEnabled(!empty && !list.getUniverseList().isSelectionEmpty());
704                 if (empty) {
705                     list.getUniverseList().clearSelection();
706                 }
707             }
708         });
709 
710         // add mouse listener to auto selected item on right click
711         list.getSelectedList().addMouseListener(new MouseAdapter() {
712 
713             @Override
714             public void mousePressed(MouseEvent e) {
715                 boolean rightClick = SwingUtilities.isRightMouseButton(e);
716                 if (rightClick) {
717                     JList list = (JList) e.getSource();
718                     int index = list.locationToIndex(e.getPoint());
719                     list.setSelectedIndex(index);
720                 }
721             }
722         });
723 
724         // disable focus traversal on JList
725         list.getUniverseList().setFocusTraversalKeysEnabled(true);
726         list.getSelectedList().setFocusTraversalKeysEnabled(true);
727     }
728 
729     protected <O> FilteredDecoratorListCellRenderer newFilteredListCellRenderer(MultiJXPathDecorator<O> decorator) {
730         Assert.notNull(decorator);
731         return new ColoredFilteredListCellRenderer(decorator,
732             UIManager.getColor("nimbusSelectionBackground"), UIManager.getColor("nimbusSelectionBackground"));
733     }
734 
735     @Override
736     @Deprecated
737     protected <O> ListCellRenderer newListCellRender(org.nuiton.decorator.Decorator<O> decorator) {
738         if (decorator instanceof Decorator) {
739             return newListCellRender((Decorator<O>) decorator);
740         }
741         LOG.warn("use of deprecated method : fr.ifremer.quadrige3.ui.swing.AbstractUIHandler.newListCellRender(org.nuiton.decorator.Decorator<O>)");
742         return super.newListCellRender(decorator);
743     }
744 
745     protected <O> ListCellRenderer newListCellRender(MultiJXPathDecorator<O> decorator) {
746         Assert.notNull(decorator);
747         return new ExtendedDecoratorListCellRenderer(decorator, UIManager.getColor("nimbusSelectionBackground"), true);
748     }
749 
750     private <E> void fixBeanListWidth(BeanDoubleList<E> list, int preferredWidth) {
751 
752         JScrollPane scrollPane = (JScrollPane) list.get$objectMap().get("$JScrollPane0");
753         if (scrollPane == null) {
754             if (LOG.isWarnEnabled()) {
755                 LOG.warn("the component '$JScrollPane0' has not been found in the BeanDoubleList");
756             }
757         } else {
758             scrollPane.setPreferredSize(new Dimension(preferredWidth, (int) scrollPane.getPreferredSize().getHeight()));
759         }
760         scrollPane = (JScrollPane) list.get$objectMap().get("$JScrollPane1");
761         if (scrollPane == null) {
762             if (LOG.isWarnEnabled()) {
763                 LOG.warn("the component '$JScrollPane1' has not been found in the BeanDoubleList");
764             }
765         } else {
766             scrollPane.setPreferredSize(new Dimension(preferredWidth, (int) scrollPane.getPreferredSize().getHeight()));
767         }
768 
769     }
770 
771     /**
772      * {@inheritDoc}
773      */
774     @Override
775     protected <E> void initBeanFilterableComboBox(
776         final BeanFilterableComboBox<E> comboBox,
777         final List<E> data,
778         final E selectedData,
779         final String decoratorContext) {
780 
781         initBeanFilterableComboBox(comboBox, data, selectedData, decoratorContext, null);
782     }
783 
784     @SuppressWarnings("unchecked")
785     protected <E> void initBeanFilterableComboBox(
786         final BeanFilterableComboBox<E> comboBox,
787         final List<E> data,
788         final E selectedData,
789         final String decoratorContext,
790         final String toolTipDecoratorContext) {
791 
792         Assert.notNull(comboBox, "No comboBox!");
793 
794         final Class<E> beanType = comboBox.getBeanType();
795 
796         Assert.notNull(beanType, "No beanType on the combobox!");
797 
798         Decorator<E> decorator = getDecorator(beanType, decoratorContext);
799 
800         List<E> dataTemp = data;
801         if (dataTemp == null) {
802             dataTemp = Lists.newArrayList();
803         }
804         if (LOG.isDebugEnabled()) {
805             LOG.debug("entity comboBox list [" + beanType.getName() + "] : " + dataTemp.size());
806         }
807 
808         comboBox.setI18nPrefix(getContext().getI18nPrefix());
809 
810         // init component and add data list to combo box
811         comboBox.init(decorator, dataTemp);
812 
813         // final decorator
814         final MultiJXPathDecorator<E> finalDecorator = comboBox.getHandler().getDecorator();
815 
816         // add extended renderer
817 //        comboBox.getCombobox().setRenderer(new ExtendedDecoratorListCellRenderer(finalDecorator, getConfig().getColorSelectedRow(), false));
818         // TODO assume laf is Nimbus but getConfig().getColorSelectedRow() is missing
819         comboBox.getCombobox().setRenderer(newComboBoxListCellRenderer(finalDecorator));
820 
821         // choose tooltip decorator
822         final MultiJXPathDecorator<E> toolTipDecorator = toolTipDecoratorContext != null
823             ? getDecorator(beanType, toolTipDecoratorContext)
824             : finalDecorator; // the combobox decorator by default
825 
826         // add listener to add tooltip text on combo
827         comboBox.addPropertyChangeListener(BeanFilterableComboBox.PROPERTY_SELECTED_ITEM, evt -> {
828             E newValue = (E) evt.getNewValue();
829             String tooltip = beanType.isInstance(newValue) ? toolTipDecorator.toString(newValue) : null;
830             comboBox.getCombobox().setToolTipText(tooltip);
831         });
832 
833         comboBox.setSelectedItem(selectedData);
834 
835         if (LOG.isDebugEnabled()) {
836             LOG.debug("combo [" + beanType.getName() + "] : " + comboBox.getData().size());
837         }
838     }
839 
840     protected <E> ListCellRenderer newComboBoxListCellRenderer(MultiJXPathDecorator<E> decorator) {
841         Assert.notNull(decorator);
842         return new ExtendedDecoratorListCellRenderer(decorator, UIManager.getColor("nimbusSelectionBackground"), false);
843     }
844 
845     /**
846      * {@inheritDoc}
847      */
848     @Override
849     protected void initLabel(JLabel jLabel) {
850         super.initLabel(jLabel);
851 
852         // add validator label property if this label is labelling
853         if (jLabel.getLabelFor() instanceof JComponent) {
854             JComponent c = (JComponent) jLabel.getLabelFor();
855             if (c.getClientProperty("validatorLabel") == null) {
856                 c.putClientProperty("validatorLabel", jLabel.getText());
857             }
858         }
859     }
860 
861     /**
862      * <p>initActionComboBox.</p>
863      *
864      * @param combo a {@link javax.swing.JComboBox} object.
865      */
866     protected void initActionComboBox(final JComboBox<?> combo) {
867         Assert.notNull(combo, "no combobox to init");
868         Assert.notNull(combo.getModel(), "the combobox has no model");
869         Assert.isInstanceOf(ActionComboBoxModel.class, combo.getModel(), "the combobox model must be a ActionListModel");
870 
871         combo.setEditable(false);
872         combo.setRenderer(new ActionListCellRenderer(combo));
873 
874         // add actions listeners here (remove from jaxx files)
875         combo.addActionListener(this::actionComboBoxAction);
876         combo.addMouseListener(new MouseAdapter() {
877 
878             @Override
879             public void mouseClicked(MouseEvent e) {
880                 JComboBox<?> combo = (JComboBox<?>) e.getSource();
881                 if (combo.isEnabled()) {
882                     actionComboBoxAction(e);
883                 }
884             }
885 
886             @Override
887             public void mouseEntered(MouseEvent e) {
888                 JComboBox<?> combo = (JComboBox<?>) e.getSource();
889                 if (combo.isEnabled()) {
890                     combo.showPopup();
891                 }
892             }
893         });
894     }
895 
896     /**
897      * return a combo box model initialized with at least 1 element
898      *
899      * @param item  the first item to render in the combo editor part
900      * @param items other items to render in the combo list part
901      * @return a {@link javax.swing.DefaultComboBoxModel} object.
902      */
903     public DefaultComboBoxModel newActionComboBoxModel(Object item, Object... items) {
904         AbstractButton button = (AbstractButton) item;
905         AbstractButton[] otherButtons = Arrays.copyOf(items, items.length, AbstractButton[].class);
906         return new ActionComboBoxModel(button, otherButtons);
907     }
908 
909     /**
910      * <p>actionComboBoxAction.</p>
911      *
912      * @param event a {@link java.awt.event.ActionEvent} object.
913      */
914     protected void actionComboBoxAction(EventObject event) {
915         JComboBox<?> existingCombo = (JComboBox<?>) event.getSource();
916         JButton selectedButton = (JButton) existingCombo.getSelectedItem();
917         // reset action combo model
918         if (existingCombo.getModel() instanceof ActionComboBoxModel) {
919             ((ActionComboBoxModel) existingCombo.getModel()).reset();
920         } else {
921             existingCombo.setSelectedIndex(0);
922         }
923 
924         // hide popup before performing the action
925         existingCombo.hidePopup();
926         if (selectedButton != null && selectedButton.isEnabled()) {
927             if (selectedButton.getAction() != null) {
928                 getContext().getActionEngine().runAction(selectedButton);
929             } else {
930                 selectedButton.doClick();
931             }
932         }
933     }
934 
935     // ------------------------------------------------------------------------//
936     // -- Internal methods --//
937     // ------------------------------------------------------------------------//
938 
939     /**
940      * <p>registerValidators.</p>
941      *
942      * @param validators a {@link jaxx.runtime.validator.swing.SwingValidator} object.
943      */
944     @SuppressWarnings("unchecked")
945     protected void registerValidators(SwingValidator... validators) {
946         MainUI main = getContext().getMainUI();
947         Assert.notNull(main, "No mainUI registered in application context");
948         AbstractMainUIHandler handler = (AbstractMainUIHandler) main.getHandler();
949         handler.clearValidators();
950         for (SwingValidator validator : validators) {
951             handler.registerValidator(validator);
952         }
953     }
954 
955     /**
956      * <p>listenValidatorValid.</p>
957      *
958      * @param validator a {@link org.nuiton.validator.bean.simple.SimpleBeanValidator} object.
959      * @param model     a {@link AbstractBeanUIModel} object.
960      */
961     protected void listenValidatorValid(SimpleBeanValidator validator, final AbstractBeanUIModel model) {
962 
963         stopListenValidatorValid(validator);
964         Assert.notNull(model);
965 
966         PropertyChangeListener listener = evt -> {
967             if (LOG.isDebugEnabled()) {
968                 LOG.debug("method listenValidatorValid " + "Model [" + model + "] pass to valid state [" + evt.getNewValue() + "]");
969             }
970             model.setValid((Boolean) evt.getNewValue());
971         };
972         validator.addPropertyChangeListener(SimpleBeanValidator.VALID_PROPERTY, listener);
973         validatorValidListenerMap.put(validator, listener);
974     }
975 
976     /**
977      * <p>stopListenValidatorValid.</p>
978      *
979      * @param validator a {@link org.nuiton.validator.bean.simple.SimpleBeanValidator} object.
980      */
981     protected void stopListenValidatorValid(SimpleBeanValidator validator) {
982         PropertyChangeListener listener = validatorValidListenerMap.get(validator);
983         if (listener != null) {
984             validator.removePropertyChangeListener(SimpleBeanValidator.VALID_PROPERTY, listener);
985             validatorValidListenerMap.remove(validator);
986         }
987     }
988 
989     /**
990      * <p>listenValidationTableHasNoFatalError.</p>
991      *
992      * @param validator a {@link org.nuiton.validator.bean.simple.SimpleBeanValidator} object.
993      * @param model     a {@link AbstractBeanUIModel} object.
994      */
995     protected void listenValidationTableHasNoFatalError(final SimpleBeanValidator validator,
996                                                         final AbstractBeanUIModel model) {
997         getContext().getMainUI().getValidatorMessageWidget().addTableModelListener(e -> {
998             boolean valid = !validator.hasFatalErrors();
999             if (LOG.isDebugEnabled()) {
1000                 LOG.debug("method listenValidationTableHasNoFatalError " + "Model [" + model
1001                     + "] pass to valid state [" + valid + "]");
1002             }
1003             model.setValid(valid);
1004         });
1005     }
1006 
1007     /**
1008      * <p>listModelIsModify.</p>
1009      *
1010      * @param model a {@link AbstractBeanUIModel} object.
1011      */
1012     protected void listModelIsModify(AbstractBeanUIModel model) {
1013         model.addPropertyChangeListener(new PropertyChangeListener() {
1014 
1015             final Set<String> excludeProperties = getPropertiesToIgnore();
1016 
1017             @Override
1018             public void propertyChange(PropertyChangeEvent evt) {
1019                 if (!excludeProperties.contains(evt.getPropertyName())) {
1020                     ((AbstractBeanUIModel) evt.getSource()).setModify(true);
1021                 }
1022             }
1023         });
1024     }
1025 
1026     /**
1027      * <p>getPropertiesToIgnore.</p>
1028      *
1029      * @return a {@link java.util.Set} object.
1030      */
1031     protected Set<String> getPropertiesToIgnore() {
1032         return Sets.newHashSet(
1033             AbstractBeanUIModel.PROPERTY_MODIFY,
1034             AbstractBeanUIModel.PROPERTY_VALID);
1035     }
1036 
1037     /**
1038      * <p>askBefore.</p>
1039      *
1040      * @param title   a {@link java.lang.String} object.
1041      * @param message a {@link java.lang.String} object.
1042      * @return a boolean.
1043      */
1044     public boolean askBefore(String title, String message) {
1045         return getContext().getDialogHelper().showConfirmDialog(message, title, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1046     }
1047 
1048     /**
1049      * <p>askOverwriteFile.</p>
1050      *
1051      * @param file a {@link java.io.File} object.
1052      * @return a boolean.
1053      */
1054     public boolean askOverwriteFile(File file) {
1055         if (!file.exists()) {
1056             // file does not exist
1057             return true;
1058         }
1059 
1060         // file exists ask user to overwrite
1061         return getContext().getDialogHelper().showConfirmDialog(
1062             t("quadrige3.action.common.askOverwriteFile.message", file),
1063             t("quadrige3.action.common.askOverwriteFile.title"), DialogHelper.OVERWRITE_FILE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1064 
1065     }
1066 
1067     /**
1068      * {@inheritDoc}
1069      */
1070     @Override
1071     public int askSaveBeforeLeaving(String message) {
1072         return getContext().getDialogHelper().showConfirmDialog(message, t("quadrige3.action.common.askSaveBeforeLeaving.title"), DialogHelper.SAVE_NO_SAVE_CANCEL_OPTION);
1073     }
1074 
1075     /**
1076      * {@inheritDoc}
1077      */
1078     @Override
1079     public boolean askCancelEditBeforeLeaving(String message) {
1080         return getContext().getDialogHelper().showConfirmDialog(
1081             message, t("quadrige3.action.common.askCancelEditBeforeLeaving.title"), DialogHelper.NO_SAVE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1082     }
1083 
1084     /**
1085      * <p>askBeforeDelete.</p>
1086      *
1087      * @param title   a {@link java.lang.String} object.
1088      * @param message a {@link java.lang.String} object.
1089      * @return a boolean.
1090      */
1091     public boolean askBeforeDelete(String title, String message) {
1092         return getContext().getDialogHelper().showConfirmDialog(message, title, DialogHelper.DELETE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1093     }
1094 
1095     /**
1096      * <p>askBeforeDeleteMany.</p>
1097      *
1098      * @param title   a {@link java.lang.String} object.
1099      * @param message a {@link java.lang.String} object.
1100      * @param list    a {@link java.util.List} object.
1101      * @return a boolean.
1102      */
1103     public boolean askBeforeDeleteMany(String title, String message, List<String> list) {
1104         return getContext().getDialogHelper().showConfirmDialog(message, ApplicationUIUtil.getHtmlString(list), null, title, DialogHelper.DELETE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1105     }
1106 
1107     /**
1108      * <p>askBeforeChangeTab.</p>
1109      *
1110      * @param message a {@link java.lang.String} object.
1111      * @return a int.
1112      */
1113     public int askBeforeChangeTab(String message) {
1114         return getContext().getDialogHelper().showConfirmDialog(message, t("quadrige3.action.common.askBeforeChangeTab.title"), DialogHelper.SAVE_NO_SAVE_CANCEL_OPTION);
1115     }
1116 
1117     /**
1118      * <p>askBeforeReset.</p>
1119      *
1120      * @param title   a {@link java.lang.String} object.
1121      * @param message a {@link java.lang.String} object.
1122      * @return a boolean.
1123      */
1124     public boolean askBeforeReset(String title, String message) {
1125         return getContext().getDialogHelper().showConfirmDialog(message, title, DialogHelper.RESET_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1126     }
1127 
1128     /**
1129      * <p>askDeleteInvalidBeforeLeaving.</p>
1130      *
1131      * @param message a {@link java.lang.String} object.
1132      * @return a boolean.
1133      */
1134     public boolean askDeleteInvalidBeforeLeaving(String message) {
1135         return getContext().getDialogHelper().showConfirmDialog(
1136             message, t("quadrige3.action.common.askDeleteInvalidBeforeLeaving.title"), DialogHelper.DELETE_INVALID_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1137     }
1138 
1139     /**
1140      * {@inheritDoc}
1141      */
1142     @Override
1143     protected void initScrollPane(JScrollPane scrollPane) {
1144         // don't call super.initScrollPane(scrollPane); see below
1145 
1146         Boolean onlyVerticalScrollable = (Boolean) scrollPane.getClientProperty("onlyVerticalScrollable");
1147         if (onlyVerticalScrollable != null && onlyVerticalScrollable) {
1148             scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1149             scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
1150 
1151             // Encapsulate the viewport view in a JXPanel to avoid constant redraw when component has a size > view port size (see mantis #31981)
1152             Component component = scrollPane.getViewport().getView();
1153             JXPanel innerPanel = new JXPanel(new BorderLayout());
1154             innerPanel.add(component);
1155             scrollPane.setViewportView(innerPanel);
1156             innerPanel.setScrollableTracksViewportHeight(false);
1157             innerPanel.setScrollableTracksViewportWidth(true);
1158         }
1159 
1160         // Init ScrollPane speed on non-table component
1161         if (scrollPane.getViewport().getComponentCount() > 0) {
1162             boolean isTable = false;
1163             for (Component component : scrollPane.getViewport().getComponents()) {
1164                 if (component instanceof JTable) {
1165                     isTable = true;
1166                     break;
1167                 }
1168             }
1169             if (!isTable) {
1170                 scrollPane.getVerticalScrollBar().setUnitIncrement(20);
1171             }
1172         }
1173     }
1174 
1175     /*
1176      * DIALOG HANDLING
1177      */
1178 
1179     /**
1180      * <p>openDialog.</p>
1181      *
1182      * @param dialog a {@link javax.swing.JDialog} object.
1183      */
1184     public void openDialog(JDialog dialog) {
1185         openDialog(dialog, null);
1186     }
1187 
1188     /**
1189      * <p>openDialog.</p>
1190      *
1191      * @param dialog a {@link javax.swing.JDialog} object.
1192      * @param size   a {@link java.awt.Dimension} object.
1193      */
1194     public void openDialog(JDialog dialog, Dimension size) {
1195         openDialog(dialog, size, false, null);
1196     }
1197 
1198     /**
1199      * <p>openDialogForceOnTop.</p>
1200      *
1201      * @param dialog a {@link javax.swing.JDialog} object.
1202      */
1203     public void openDialogForceOnTop(JDialog dialog) {
1204         openDialogForceOnTop(dialog, null);
1205     }
1206 
1207     /**
1208      * <p>openDialogForceOnTop.</p>
1209      *
1210      * @param dialog a {@link javax.swing.JDialog} object.
1211      * @param size   a {@link java.awt.Dimension} object.
1212      */
1213     public void openDialogForceOnTop(JDialog dialog, Dimension size) {
1214         openDialog(dialog, size, true, null);
1215     }
1216 
1217     public void openDialog(JDialog dialog, Dimension size, boolean forceOnTop, Component centerComponent) {
1218 
1219         // keep actual ActionUI
1220         ActionUI oldApplicationActionUI = getContext().getActionUI();
1221 
1222         try {
1223             // Create a new ActionUI depending on this JDialog
1224             final ActionUI dialogActionUI = new ActionUI(dialog, true) {
1225                 @Override
1226                 protected ApplicationActionUIHandler createHandler() {
1227                     // the context must be set before ActionUI initialization
1228                     ApplicationUIUtil.setApplicationContext(this, getContext());
1229                     return super.createHandler();
1230                 }
1231             };
1232             getContext().setActionUI(dialogActionUI);
1233 
1234             // Init dialog behavior
1235             initDialog(dialog, null);
1236 
1237             if (CollectionUtils.isEmpty(dialog.getIconImages())) {
1238                 dialog.setIconImage(getContext().getMainUI().getIconImage());
1239             }
1240 
1241             if (size != null) {
1242                 dialog.setSize(size);
1243             } else {
1244                 dialog.pack();
1245             }
1246 
1247             // Center dialog on main ui or the specified component
1248             dialog.setLocationRelativeTo(centerComponent != null ? centerComponent : getContext().getMainUI());
1249 
1250             // Set modality type depending on forceOnTop property
1251             dialog.setModalityType(forceOnTop
1252                 ? Dialog.ModalityType.TOOLKIT_MODAL
1253                 : Dialog.ModalityType.APPLICATION_MODAL);
1254             dialog.setVisible(true);
1255 
1256         } finally {
1257 
1258             // restore previous ActionUI
1259             getContext().setActionUI(oldApplicationActionUI);
1260         }
1261 
1262     }
1263 
1264     @Override
1265     public void openDialog(org.nuiton.jaxx.application.swing.ApplicationUI dialogContent, String title, Dimension dim) {
1266         openDialog(dialogContent, title, dim, null);
1267     }
1268 
1269     public void openDialog(org.nuiton.jaxx.application.swing.ApplicationUI dialogContent, String title, Dimension dim, Component centerComponent) {
1270 
1271         Component topestUI = getTopestUI();
1272 
1273         JDialog dialog;
1274         if (topestUI instanceof Frame) {
1275             dialog = new JDialog((Frame) topestUI, title, true);
1276         } else {
1277             dialog = new JDialog((Dialog) topestUI, title, true);
1278         }
1279 
1280         // Init dialog behavior
1281         initDialog(dialog, dialogContent.getHandler());
1282 
1283         dialog.setIconImage(getContext().getMainUI().getIconImage());
1284         Component dialogComponent = (Component) dialogContent;
1285         dialogComponent.setVisible(true);
1286         dialog.add(dialogComponent);
1287         dialog.setResizable(true);
1288         dialog.setName("dialog_" + dialogComponent.getName());
1289 
1290         if (dim != null) {
1291             dialog.setSize(dim);
1292         } else {
1293             dialog.pack();
1294         }
1295 
1296         // Center dialog on main ui or the specified component
1297         dialog.setLocationRelativeTo(centerComponent != null ? centerComponent : getContext().getMainUI());
1298 
1299         dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
1300         dialog.setVisible(true);
1301     }
1302 
1303     public JFrame createFrame(Component dialogContent, String title, Image iconImage, Rectangle dim) {
1304 
1305         JFrame frame = new JFrame();
1306         frame.setTitle(getContext().getMainUI().getTitle() + (StringUtils.isNotEmpty(title) ? " - " + title : ""));
1307         if (iconImage != null) {
1308             frame.setIconImage(iconImage);
1309         } else {
1310             frame.setIconImage(getContext().getMainUI().getIconImage());
1311         }
1312 
1313         Container dialogComponent;
1314         if (dialogContent instanceof Container) {
1315             dialogComponent = (Container) dialogContent;
1316         } else {
1317             dialogComponent = new JPanel(new BorderLayout());
1318             dialogComponent.add(dialogContent);
1319         }
1320 
1321         // Init frame behavior if content if an Application UI
1322         if (dialogContent instanceof ApplicationUI) {
1323             initDialog(frame, ((ApplicationUI) dialogContent).getHandler());
1324         }
1325         dialogComponent.setVisible(true);
1326         frame.setContentPane(dialogComponent);
1327         frame.setName("frame_" + dialogComponent.getName());
1328 
1329         if (dim != null) {
1330             frame.setBounds(dim);
1331         } else {
1332             frame.pack();
1333         }
1334 
1335         frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
1336         return frame;
1337     }
1338 
1339     public JFrame openFrame(Component dialogContent, String title, Image iconImage, Rectangle dim) {
1340 
1341         JFrame frame = createFrame(dialogContent, title, iconImage, dim);
1342         SwingUtilities.invokeLater(() -> frame.setVisible(true));
1343         return frame;
1344     }
1345 
1346     private void initDialog(Window dialog, AbstractApplicationUIHandler dialogHandler) {
1347 
1348         final AbstractApplicationUIHandler handler;
1349         if (dialogHandler == null) {
1350             handler = dialog instanceof ApplicationUI
1351                 ? ((ApplicationUI) dialog).getHandler()
1352                 : null;
1353         } else {
1354             handler = dialogHandler;
1355         }
1356 
1357         if (handler instanceof Cancelable) {
1358 
1359             // add a auto-cancel action
1360             final Action cancelAction = new AbstractAction() {
1361 
1362                 @Override
1363                 public void actionPerformed(ActionEvent e) {
1364                     ((Cancelable) handler).cancel();
1365                 }
1366             };
1367 
1368             JRootPane rootPane = ((RootPaneContainer) dialog).getRootPane();
1369             KeyStroke shortcutClosePopup = getContext().getConfiguration().getShortcutClosePopup();
1370             rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcutClosePopup, "close");
1371             rootPane.getActionMap().put("close", cancelAction);
1372 
1373             dialog.addWindowListener(new WindowAdapter() {
1374                 @Override
1375                 public void windowClosing(WindowEvent e) {
1376                     cancelAction.actionPerformed(null);
1377                 }
1378             });
1379         }
1380 
1381         dialog.addWindowListener(new WindowAdapter() {
1382 
1383             @Override
1384             public void windowClosed(WindowEvent e) {
1385                 Optional.ofNullable(handler).ifPresent(AbstractApplicationUIHandler::closeDialog);
1386             }
1387         });
1388 
1389     }
1390 
1391     /**
1392      * Close the dialog only. Note: Don't destroy ui, cause side effects in parent listeners
1393      * <p>
1394      * {@inheritDoc}
1395      */
1396     @Override
1397     public void closeDialog() {
1398 
1399         ApplicationUIUtil.destroy(getUI());
1400 
1401         if (getUI() instanceof JDialog) {
1402             if (LOG.isDebugEnabled()) {
1403                 LOG.debug("Destroy dialog " + getUI());
1404             }
1405             ((JDialog) getUI()).dispose();
1406         } else {
1407             JDialog parent = getParentContainer(JDialog.class);
1408             if (parent != null) {
1409                 if (LOG.isDebugEnabled()) {
1410                     LOG.debug("Destroy dialog " + parent + " of " + getUI());
1411                 }
1412                 parent.dispose();
1413             }
1414         }
1415     }
1416 
1417 }