View Javadoc
1   package fr.ifremer.quadrige2.ui.swing.common;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 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.base.Preconditions;
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Maps;
29  import com.google.common.collect.Sets;
30  import fr.ifremer.quadrige2.core.config.Quadrige2CoreConfiguration;
31  import fr.ifremer.quadrige2.core.dao.technical.decorator.DecoratorComparator;
32  import fr.ifremer.quadrige2.core.service.ClientServiceLocator;
33  import fr.ifremer.quadrige2.core.service.decorator.DecoratorService;
34  import fr.ifremer.quadrige2.ui.swing.common.action.AbstractChangeScreenAction;
35  import fr.ifremer.quadrige2.ui.swing.common.action.ActionUI;
36  import fr.ifremer.quadrige2.ui.swing.common.component.ActionComboBoxModel;
37  import fr.ifremer.quadrige2.ui.swing.common.component.ActionListCellRenderer;
38  import fr.ifremer.quadrige2.ui.swing.common.component.ColoredFilteredListCellRenderer;
39  import fr.ifremer.quadrige2.ui.swing.common.component.ExtendedDecoratorListCellRenderer;
40  import fr.ifremer.quadrige2.ui.swing.common.component.bean.ExtendedBeanDoubleList;
41  import fr.ifremer.quadrige2.ui.swing.common.component.bean.ExtendedComboBox;
42  import fr.ifremer.quadrige2.ui.swing.common.component.coordinate.CoordinateEditor;
43  import fr.ifremer.quadrige2.ui.swing.common.content.AbstractMainUIHandler;
44  import fr.ifremer.quadrige2.ui.swing.common.content.MainUI;
45  import fr.ifremer.quadrige2.ui.swing.common.model.AbstractBeanUIModel;
46  import fr.ifremer.quadrige2.ui.swing.common.plaf.HintSynthTextFieldUI;
47  import fr.ifremer.quadrige2.ui.swing.common.table.AbstractRowUIModel;
48  import fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier;
49  import fr.ifremer.quadrige2.ui.swing.common.table.editor.CoordinateCellEditor;
50  import fr.ifremer.quadrige2.ui.swing.common.table.editor.ExtendedComboBoxCellEditor;
51  import fr.ifremer.quadrige2.ui.swing.common.table.editor.FilterableComboBoxCellEditor;
52  import fr.ifremer.quadrige2.ui.swing.common.table.editor.NumberCellEditor;
53  import fr.ifremer.quadrige2.ui.swing.common.table.renderer.ExtendedDecoratorCellRenderer;
54  import fr.ifremer.quadrige2.ui.swing.common.table.renderer.NumberCellRenderer;
55  import jaxx.runtime.SwingUtil;
56  import jaxx.runtime.swing.editor.bean.BeanDoubleList;
57  import jaxx.runtime.swing.editor.bean.BeanFilterableComboBox;
58  import jaxx.runtime.swing.model.JaxxFilterableListModel;
59  import jaxx.runtime.validator.swing.SwingValidator;
60  import org.apache.commons.collections4.CollectionUtils;
61  import org.apache.commons.lang3.ArrayUtils;
62  import org.apache.commons.lang3.StringUtils;
63  import org.apache.commons.lang3.time.DateUtils;
64  import org.apache.commons.logging.Log;
65  import org.apache.commons.logging.LogFactory;
66  import org.jdatepicker.AbstractComponentColor;
67  import org.jdatepicker.JDatePanel;
68  import org.jdatepicker.JDatePicker;
69  import org.jdesktop.swingx.JXDatePicker;
70  import org.jdesktop.swingx.JXPanel;
71  import org.jdesktop.swingx.JXTable;
72  import org.jdesktop.swingx.decorator.AbstractHighlighter;
73  import org.jdesktop.swingx.decorator.ComponentAdapter;
74  import org.jdesktop.swingx.decorator.HighlightPredicate;
75  import org.jdesktop.swingx.renderer.DefaultTableRenderer;
76  import org.jdesktop.swingx.renderer.StringValue;
77  import org.jdesktop.swingx.table.TableColumnExt;
78  import org.nuiton.decorator.Decorator;
79  import org.nuiton.decorator.JXPathDecorator;
80  import org.nuiton.jaxx.application.bean.JavaBeanObjectUtil;
81  import org.nuiton.jaxx.application.swing.AbstractApplicationUIHandler;
82  import org.nuiton.jaxx.application.swing.action.ApplicationActionUIHandler;
83  import org.nuiton.jaxx.application.swing.action.ApplicationUIAction;
84  import org.nuiton.jaxx.application.swing.util.Cancelable;
85  import org.nuiton.jaxx.widgets.number.NumberEditor;
86  import org.nuiton.validator.bean.simple.SimpleBeanValidator;
87  
88  import javax.swing.*;
89  import javax.swing.event.*;
90  import javax.swing.plaf.nimbus.NimbusLookAndFeel;
91  import javax.swing.table.TableCellEditor;
92  import javax.swing.table.TableCellRenderer;
93  import javax.swing.table.TableColumnModel;
94  import javax.swing.text.AbstractDocument;
95  import java.awt.*;
96  import java.awt.event.*;
97  import java.beans.PropertyChangeEvent;
98  import java.beans.PropertyChangeListener;
99  import java.io.File;
100 import java.io.Serializable;
101 import java.math.BigDecimal;
102 import java.text.DecimalFormat;
103 import java.util.*;
104 import java.util.List;
105 
106 import static org.nuiton.i18n.I18n.t;
107 
108 /**
109  * Contract of any UI handler.
110  *
111  * @param <M>
112  * @param <UI>
113  * @author Lionel Touseau <lionel.touseau@e-is.pro>
114  * @since 1.0
115  */
116 public abstract class AbstractUIHandler<M, UI extends ApplicationUI<M, ?>>
117         extends AbstractApplicationUIHandler<M, UI>
118         implements UIMessageNotifier {
119 
120     /**
121      * Logger.
122      */
123     private static final Log LOG = LogFactory.getLog(AbstractUIHandler.class);
124 
125     private final Map<AbstractBeanUIModel, PropertyChangeListener> modelModifyListenerMap = Maps.newHashMap();
126     private final Map<SimpleBeanValidator, PropertyChangeListener> validatorValidListenerMap = Maps.newHashMap();
127 
128     // ------------------------------------------------------------------------//
129     // -- Public methods --//
130     // ------------------------------------------------------------------------//
131 
132     /**
133      * <p>changeScreenAction.</p>
134      *
135      * @param screenActionClass a {@link java.lang.Class} object.
136      */
137     public void changeScreenAction(Class<? extends AbstractChangeScreenAction> screenActionClass) {
138         ApplicationUIAction<? extends AbstractChangeScreenAction> screenAction;
139         screenAction = getContext().getActionFactory().createUIAction(getContext().getMainUI().getHandler(), screenActionClass);
140         getContext().getActionEngine().runAction(screenAction.getLogicAction());
141     }
142 
143     /**
144      * {@inheritDoc}
145      */
146     @Override
147     public void showInformationMessage(String message) {
148         getContext().showInformationMessage(message);
149     }
150 
151     /**
152      * <p>showNotImplementedFunctionnality.</p>
153      */
154     public void showNotImplementedFunctionnality() {
155         getContext().getDialogHelper().showMessageDialog("Fonctionnalité à venir ...");
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     @Override
162     public ApplicationUIContext getContext() {
163         return (ApplicationUIContext) super.getContext();
164     }
165 
166     /**
167      * get the screen title
168      *
169      * @return the screen title
170      */
171     public String getTitle() {
172         return t(getUI().getClass().getSimpleName() + ".title");
173     }
174 
175     /**
176      * <p>getConfig.</p>
177      *
178      * @return a {@link Quadrige2CoreConfiguration} object.
179      */
180     public Quadrige2CoreConfiguration getConfig() {
181         return getContext().getConfiguration();
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
188     public Component getTopestUI() {
189         return getContext().getActionUI();
190     }
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
196     public void onCloseUI() {
197         // default close actions
198         if (LOG.isDebugEnabled()) {
199             LOG.debug("closing: " + getUI());
200         }
201 
202         ApplicationUIUtil.destroy(getUI());
203         clearValidators();
204 
205         if (getUI() instanceof Window) {
206             ((Window) getUI()).dispose();
207         }
208     }
209 
210     /**
211      * <p>clearValidators.</p>
212      */
213     public void clearValidators() {
214         MainUI main = getContext().getMainUI();
215         Preconditions.checkNotNull(main, "No mainUI registered in application context");
216         AbstractMainUIHandler handler = (AbstractMainUIHandler) main.getHandler();
217         handler.clearValidators();
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     @Override
224     protected JComponent getComponentToFocus() {
225         return null;
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     @Override
232     public SwingValidator<M> getValidator() {
233         return null;
234     }
235 
236     /**
237      * <p>forceRevalidateModel.</p>
238      */
239     public void forceRevalidateModel() {
240 
241         if (getValidator() != null) {
242             getValidator().doValidate();
243         }
244         if (getModel() instanceof AbstractBeanUIModel) {
245             ((AbstractBeanUIModel) getModel()).firePropertyChanged(AbstractBeanUIModel.PROPERTY_VALID, null, null);
246         }
247     }
248 
249     /**
250      * {@inheritDoc}
251      */
252     @Override
253     public <O> Decorator<O> getDecorator(Class<O> type, String name) {
254         DecoratorService decoratorService = ClientServiceLocator.instance().getDecoratorService();
255 
256         Preconditions.checkNotNull(type);
257 
258         return decoratorService.getDecoratorByType(type, name);
259     }
260 
261     /**
262      * {@inheritDoc}
263      */
264     @Override
265     public String decorate(Serializable object, String context) {
266         return super.decorate(object, context);
267     }
268 
269     /**
270      * {@inheritDoc}
271      */
272     @Override
273     public String decorate(Serializable object) {
274         return super.decorate(object);
275     }
276 
277     /**
278      * <p>newBigDecimalRenderer.</p>
279      *
280      * @return a {@link javax.swing.table.TableCellRenderer} object.
281      */
282     public TableCellRenderer newBigDecimalRenderer() {
283         DefaultTableRenderer defaultRenderer = new DefaultTableRenderer(new StringValue() {
284 
285             @Override
286             public String getString(Object value) {
287 
288                 // be sure the value is a BigDecimal
289                 if (value instanceof BigDecimal) {
290                     BigDecimal bigDecimal = (BigDecimal) value;
291                     return bigDecimal.toString();
292                 }
293 
294                 return "";
295             }
296 
297         }, JLabel.RIGHT);
298         return new NumberCellRenderer(defaultRenderer);
299     }
300 
301     /**
302      * return a renderer for numbers with correct decimal format
303      *
304      * @param nbDecimal number of decimals in string representation
305      * @return a TableCellRenderer
306      */
307     public TableCellRenderer newNumberCellRenderer(final int nbDecimal) {
308         DefaultTableRenderer defaultRenderer = new DefaultTableRenderer(new StringValue() {
309 
310             @Override
311             public String getString(Object value) {
312                 if (value == null) {
313                     return "";
314                 }
315                 // compute min and max number of decimals
316                 int minDecimal = (value instanceof Integer) ? 0 : 1;
317                 int maxDecimal = (value instanceof Integer) ? 0 : nbDecimal;
318                 // get number formatter
319                 DecimalFormat decimalFormat = ApplicationUIUtil.getDecimalFormat(minDecimal, maxDecimal);
320                 return decimalFormat.format(value);
321             }
322 
323         }, JLabel.RIGHT);
324         return new NumberCellRenderer(defaultRenderer);
325     }
326 
327     /**
328      * <p>newNumberCellEditor.</p>
329      *
330      * @param numberClass   a {@link java.lang.Class} object.
331      * @param useSign       a boolean.
332      * @param numberPattern a {@link java.lang.String} object.
333      * @param minValue      a N object.
334      * @param maxValue      a N object.
335      * @param <N>           a N object.
336      * @return a {@link javax.swing.table.TableCellEditor} object.
337      */
338     public <N extends Number> TableCellEditor newNumberCellEditor(Class<N> numberClass, boolean useSign, String numberPattern, N minValue, N maxValue) {
339         return new NumberCellEditor<>(numberClass, useSign, numberPattern, minValue, maxValue);
340     }
341 
342     /**
343      * <p>newNumberCellEditor.</p>
344      *
345      * @param numberClass   a {@link java.lang.Class} object.
346      * @param useSign       a boolean.
347      * @param numberPattern a {@link java.lang.String} object.
348      * @param <N>           a N object.
349      * @return a {@link javax.swing.table.TableCellEditor} object.
350      */
351     public <N extends Number> TableCellEditor newNumberCellEditor(Class<N> numberClass, boolean useSign, String numberPattern) {
352         return newNumberCellEditor(numberClass, useSign, numberPattern, null, null);
353     }
354 
355     /**
356      * <p>addCoordinateColumnToModel.</p>
357      *
358      * @param model          a {@link javax.swing.table.TableColumnModel} object.
359      * @param coordinateType a {@link fr.ifremer.quadrige2.ui.swing.common.component.coordinate.CoordinateEditor.CoordinateType} object.
360      * @param identifier     a {@link fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier} object.
361      * @return a {@link org.jdesktop.swingx.table.TableColumnExt} object.
362      * <p>
363      * TODO move to TableUIHandler
364      */
365     protected TableColumnExt addCoordinateColumnToModel(
366             TableColumnModel model,
367             CoordinateEditor.CoordinateType coordinateType,
368             ColumnIdentifier<?> identifier) {
369 
370         TableCellEditor editor = new CoordinateCellEditor(coordinateType);
371 
372         TableCellRenderer renderer = newNumberCellRenderer(6);
373 
374         return addColumnToModel(model, editor, renderer, identifier);
375     }
376 
377     /**
378      * {@inheritDoc}
379      * <p>
380      * override method to set mandatory header if needed
381      */
382     @Override
383     protected <R> TableColumnExt addColumnToModel(
384             TableColumnModel model,
385             TableCellEditor editor,
386             TableCellRenderer renderer,
387             org.nuiton.jaxx.application.swing.table.ColumnIdentifier<R> identifier) {
388 
389         TableColumnExt column = null;
390 
391         if (identifier instanceof ColumnIdentifier) {
392             final ColumnIdentifier<?> columnIdentifier = (ColumnIdentifier<?>) identifier;
393 
394             // add a default renderer
395             if (renderer == null) {
396                 Decorator decorator = getDecorator(columnIdentifier.getPropertyType(), columnIdentifier.getDecoratorName());
397                 if (decorator != null) {
398                     renderer = new ExtendedDecoratorCellRenderer(decorator);
399                 } else {
400                     renderer = new TableCellRenderer() {
401 
402                         @Override
403                         public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row,
404                                                                        int column) {
405                             TableCellRenderer defaultRenderer = table.getDefaultRenderer(columnIdentifier.getPropertyType());
406                             JComponent component = (JComponent) defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
407 
408                             // add tooltip
409                             if (value != null) {
410                                 component.setToolTipText(value.toString());
411                             }
412 
413                             return component;
414                         }
415                     };
416                 }
417             }
418 
419             // add to model
420             column = new TableColumnExt(model.getColumnCount());
421             model.addColumn(column);
422             column.setIdentifier(columnIdentifier);
423             column.setCellEditor(editor);
424             column.setCellRenderer(renderer);
425 
426             // if the column is mandatory, set specific headerValue and toolTip
427             if (columnIdentifier.isMandatory()) {
428                 column.setHeaderValue(t("quadrige2.table.mandatoryColumn.header", t(columnIdentifier.getHeaderI18nKey())));
429                 column.setToolTipText(t("quadrige2.table.mandatoryColumn.tip", t(columnIdentifier.getHeaderTipI18nKey())));
430                 // this column is not hideable
431                 column.setHideable(false);
432             } else {
433                 // set header and tooltip as HTML
434                 column.setHeaderValue(ApplicationUIUtil.getHtmlString(t(columnIdentifier.getHeaderI18nKey())));
435                 column.setToolTipText(ApplicationUIUtil.getHtmlString(t(columnIdentifier.getHeaderTipI18nKey())));
436             }
437             // by default no column is sortable, must specify it
438             column.setSortable(false);
439         }
440 
441         return column;
442     }
443 
444     /**
445      * override method of AbstractApplicationUIHandler. it creates a BeanFilterableComboBox
446      *
447      * @param model      a {@link javax.swing.table.TableColumnModel} object.
448      * @param identifier a {@link fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier} object.
449      * @param data       a {@link java.util.List} object.
450      * @param resettable a boolean.
451      * @return a column
452      */
453     protected <R extends AbstractRowUIModel, B> TableColumnExt addFilterableComboDataColumnToModel(TableColumnModel model, ColumnIdentifier<R> identifier,
454                                                                                                    List<B> data, boolean resettable) {
455         @SuppressWarnings("unchecked")
456         Class<B> beanType = (Class<B>) identifier.getPropertyType();
457         String decoratorName = identifier.getDecoratorName();
458         FilterableComboBoxCellEditor<B> editor = newFilterableComboBoxCellEditor(data, beanType, decoratorName, resettable);
459         return addColumnToModel(model, editor, newTableCellRender(beanType, decoratorName), identifier);
460     }
461 
462     /**
463      * <p>addExtendedComboDataColumnToModel.</p>
464      *
465      * @param model      a {@link javax.swing.table.TableColumnModel} object.
466      * @param identifier a {@link ColumnIdentifier} object.
467      * @param data       a {@link java.util.List} object.
468      * @param resettable a boolean.
469      * @param <R>        a R object.
470      * @param <B>        a B object.
471      * @return a {@link org.jdesktop.swingx.table.TableColumnExt} object.
472      */
473     protected <R extends AbstractRowUIModel, B> TableColumnExt addExtendedComboDataColumnToModel(TableColumnModel model, ColumnIdentifier<R> identifier,
474                                                                                                  List<B> data, boolean resettable) {
475         @SuppressWarnings("unchecked")
476         Class<B> beanType = (Class<B>) identifier.getPropertyType();
477         String decoratorName = identifier.getDecoratorName();
478         FilterableComboBoxCellEditor<B> editor = newExtendedComboBoxCellEditor(data, beanType, decoratorName, resettable);
479         return addColumnToModel(model, editor, newTableCellRender(beanType, decoratorName), identifier);
480     }
481 
482     /**
483      * <p>newFilterableComboBoxCellEditor.</p>
484      *
485      * @param data       a {@link java.util.List} object.
486      * @param beanType   a {@link java.lang.Class} object.
487      * @param resettable a boolean.
488      * @param <B>        a B object.
489      * @return a {@link FilterableComboBoxCellEditor} object.
490      */
491     protected <B> FilterableComboBoxCellEditor<B> newFilterableComboBoxCellEditor(List<B> data, Class<B> beanType,
492                                                                                   boolean resettable) {
493         return newFilterableComboBoxCellEditor(data, beanType, null, resettable);
494     }
495 
496     /**
497      * <p>newFilterableComboBoxCellEditor.</p>
498      *
499      * @param data          a {@link java.util.List} object.
500      * @param beanType      a {@link java.lang.Class} object.
501      * @param decoratorName a {@link java.lang.String} object.
502      * @param resettable    a boolean.
503      * @param <B>           a B object.
504      * @return a {@link FilterableComboBoxCellEditor} object.
505      */
506     protected <B> FilterableComboBoxCellEditor<B> newFilterableComboBoxCellEditor(List<B> data, Class<B> beanType,
507                                                                                   String decoratorName, boolean resettable) {
508 
509         BeanFilterableComboBox<B> comboBox = new BeanFilterableComboBox<>(getUI());
510         preInitBeanFilterableComboBox(comboBox, beanType, resettable);
511         initBeanFilterableComboBox(comboBox, data, null, decoratorName);
512         return new FilterableComboBoxCellEditor<>(comboBox);
513     }
514 
515     /**
516      * <p>newExtendedComboBoxCellEditor.</p>
517      *
518      * @param data       a {@link java.util.List} object.
519      * @param beanType   a {@link java.lang.Class} object.
520      * @param resettable a boolean.
521      * @param <B>        a B object.
522      * @return a {@link ExtendedComboBoxCellEditor} object.
523      */
524     protected <B> ExtendedComboBoxCellEditor<B> newExtendedComboBoxCellEditor(List<B> data, Class<B> beanType, boolean resettable) {
525         return newExtendedComboBoxCellEditor(data, beanType, null, resettable);
526     }
527 
528     /**
529      * <p>newExtendedComboBoxCellEditor.</p>
530      *
531      * @param data       a {@link java.util.List} object.
532      * @param identifier a {@link ColumnIdentifier} object.
533      * @param resettable a boolean.
534      * @param <B>        a B object.
535      * @return a {@link ExtendedComboBoxCellEditor} object.
536      */
537     @SuppressWarnings("unchecked")
538     protected <B> ExtendedComboBoxCellEditor<B> newExtendedComboBoxCellEditor(List<B> data, ColumnIdentifier identifier, boolean resettable) {
539         Class<B> beanType = identifier.getPropertyType();
540         return newExtendedComboBoxCellEditor(data, beanType, identifier.getDecoratorName(), resettable);
541     }
542 
543     /**
544      * <p>newExtendedComboBoxCellEditor.</p>
545      *
546      * @param data          a {@link java.util.List} object.
547      * @param beanType      a {@link java.lang.Class} object.
548      * @param decoratorName a {@link java.lang.String} object.
549      * @param resettable    a boolean.
550      * @param <B>           a B object.
551      * @return a {@link ExtendedComboBoxCellEditor} object.
552      */
553     protected <B> ExtendedComboBoxCellEditor<B> newExtendedComboBoxCellEditor(List<B> data, Class<B> beanType,
554                                                                               String decoratorName, boolean resettable) {
555 
556         ExtendedComboBox<B> comboBox = new ExtendedComboBox<>(getUI());
557         preInitBeanFilterableComboBox(comboBox, beanType, resettable);
558         initBeanFilterableComboBox(comboBox, data, null, decoratorName);
559         return new ExtendedComboBoxCellEditor<>(comboBox);
560     }
561 
562     private <B> void preInitBeanFilterableComboBox(BeanFilterableComboBox<B> comboBox, Class<B> beanType, boolean resettable) {
563         comboBox.setFilterable(true);
564         comboBox.setShowReset(resettable);
565         comboBox.setShowDecorator(false);
566         comboBox.setBeanType(beanType);
567         comboBox.setAutoFocus(false);
568     }
569 
570     /**
571      * {@inheritDoc}
572      * <p>
573      * Override default Jaxx method because if the table selection mode is MULTIPLE_INTERVAL_SELECTION,
574      * it will add the selected row in selection model, witch is not acceptable
575      */
576     @Override
577     public void autoSelectRowInTable(MouseEvent e, JPopupMenu popup) {
578         boolean rightClick = SwingUtilities.isRightMouseButton(e);
579 
580         if (e.getSource() instanceof JXTable && (rightClick || SwingUtilities.isLeftMouseButton(e))) {
581 
582             // get the coordinates of the mouse click
583             Point p = e.getPoint();
584 
585             JXTable source = (JXTable) e.getSource();
586 
587             int[] selectedRows = source.getSelectedRows();
588             int[] selectedColumns = source.getSelectedColumns();
589 
590             // get the row index at this point
591             int rowIndex = source.rowAtPoint(p);
592 
593             // get the column index at this point
594             int columnIndex = source.columnAtPoint(p);
595 
596             if (LOG.isDebugEnabled()) {
597                 LOG.debug("At point [" + p + "] found Row " + rowIndex + ", Column " + columnIndex);
598             }
599 
600             boolean canContinue = true;
601 
602             if (source.isEditing()) {
603 
604                 // stop editing
605                 boolean stopEdit = source.getCellEditor().stopCellEditing();
606                 if (!stopEdit) {
607                     if (LOG.isWarnEnabled()) {
608                         LOG.warn("Could not stop edit cell...");
609                     }
610                     canContinue = false;
611                 }
612             }
613 
614             if (canContinue) {
615 
616                 // select row (could empty selection)
617                 if (rowIndex == -1) {
618                     source.clearSelection();
619                 } else if (!ArrayUtils.contains(selectedRows, rowIndex)) {
620                     source.setRowSelectionInterval(rowIndex, rowIndex);
621                 }
622 
623                 // select column (could empty selection)
624                 if (columnIndex == -1) {
625                     source.clearSelection();
626                 } else if (!ArrayUtils.contains(selectedColumns, columnIndex)) {
627                     source.setColumnSelectionInterval(columnIndex, columnIndex);
628                 }
629 
630                 if (rightClick) {
631 
632                     // use now model coordinate
633                     int modelRowIndex = rowIndex == -1 ? -1 : source.convertRowIndexToModel(rowIndex);
634                     int modelColumnIndex = columnIndex == -1 ? -1 : source.convertColumnIndexToModel(columnIndex);
635 
636                     beforeOpenPopup(modelRowIndex, modelColumnIndex);
637 
638                     // on right click show popup
639                     popup.show(source, e.getX(), e.getY());
640                 }
641             }
642         }
643 
644     }
645 
646     /**
647      * <p>listenModelModify.</p>
648      *
649      * @param model a {@link AbstractBeanUIModel} object.
650      */
651     protected void listenModelModify(AbstractBeanUIModel model) {
652         Preconditions.checkArgument(getModel() != model, "model can't listen to itself");
653 
654         stopListenModelModify(model);
655 
656         PropertyChangeListener listener = new PropertyChangeListener() {
657             @Override
658             public void propertyChange(PropertyChangeEvent evt) {
659                 Boolean modify = (Boolean) evt.getNewValue();
660                 if (modify != null) {
661                     ((AbstractBeanUIModel) getModel()).setModify(modify);
662                 }
663             }
664         };
665 
666         model.addPropertyChangeListener(AbstractBeanUIModel.PROPERTY_MODIFY, listener);
667         modelModifyListenerMap.put(model, listener);
668     }
669 
670     /**
671      * <p>stopListenModelModify.</p>
672      *
673      * @param model a {@link AbstractBeanUIModel} object.
674      */
675     protected void stopListenModelModify(AbstractBeanUIModel model) {
676         PropertyChangeListener listener = modelModifyListenerMap.get(model);
677         if (listener != null) {
678             model.removePropertyChangeListener(AbstractBeanUIModel.PROPERTY_MODIFY, listener);
679             modelModifyListenerMap.remove(model);
680         }
681     }
682 
683     /**
684      * <p>listenModelValid.</p>
685      *
686      * @param model a {@link AbstractBeanUIModel} object.
687      */
688     protected void listenModelValid(AbstractBeanUIModel model) {
689         model.addPropertyChangeListener(AbstractBeanUIModel.PROPERTY_VALID, new PropertyChangeListener() {
690             @Override
691             public void propertyChange(PropertyChangeEvent evt) {
692                 Boolean valid = (Boolean) evt.getNewValue();
693                 if (valid != null) {
694                     ((AbstractBeanUIModel) getModel()).setValid(valid);
695                 }
696             }
697         });
698     }
699 
700     /**
701      * <p>setEnabled.</p>
702      *
703      * @param container a {@link java.awt.Container} object.
704      * @param enabled   a boolean.
705      */
706     public void setEnabled(Container container, boolean enabled) {
707         Component[] components = container.getComponents();
708         for (Component component : components) {
709             component.setEnabled(enabled);
710             if (component instanceof Container) {
711                 setEnabled((Container) component, enabled);
712             }
713         }
714     }
715 
716     /**
717      * {@inheritDoc}
718      */
719     @Override
720     public void setDate(ActionEvent event, String property) {
721         Date value = null;
722         if (event.getSource() instanceof JXDatePicker) {
723             value = ((JXDatePicker) event.getSource()).getDate();
724         } else if (event.getSource() instanceof JDatePanel) {
725             value = ((JDatePanel) event.getSource()).getDate();
726         } else if (event.getSource() instanceof JDatePicker) {
727             value = ((JDatePicker) event.getSource()).getDate();
728         }
729 
730         Date date = (Date) JavaBeanObjectUtil.getProperty(getModel(), property);
731         if (value != null && date != null) {
732             Calendar cal = DateUtils.toCalendar(date);
733             value = DateUtils.setHours(value, cal.get(Calendar.HOUR_OF_DAY));
734             value = DateUtils.setMinutes(value, cal.get(Calendar.MINUTE));
735             value = DateUtils.setSeconds(value, cal.get(Calendar.SECOND));
736         }
737         JavaBeanObjectUtil.setProperty(getModel(), property, value);
738     }
739 
740     // ------------------------------------------------------------------------//
741     // -- Init methods --//
742     // ------------------------------------------------------------------------//
743 
744     /**
745      * {@inheritDoc}
746      */
747     @Override
748     protected void initUIComponent(Object component) {
749         super.initUIComponent(component);
750 
751         if (component instanceof NumberEditor) {
752             initNumberEditor((NumberEditor) component);
753 
754         } else if (component instanceof JDatePicker) {
755             initDatePicker((JDatePicker) component);
756         }
757     }
758 
759     private void initDatePicker(JDatePicker datePicker) {
760 
761         String datePattern = getContext().getDateFormat();
762         datePicker.setDateFormat(datePattern, ApplicationUIUtil.getDefault2DigitYearStart());
763 
764         // colors
765         datePicker.setColor(AbstractComponentColor.Key.FG_MONTH_SELECTOR, Color.black);
766         datePicker.setColor(AbstractComponentColor.Key.BG_MONTH_SELECTOR, UIManager.getColor("background"));
767         datePicker.setColor(AbstractComponentColor.Key.FG_GRID_HEADER, UIManager.getColor("thematicLabelColor"));
768         datePicker.setColor(AbstractComponentColor.Key.BG_GRID_SELECTED, UIManager.getColor("nimbusSelectionBackground"));
769         datePicker.setColor(AbstractComponentColor.Key.BG_GRID_TODAY_SELECTED, UIManager.getColor("nimbusSelectionBackground"));
770 
771         datePicker.setToolTipText(t("jaxx.application.common.datefield.tip", datePattern));
772         datePicker.setTextEditable(true);
773         datePicker.setShowYearButtons(true);
774 
775         if (isAutoSelectOnFocus(datePicker)) {
776             addAutoSelectOnFocus(datePicker.getEditor());
777         }
778 
779     }
780 
781     private void initNumberEditor(NumberEditor numberEditor) {
782 
783         // Init the org.nuiton.jaxx.widgets.number.NumberEditor
784         numberEditor.init();
785 
786         // Fix Mantis #37194 - missing text on 0 button
787         numberEditor.getNumber0().setText(t("numbereditor.0"));
788     }
789 
790     /*
791     private void initPanel(JPanel panel) {
792 
793         // set background color
794         String panelType = (String) panel.getClientProperty("panelType");
795         if (StringUtils.isNotBlank(panelType)) {
796 
797             Color backgroundColor = null;
798             switch (panelType) {
799                 case ApplicationUI.CONTEXT_PANEL_TYPE:
800                     backgroundColor = getConfig().getColorContextPanelBackground();
801                     break;
802                 case ApplicationUI.SELECTION_PANEL_TYPE:
803                     backgroundColor = getConfig().getColorSelectionPanelBackground();
804                     break;
805                 case ApplicationUI.EDITION_PANEL_TYPE:
806                     backgroundColor = getConfig().getColorEditionPanelBackground();
807                     break;
808             }
809 
810             if (backgroundColor != null) {
811                 ApplicationUIUtil.setComponentTreeBackground(panel, backgroundColor);
812             }
813         }
814     }
815 */
816 
817     /**
818      * {@inheritDoc}
819      */
820     @Override
821     protected void initTextField(JTextField jTextField) {
822         super.initTextField(jTextField);
823 
824         String hint = (String) jTextField.getClientProperty("hint");
825         if (hint != null && UIManager.getLookAndFeel() instanceof NimbusLookAndFeel) {
826             jTextField.setUI(new HintSynthTextFieldUI(hint, false));
827         }
828     }
829 
830     /**
831      * {@inheritDoc}
832      */
833     @Override
834     protected void initButton(AbstractButton abstractButton) {
835         super.initButton(abstractButton);
836 
837         // Add ENTER key to input map and affect the same action than SPACE
838         abstractButton.getInputMap().put(KeyStroke.getKeyStroke("pressed ENTER"), "pressed");
839         abstractButton.getInputMap().put(KeyStroke.getKeyStroke("released ENTER"), "released");
840 
841         // configure non blocking action
842         Class actionName = (Class) abstractButton.getClientProperty("applicationWorkerAction");
843         if (actionName != null) {
844             if (abstractButton.getAction() == null) {
845                 Action action = getContext().getActionFactory().createNonBlockingUIAction(this, actionName, abstractButton);
846                 abstractButton.setAction(action);
847             } else {
848                 if (LOG.isErrorEnabled()) {
849                     LOG.error(String.format("button '%s' has already an action. the action '%s' will be ignored", abstractButton.getName(), actionName.getName()));
850                 }
851             }
852         }
853     }
854 
855     /**
856      * {@inheritDoc}
857      */
858     @Override
859     protected <E> void initBeanList(BeanDoubleList<E> list, List<E> data, List<E> selectedData) {
860         initBeanList(list, data, selectedData, -1);
861     }
862 
863     /**
864      * {@inheritDoc}
865      */
866     @Override
867     protected <E> void initBeanList(BeanDoubleList<E> list, List<E> data, List<E> selectedData, Decorator<E> selectedDecorator) {
868         initBeanList(list, data, selectedData, -1);
869     }
870 
871     /**
872      * <p>initBeanList.</p>
873      *
874      * @param list           a {@link jaxx.runtime.swing.editor.bean.BeanDoubleList} object.
875      * @param data           a {@link java.util.List} object.
876      * @param selectedData   a {@link java.util.List} object.
877      * @param preferredWidth a int.
878      * @param <E>            a E object.
879      */
880     @SuppressWarnings("unchecked")
881     protected <E> void initBeanList(final BeanDoubleList<E> list, List<E> data, List<E> selectedData, int preferredWidth) {
882 
883         Preconditions.checkNotNull(list, "No list!");
884 
885         Class<E> beanType = list.getBeanType();
886         Preconditions.checkNotNull(beanType, "No beanType on the double list!");
887 
888         String decoratorContext = null;
889         if (list instanceof ExtendedBeanDoubleList) {
890             decoratorContext = ((ExtendedBeanDoubleList) list).getDecoratorContext();
891         }
892 
893         Decorator<E> decorator = getDecorator(beanType, decoratorContext);
894 
895         if (CollectionUtils.isNotEmpty(data)) {
896             if (decorator != null) {
897                 DecoratorComparator<E> comparator = new DecoratorComparator<E>(decorator);
898                 Collections.sort(data, comparator);
899                 if (selectedData != null) {
900                     Collections.sort(selectedData, comparator);
901                 }
902             }
903         }
904 
905         if (LOG.isDebugEnabled()) {
906             LOG.debug("entity list [" + beanType.getName() + "] : " +
907                     (data == null ? 0 : data.size()));
908         }
909 
910         list.setI18nPrefix(getContext().getI18nPrefix());
911 
912         // TODO ne pas appeler le super, et créer correctement les decorator
913 //        super.initBeanList(list, data, selectedData, null);
914         // add data list to combo box
915         list.init((JXPathDecorator<E>) decorator, null, data, selectedData);
916 
917         if (LOG.isDebugEnabled()) {
918             LOG.debug("Jlist [" + beanType.getName() + "] : " +
919                     list.getUniverseList().getModel().getSize());
920         }
921 
922         if (preferredWidth > -1) {
923             fixBeanListWidth(list, preferredWidth);
924         }
925 
926         // init custom renderer
927         final ColoredFilteredListCellRenderer universeRenderer = new ColoredFilteredListCellRenderer(list.getHandler().getDecorator(),
928                 UIManager.getColor("nimbusSelectionBackground"), UIManager.getColor("nimbusSelectionBackground"));
929         list.getUniverseList().setCellRenderer(universeRenderer);
930         list.getSelectedList().setCellRenderer(newListCellRender(list.getHandler().getDecorator()));
931 
932         // override filter field listener and add custom document listener
933         // because JAXX v2.8.7 don't use the filter text highlighter and don't support list cell renderer override
934         final JTextField filterField = list.getFilterField();
935         AbstractDocument document = (AbstractDocument) list.getFilterField().getDocument();
936         DocumentListener[] dls = document.getDocumentListeners();
937         if (dls != null) {
938             for (DocumentListener dl : dls) {
939                 if (dl.getClass().getName().contains("BeanDoubleListHandler")) {
940                     document.removeDocumentListener(dl);
941                     break;
942                 }
943             }
944         }
945         final JaxxFilterableListModel<E> filterModel = (JaxxFilterableListModel<E>) list.getModel().getUniverseModel();
946         document.addDocumentListener(new DocumentListener() {
947 
948             @Override
949             public void insertUpdate(DocumentEvent e) {
950                 String text = filterField.getText();
951                 universeRenderer.setFilterText(text);
952                 filterModel.setFilterText(text);
953             }
954 
955             @Override
956             public void removeUpdate(DocumentEvent e) {
957                 String text = filterField.getText();
958                 universeRenderer.setFilterText(text);
959                 filterModel.setFilterText(text);
960             }
961 
962             @Override
963             public void changedUpdate(DocumentEvent e) {
964                 String text = filterField.getText();
965                 universeRenderer.setFilterText(text);
966                 filterModel.setFilterText(text);
967             }
968         });
969 
970         filterModel.addListDataListener(new ListDataListener() {
971             @Override
972             public void intervalAdded(ListDataEvent e) {}
973 
974             @Override
975             public void intervalRemoved(ListDataEvent e) {}
976 
977             @Override
978             public void contentsChanged(ListDataEvent e) {
979                 // fix Mantis #37930
980                 boolean empty = e.getIndex0() == 0 && e.getIndex1() == 0;
981                 list.getAddButton().setEnabled(!empty && !list.getUniverseList().isSelectionEmpty());
982                 if (empty) {
983                     list.getUniverseList().clearSelection();
984                 }
985             }
986         });
987 
988         // add mouse listener to auto selected item on right click
989         list.getSelectedList().addMouseListener(new MouseAdapter() {
990 
991             @Override
992             public void mousePressed(MouseEvent e) {
993                 boolean rightClick = SwingUtilities.isRightMouseButton(e);
994                 if (rightClick) {
995                     JList list = (JList) e.getSource();
996                     int index = list.locationToIndex(e.getPoint());
997                     list.setSelectedIndex(index);
998                 }
999             }
1000         });
1001 
1002         // disable focus traversal on JList
1003         list.getUniverseList().setFocusTraversalKeysEnabled(true);
1004         list.getSelectedList().setFocusTraversalKeysEnabled(true);
1005     }
1006 
1007     private <E> void fixBeanListWidth(BeanDoubleList<E> list, int preferredWidth) {
1008 
1009         JScrollPane scrollPane = (JScrollPane) list.get$objectMap().get("$JScrollPane0");
1010         if (scrollPane == null) {
1011             if (LOG.isWarnEnabled()) {
1012                 LOG.warn("the component '$JScrollPane0' has not been found in the BeanDoubleList");
1013             }
1014         } else {
1015             scrollPane.setPreferredSize(new Dimension(preferredWidth, (int) scrollPane.getPreferredSize().getHeight()));
1016         }
1017         scrollPane = (JScrollPane) list.get$objectMap().get("$JScrollPane1");
1018         if (scrollPane == null) {
1019             if (LOG.isWarnEnabled()) {
1020                 LOG.warn("the component '$JScrollPane1' has not been found in the BeanDoubleList");
1021             }
1022         } else {
1023             scrollPane.setPreferredSize(new Dimension(preferredWidth, (int) scrollPane.getPreferredSize().getHeight()));
1024         }
1025 
1026     }
1027 
1028     /**
1029      * {@inheritDoc}
1030      */
1031     @Override
1032     @SuppressWarnings("unchecked")
1033     protected <E> void initBeanFilterableComboBox(
1034             final BeanFilterableComboBox<E> comboBox,
1035             final List<E> data,
1036             final E selectedData,
1037             final String decoratorContext) {
1038         Preconditions.checkNotNull(comboBox, "No comboBox!");
1039 
1040         final Class<E> beanType = comboBox.getBeanType();
1041 
1042         Preconditions.checkNotNull(beanType, "No beanType on the combobox!");
1043 
1044         Decorator<E> decorator = getDecorator(beanType, decoratorContext);
1045 
1046         List<E> dataTemp = data;
1047         if (dataTemp == null) {
1048             dataTemp = Lists.newArrayList();
1049         }
1050         if (LOG.isDebugEnabled()) {
1051             LOG.debug("entity comboBox list [" + beanType.getName() + "] : " + dataTemp.size());
1052         }
1053 
1054         comboBox.setI18nPrefix(getContext().getI18nPrefix());
1055 
1056         // init component and add data list to combo box
1057         comboBox.init((JXPathDecorator<E>) decorator, dataTemp);
1058 
1059         // final decorator
1060         final Decorator<E> finalDecorator = comboBox.getHandler().getDecorator();
1061 
1062         // add extended renderer
1063 //        comboBox.getCombobox().setRenderer(new ExtendedDecoratorListCellRenderer(finalDecorator, getConfig().getColorSelectedRow(), false));
1064         // TODO assume laf is Nimbus
1065         comboBox.getCombobox().setRenderer(new ExtendedDecoratorListCellRenderer(finalDecorator, UIManager.getColor("nimbusSelectionBackground"), false));
1066 
1067         // add listener to add tooltip text on combo
1068         comboBox.addPropertyChangeListener(BeanFilterableComboBox.PROPERTY_SELECTED_ITEM, new PropertyChangeListener() {
1069 
1070             @Override
1071             public void propertyChange(PropertyChangeEvent evt) {
1072                 E newValue = (E) evt.getNewValue();
1073                 String tooltip = beanType.isInstance(newValue) ? finalDecorator.toString(newValue) : null;
1074                 comboBox.getCombobox().setToolTipText(tooltip);
1075             }
1076         });
1077 
1078         comboBox.setSelectedItem(selectedData);
1079 
1080         if (LOG.isDebugEnabled()) {
1081             LOG.debug("combo [" + beanType.getName() + "] : " + comboBox.getData().size());
1082         }
1083     }
1084 
1085 //    @SuppressWarnings("unchecked")
1086 //    protected void setBeanFilterableComboBoxListRenderer(BeanFilterableComboBox beanComboBox, ListCellRenderer renderer) {
1087 //
1088 //        if (beanComboBox.getCombobox().getUI() instanceof WiderSynthComboBoxUI) {
1089 //            ((WiderSynthComboBoxUI)beanComboBox.getCombobox().getUI()).getPopupList().setCellRenderer(renderer);
1090 //        }
1091 //    }
1092 
1093     /**
1094      * <p>initActionComboBox.</p>
1095      *
1096      * @param combo a {@link javax.swing.JComboBox} object.
1097      */
1098     protected void initActionComboBox(final JComboBox<?> combo) {
1099         Preconditions.checkNotNull(combo, "no combobox to init");
1100         Preconditions.checkNotNull(combo.getModel(), "the combobox has no model");
1101         Preconditions.checkArgument(combo.getModel() instanceof ActionComboBoxModel, "the combobox model must be a ActionListModel");
1102 
1103         combo.setEditable(false);
1104         combo.setRenderer(new ActionListCellRenderer(combo));
1105 
1106         // add actions listeners here (remove from jaxx files)
1107         combo.addActionListener(new ActionListener() {
1108 
1109             @Override
1110             public void actionPerformed(ActionEvent e) {
1111                 actionComboBoxAction(e);
1112             }
1113         });
1114         combo.addMouseListener(new MouseAdapter() {
1115 
1116             @Override
1117             public void mouseClicked(MouseEvent e) {
1118                 JComboBox<?> combo = (JComboBox<?>) e.getSource();
1119                 if (combo.isEnabled()) {
1120                     actionComboBoxAction(e);
1121                 }
1122             }
1123 
1124             @Override
1125             public void mouseEntered(MouseEvent e) {
1126                 JComboBox<?> combo = (JComboBox<?>) e.getSource();
1127                 if (combo.isEnabled()) {
1128                     combo.showPopup();
1129                 }
1130             }
1131         });
1132     }
1133 
1134     /**
1135      * {@inheritDoc}
1136      */
1137     @Override
1138     protected void initLabel(JLabel jLabel) {
1139         super.initLabel(jLabel);
1140 
1141         // add validator label property if this label is labelling
1142         if (jLabel.getLabelFor() instanceof JComponent) {
1143             JComponent c = (JComponent) jLabel.getLabelFor();
1144             if (c.getClientProperty("validatorLabel") == null) {
1145                 c.putClientProperty("validatorLabel", jLabel.getText());
1146             }
1147         }
1148     }
1149 
1150     /**
1151      * return a combo box model initialized with at least 1 element
1152      *
1153      * @param item  the first item to render in the combo editor part
1154      * @param items other items to render in the combo list part
1155      * @return a {@link javax.swing.DefaultComboBoxModel} object.
1156      */
1157     public DefaultComboBoxModel newActionComboBoxModel(Object item, Object... items) {
1158         AbstractButton button = (AbstractButton) item;
1159         AbstractButton[] otherButtons = Arrays.copyOf(items, items.length, AbstractButton[].class);
1160         return new ActionComboBoxModel(button, otherButtons);
1161     }
1162 
1163     /**
1164      * <p>actionComboBoxAction.</p>
1165      *
1166      * @param event a {@link java.awt.event.ActionEvent} object.
1167      */
1168     protected void actionComboBoxAction(EventObject event) {
1169         JComboBox<?> existingCombo = (JComboBox<?>) event.getSource();
1170         JButton selectedButton = (JButton) existingCombo.getSelectedItem();
1171         // reset action combo model
1172         if (existingCombo.getModel() instanceof ActionComboBoxModel) {
1173             ((ActionComboBoxModel) existingCombo.getModel()).reset();
1174         } else {
1175             existingCombo.setSelectedIndex(0);
1176         }
1177 
1178         // hide popup before performing the action
1179         existingCombo.hidePopup();
1180         if (selectedButton.isEnabled()) {
1181             if (selectedButton.getAction() != null) {
1182                 getContext().getActionEngine().runAction(selectedButton);
1183             } else {
1184                 selectedButton.doClick();
1185             }
1186         }
1187     }
1188 
1189 //    /**
1190 //     * <p>actionComboBoxAction.</p>
1191 //     *
1192 //     * @param event a {@link java.awt.event.MouseEvent} object.
1193 //     */
1194 //    public void actionComboBoxAction(MouseEvent event) {
1195 //        JComboBox<?> combo = (JComboBox<?>) event.getSource();
1196 //        JButton selectedButton = (JButton) combo.getSelectedItem();
1197 //        if (selectedButton.isEnabled()) {
1198 //            if (selectedButton.getAction() != null) {
1199 //                getContext().getActionEngine().runAction(selectedButton);
1200 //            } else {
1201 //                selectedButton.doClick();
1202 //            }
1203 //        }
1204 //    }
1205 
1206     // ------------------------------------------------------------------------//
1207     // -- Internal methods --//
1208     // ------------------------------------------------------------------------//
1209 
1210     /**
1211      * <p>registerValidators.</p>
1212      *
1213      * @param validators a {@link jaxx.runtime.validator.swing.SwingValidator} object.
1214      */
1215     protected void registerValidators(SwingValidator... validators) {
1216         MainUI main = getContext().getMainUI();
1217         Preconditions.checkNotNull(main, "No mainUI registred in application context");
1218         AbstractMainUIHandler handler = (AbstractMainUIHandler) main.getHandler();
1219         handler.clearValidators();
1220         for (SwingValidator validator : validators) {
1221             handler.registerValidator(validator);
1222         }
1223     }
1224 
1225     /**
1226      * <p>listenValidatorValid.</p>
1227      *
1228      * @param validator a {@link org.nuiton.validator.bean.simple.SimpleBeanValidator} object.
1229      * @param model     a {@link AbstractBeanUIModel} object.
1230      */
1231     protected void listenValidatorValid(SimpleBeanValidator validator, final AbstractBeanUIModel model) {
1232 
1233         stopListenValidatorValid(validator);
1234         Preconditions.checkNotNull(model);
1235 
1236         PropertyChangeListener listener = new PropertyChangeListener() {
1237             @Override
1238             public void propertyChange(PropertyChangeEvent evt) {
1239                 if (LOG.isDebugEnabled()) {
1240                     LOG.debug("method listenValidatorValid " + "Model [" + model
1241                             + "] pass to valid state ["
1242                             + evt.getNewValue() + "]");
1243                 }
1244                 model.setValid((Boolean) evt.getNewValue());
1245             }
1246         };
1247         validator.addPropertyChangeListener(SimpleBeanValidator.VALID_PROPERTY, listener);
1248         validatorValidListenerMap.put(validator, listener);
1249     }
1250 
1251     /**
1252      * <p>stopListenValidatorValid.</p>
1253      *
1254      * @param validator a {@link org.nuiton.validator.bean.simple.SimpleBeanValidator} object.
1255      */
1256     protected void stopListenValidatorValid(SimpleBeanValidator validator) {
1257         PropertyChangeListener listener = validatorValidListenerMap.get(validator);
1258         if (listener != null) {
1259             validator.removePropertyChangeListener(SimpleBeanValidator.VALID_PROPERTY, listener);
1260             validatorValidListenerMap.remove(validator);
1261         }
1262     }
1263 
1264     /**
1265      * <p>listenValidationTableHasNoFatalError.</p>
1266      *
1267      * @param validator a {@link org.nuiton.validator.bean.simple.SimpleBeanValidator} object.
1268      * @param model     a {@link AbstractBeanUIModel} object.
1269      */
1270     protected void listenValidationTableHasNoFatalError(final SimpleBeanValidator validator,
1271                                                         final AbstractBeanUIModel model) {
1272         getContext().getMainUI().getValidatorMessageWidget().addTableModelListener(new TableModelListener() {
1273             @Override
1274             public void tableChanged(TableModelEvent e) {
1275                 boolean valid = !validator.hasFatalErrors();
1276                 if (LOG.isDebugEnabled()) {
1277                     LOG.debug("method listenValidationTableHasNoFatalError " + "Model [" + model
1278                             + "] pass to valid state [" + valid + "]");
1279                 }
1280                 model.setValid(valid);
1281             }
1282         });
1283     }
1284 
1285     /**
1286      * <p>listModelIsModify.</p>
1287      *
1288      * @param model a {@link AbstractBeanUIModel} object.
1289      */
1290     protected void listModelIsModify(AbstractBeanUIModel model) {
1291         model.addPropertyChangeListener(new PropertyChangeListener() {
1292 
1293             final Set<String> excludeProperties = getPropertiesToIgnore();
1294 
1295             @Override
1296             public void propertyChange(PropertyChangeEvent evt) {
1297                 if (!excludeProperties.contains(evt.getPropertyName())) {
1298                     ((AbstractBeanUIModel) evt.getSource()).setModify(true);
1299                 }
1300             }
1301         });
1302     }
1303 
1304     /**
1305      * <p>getPropertiesToIgnore.</p>
1306      *
1307      * @return a {@link java.util.Set} object.
1308      */
1309     protected Set<String> getPropertiesToIgnore() {
1310         return Sets.newHashSet(
1311                 AbstractBeanUIModel.PROPERTY_MODIFY,
1312                 AbstractBeanUIModel.PROPERTY_VALID);
1313     }
1314 
1315     /**
1316      * <p>askBefore.</p>
1317      *
1318      * @param title   a {@link java.lang.String} object.
1319      * @param message a {@link java.lang.String} object.
1320      * @return a boolean.
1321      */
1322     public boolean askBefore(String title, String message) {
1323         return getContext().getDialogHelper().showConfirmDialog(message, title, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1324     }
1325 
1326     /**
1327      * <p>askOverwriteFile.</p>
1328      *
1329      * @param file a {@link java.io.File} object.
1330      * @return a boolean.
1331      */
1332     public boolean askOverwriteFile(File file) {
1333         if (!file.exists()) {
1334             // file does not exist
1335             return true;
1336         }
1337 
1338         // file exists ask user to overwrite
1339         return getContext().getDialogHelper().showConfirmDialog(
1340                 t("quadrige2.action.common.askOverwriteFile.message", file),
1341                 t("quadrige2.action.common.askOverwriteFile.title"), DialogHelper.OVERWRITE_FILE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1342 
1343     }
1344 
1345     /**
1346      * {@inheritDoc}
1347      */
1348     @Override
1349     public int askSaveBeforeLeaving(String message) {
1350         return getContext().getDialogHelper().showConfirmDialog(message, t("quadrige2.action.common.askSaveBeforeLeaving.title"), DialogHelper.SAVE_NO_SAVE_CANCEL_OPTION);
1351     }
1352 
1353     /**
1354      * {@inheritDoc}
1355      */
1356     @Override
1357     public boolean askCancelEditBeforeLeaving(String message) {
1358         return getContext().getDialogHelper().showConfirmDialog(
1359                 message, t("quadrige2.action.common.askCancelEditBeforeLeaving.title"), DialogHelper.NO_SAVE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1360     }
1361 
1362     /**
1363      * <p>askBeforeDelete.</p>
1364      *
1365      * @param title   a {@link java.lang.String} object.
1366      * @param message a {@link java.lang.String} object.
1367      * @return a boolean.
1368      */
1369     public boolean askBeforeDelete(String title, String message) {
1370         return getContext().getDialogHelper().showConfirmDialog(message, title, DialogHelper.DELETE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1371     }
1372 
1373     /**
1374      * <p>askBeforeDeleteMany.</p>
1375      *
1376      * @param title   a {@link java.lang.String} object.
1377      * @param message a {@link java.lang.String} object.
1378      * @param list    a {@link java.util.List} object.
1379      * @return a boolean.
1380      */
1381     public boolean askBeforeDeleteMany(String title, String message, List<String> list) {
1382         return getContext().getDialogHelper().showConfirmDialog(message, ApplicationUIUtil.getHtmlString(list), null, title, DialogHelper.DELETE_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1383     }
1384 
1385     /**
1386      * <p>askBeforeChangeTab.</p>
1387      *
1388      * @param message a {@link java.lang.String} object.
1389      * @return a int.
1390      */
1391     public int askBeforeChangeTab(String message) {
1392         return getContext().getDialogHelper().showConfirmDialog(message, t("quadrige2.action.common.askBeforeChangeTab.title"), DialogHelper.SAVE_NO_SAVE_CANCEL_OPTION);
1393     }
1394 
1395     /**
1396      * <p>askBeforeReset.</p>
1397      *
1398      * @param title   a {@link java.lang.String} object.
1399      * @param message a {@link java.lang.String} object.
1400      * @return a boolean.
1401      */
1402     public boolean askBeforeReset(String title, String message) {
1403         return getContext().getDialogHelper().showConfirmDialog(message, title, DialogHelper.RESET_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1404     }
1405 
1406     /**
1407      * <p>askDeleteInvalidBeforeLeaving.</p>
1408      *
1409      * @param message a {@link java.lang.String} object.
1410      * @return a boolean.
1411      */
1412     public boolean askDeleteInvalidBeforeLeaving(String message) {
1413         return getContext().getDialogHelper().showConfirmDialog(
1414                 message, t("quadrige2.action.common.askDeleteInvalidBeforeLeaving.title"), DialogHelper.DELETE_INVALID_CANCEL_OPTION) == JOptionPane.OK_OPTION;
1415     }
1416 
1417     /**
1418      * {@inheritDoc}
1419      */
1420     @Override
1421     protected void initScrollPane(JScrollPane scrollPane) {
1422         // don't call super.initScrollPane(scrollPane); see below
1423 
1424         Boolean onlyVerticalScrollable = (Boolean) scrollPane.getClientProperty("onlyVerticalScrollable");
1425         if (onlyVerticalScrollable != null && onlyVerticalScrollable) {
1426             scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1427             scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
1428 
1429             // Encapsulate the viewport view in a JXPanel to avoid constant redraw when component has a size > view port size (see mantis #31981)
1430             Component component = scrollPane.getViewport().getView();
1431             JXPanel innerPanel = new JXPanel(new BorderLayout());
1432             innerPanel.add(component);
1433             scrollPane.setViewportView(innerPanel);
1434             innerPanel.setScrollableTracksViewportHeight(false);
1435             innerPanel.setScrollableTracksViewportWidth(true);
1436         }
1437 
1438         // Init ScrollPane speed on non-table component
1439         if (scrollPane.getViewport().getComponentCount() > 0) {
1440             boolean isTable = false;
1441             for (Component component : scrollPane.getViewport().getComponents()) {
1442                 if (component instanceof JTable) {
1443                     isTable = true;
1444                     break;
1445                 }
1446             }
1447             if (!isTable) {
1448                 scrollPane.getVerticalScrollBar().setUnitIncrement(20);
1449             }
1450         }
1451     }
1452 
1453     /*
1454      * DIALOG HANDLING
1455      */
1456 
1457     /**
1458      * <p>openDialog.</p>
1459      *
1460      * @param dialog a {@link javax.swing.JDialog} object.
1461      */
1462     public void openDialog(JDialog dialog) {
1463         openDialog(dialog, null);
1464     }
1465 
1466     /**
1467      * <p>openDialog.</p>
1468      *
1469      * @param dialog a {@link javax.swing.JDialog} object.
1470      * @param size   a {@link java.awt.Dimension} object.
1471      */
1472     public void openDialog(JDialog dialog, Dimension size) {
1473         openDialog(dialog, size, false);
1474     }
1475 
1476     /**
1477      * <p>openDialogForceOnTop.</p>
1478      *
1479      * @param dialog a {@link javax.swing.JDialog} object.
1480      */
1481     public void openDialogForceOnTop(JDialog dialog) {
1482         openDialogForceOnTop(dialog, null);
1483     }
1484 
1485     /**
1486      * <p>openDialogForceOnTop.</p>
1487      *
1488      * @param dialog a {@link javax.swing.JDialog} object.
1489      * @param size   a {@link java.awt.Dimension} object.
1490      */
1491     public void openDialogForceOnTop(JDialog dialog, Dimension size) {
1492         openDialog(dialog, size, true);
1493     }
1494 
1495     private void openDialog(JDialog dialog, Dimension size, boolean forceOnTop) {
1496 
1497         // keep actual ActionUI
1498         ActionUI oldApplicationActionUI = getContext().getActionUI();
1499 
1500         try {
1501             // Create a new ActionUI depending on this JDialog
1502             final ActionUI dialogActionUI = new ActionUI(dialog, true) {
1503                 @Override
1504                 protected ApplicationActionUIHandler createHandler() {
1505                     // the context must be set before ActionUI initialization
1506                     ApplicationUIUtil.setApplicationContext(this, getContext());
1507                     return super.createHandler();
1508                 }
1509             };
1510             getContext().setActionUI(dialogActionUI);
1511 
1512             // Init dialog behavior
1513             initDialog(dialog, null);
1514 
1515             if (CollectionUtils.isEmpty(dialog.getIconImages())) {
1516                 dialog.setIconImage(getContext().getMainUI().getIconImage());
1517             }
1518 
1519             if (size != null) {
1520                 dialog.setSize(size);
1521             } else {
1522                 dialog.pack();
1523             }
1524 
1525             // add to SwingSession
1526 //            getContext().getSwingSession().add(dialog, true);
1527 
1528             SwingUtil.center(getContext().getMainUI(), dialog);
1529             // Set modality type depending on forceOnTop property
1530             dialog.setModalityType(forceOnTop
1531                     ? Dialog.ModalityType.TOOLKIT_MODAL
1532                     : Dialog.ModalityType.APPLICATION_MODAL);
1533             dialog.setVisible(true);
1534 
1535         } finally {
1536 
1537             // restore previous ActionUI
1538             getContext().setActionUI(oldApplicationActionUI);
1539         }
1540 
1541     }
1542 
1543     @Override
1544     public void openDialog(org.nuiton.jaxx.application.swing.ApplicationUI dialogContent, String title, Dimension dim) {
1545 
1546         Component topestUI = getTopestUI();
1547 
1548         JDialog dialog;
1549         if (topestUI instanceof Frame) {
1550             dialog = new JDialog((Frame) topestUI, title, true);
1551         } else {
1552             dialog = new JDialog((Dialog) topestUI, title, true);
1553         }
1554 
1555         // Init dialog behavior
1556         initDialog(dialog, dialogContent.getHandler());
1557 
1558         dialog.setIconImage(getContext().getMainUI().getIconImage());
1559         Component dialogComponent = (Component) dialogContent;
1560         dialogComponent.setVisible(true);
1561         dialog.add(dialogComponent);
1562         dialog.setResizable(true);
1563         dialog.setName("dialog_" + dialogComponent.getName());
1564 
1565         if (dim != null) {
1566             dialog.setSize(dim);
1567         } else {
1568             dialog.pack();
1569         }
1570 
1571         // add to SwingSession
1572 //        getContext().getSwingSession().add(dialog, true);
1573 
1574         SwingUtil.center(getContext().getMainUI(), dialog);
1575         dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
1576         dialog.setVisible(true);
1577     }
1578 
1579     public JFrame createFrame(ApplicationUI dialogContent, String title, Image iconImage, Rectangle dim) {
1580 
1581         JFrame frame = new JFrame();
1582         frame.setTitle(getContext().getMainUI().getTitle() + (StringUtils.isNotEmpty(title) ? " - " + title : ""));
1583         if (iconImage != null) {
1584             frame.setIconImage(iconImage);
1585         } else {
1586             frame.setIconImage(getContext().getMainUI().getIconImage());
1587         }
1588 
1589         Container dialogComponent;
1590         if (dialogContent instanceof Container) {
1591             dialogComponent = (Container) dialogContent;
1592         } else {
1593             dialogComponent = new JPanel(new BorderLayout());
1594             dialogComponent.add((Component) dialogContent);
1595         }
1596 
1597         // Init frame behavior
1598         initDialog(frame, dialogContent.getHandler());
1599         dialogComponent.setVisible(true);
1600         frame.setContentPane(dialogComponent);
1601         frame.setName("frame_" + dialogComponent.getName());
1602 
1603         if (dim != null) {
1604             frame.setBounds(dim);
1605         } else {
1606             frame.pack();
1607         }
1608 
1609         frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
1610         return frame;
1611     }
1612 
1613     public JFrame openFrame(ApplicationUI dialogContent, String title, Image iconImage, Rectangle dim) {
1614 
1615         JFrame frame = createFrame(dialogContent, title, iconImage, dim);
1616         SwingUtilities.invokeLater(() -> frame.setVisible(true));
1617         return frame;
1618     }
1619 
1620     private void initDialog(Window dialog, AbstractApplicationUIHandler dialogHandler) {
1621 
1622         final AbstractApplicationUIHandler handler;
1623         if (dialogHandler == null) {
1624             handler = dialog instanceof ApplicationUI
1625                     ? ((ApplicationUI) dialog).getHandler()
1626                     : null;
1627         } else {
1628             handler = dialogHandler;
1629         }
1630 
1631         if (handler instanceof Cancelable) {
1632 
1633             // add a auto-cancel action
1634             final Action cancelAction = new AbstractAction() {
1635 
1636                 @Override
1637                 public void actionPerformed(ActionEvent e) {
1638                     ((Cancelable) handler).cancel();
1639                 }
1640             };
1641 
1642             JRootPane rootPane = ((RootPaneContainer) dialog).getRootPane();
1643             KeyStroke shortcutClosePopup = getContext().getConfiguration().getShortcutClosePopup();
1644             rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcutClosePopup, "close");
1645             rootPane.getActionMap().put("close", cancelAction);
1646 
1647             dialog.addWindowListener(new WindowAdapter() {
1648                 @Override
1649                 public void windowClosing(WindowEvent e) {
1650                     cancelAction.actionPerformed(null);
1651                 }
1652             });
1653         }
1654 
1655         dialog.addWindowListener(new WindowAdapter() {
1656 
1657             @Override
1658             public void windowClosed(WindowEvent e) {
1659                 closeDialog();
1660             }
1661         });
1662 
1663     }
1664 
1665     /**
1666      * {@inheritDoc}
1667      */
1668     @Override
1669     public void closeDialog() {
1670 
1671 //        getContext().saveSwingSession((Component) getUI());
1672 
1673         // Close the dialog only. Note: Don't destroy ui, cause side effects in parent listeners
1674 
1675         if (getUI() instanceof JDialog) {
1676             if (LOG.isDebugEnabled()) {
1677                 LOG.debug("Destroy dialog " + getUI());
1678             }
1679             ((JDialog) getUI()).dispose();
1680         } else {
1681             JDialog parent = getParentContainer(JDialog.class);
1682             if (parent != null) {
1683                 if (LOG.isDebugEnabled()) {
1684                     LOG.debug("Destroy dialog " + parent + " of " + getUI());
1685                 }
1686                 parent.dispose();
1687             }
1688         }
1689     }
1690 
1691     @Override
1692     protected void addHighlighters(JXTable table) {
1693         // Nothing to do here (use an AbstractTableUIHandler)
1694     }
1695 
1696     private class BoldFontHighlighter extends AbstractHighlighter {
1697 
1698         public BoldFontHighlighter(HighlightPredicate predicate) {
1699             super(predicate);
1700         }
1701 
1702         @Override
1703         protected Component doHighlight(Component component, ComponentAdapter adapter) {
1704             if (component.getFont().isItalic()) {
1705                 component.setFont(component.getFont().deriveFont(Font.BOLD + Font.ITALIC));
1706             } else {
1707                 component.setFont(component.getFont().deriveFont(Font.BOLD));
1708             }
1709             return component;
1710         }
1711     }
1712 }