1 package fr.ifremer.tutti.ui.swing.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import com.google.common.base.Preconditions;
26 import com.google.common.collect.Sets;
27 import fr.ifremer.tutti.LabelAware;
28 import fr.ifremer.tutti.TuttiConfiguration;
29 import fr.ifremer.tutti.persistence.entities.data.SampleCategory;
30 import fr.ifremer.tutti.persistence.entities.referential.Species;
31 import fr.ifremer.tutti.service.DecoratorService;
32 import fr.ifremer.tutti.service.PersistenceService;
33 import fr.ifremer.tutti.service.TuttiDataContext;
34 import fr.ifremer.tutti.service.ValidationService;
35 import fr.ifremer.tutti.service.catches.ValidateCruiseOperationsService;
36 import fr.ifremer.tutti.type.WeightUnit;
37 import fr.ifremer.tutti.ui.swing.TuttiUIContext;
38 import fr.ifremer.tutti.ui.swing.content.MainUI;
39 import fr.ifremer.tutti.ui.swing.content.MainUIHandler;
40 import fr.ifremer.tutti.ui.swing.util.actions.ShowComboBoxPopupActions;
41 import fr.ifremer.tutti.ui.swing.util.attachment.ButtonAttachment;
42 import fr.ifremer.tutti.ui.swing.util.computable.ComputableDataEditor;
43 import jaxx.runtime.swing.JAXXWidgetUtil;
44 import jaxx.runtime.swing.editor.cell.NumberCellEditor;
45 import jaxx.runtime.validator.swing.SwingValidator;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48 import org.jdesktop.swingx.JXTable;
49 import org.jdesktop.swingx.JXTitledPanel;
50 import org.jdesktop.swingx.decorator.FontHighlighter;
51 import org.jdesktop.swingx.decorator.HighlightPredicate;
52 import org.jdesktop.swingx.decorator.Highlighter;
53 import org.jdesktop.swingx.table.TableColumnExt;
54 import org.nuiton.decorator.Decorator;
55 import org.nuiton.jaxx.application.swing.AbstractApplicationUIHandler;
56 import org.nuiton.jaxx.application.swing.table.AbstractApplicationTableModel;
57 import org.nuiton.jaxx.application.swing.table.ColumnIdentifier;
58 import org.nuiton.jaxx.application.swing.util.ActionListCellRenderer;
59 import org.nuiton.jaxx.widgets.number.NumberEditor;
60 import org.nuiton.validator.bean.simple.SimpleBeanValidator;
61
62 import javax.swing.AbstractButton;
63 import javax.swing.DefaultComboBoxModel;
64 import javax.swing.JButton;
65 import javax.swing.JComboBox;
66 import javax.swing.JComponent;
67 import javax.swing.JLabel;
68 import javax.swing.JTable;
69 import javax.swing.JTextField;
70 import javax.swing.border.LineBorder;
71 import javax.swing.table.TableCellEditor;
72 import javax.swing.table.TableCellRenderer;
73 import javax.swing.table.TableColumnModel;
74 import java.awt.Color;
75 import java.awt.Component;
76 import java.awt.Font;
77 import java.awt.event.MouseAdapter;
78 import java.awt.event.MouseEvent;
79 import java.beans.PropertyChangeEvent;
80 import java.beans.PropertyChangeListener;
81 import java.util.List;
82 import java.util.Objects;
83 import java.util.Set;
84
85 import static org.nuiton.i18n.I18n.t;
86
87
88
89
90
91
92
93 public abstract class AbstractTuttiUIHandler<M, UI extends TuttiUI<M, ?>> extends AbstractApplicationUIHandler<M, UI> implements UIMessageNotifier {
94
95
96 private static final Log log =
97 LogFactory.getLog(AbstractTuttiUIHandler.class);
98
99 public static final String CAN_EDIT = "_canEdit_";
100
101
102
103
104
105 @Override
106 public void showInformationMessage(String message) {
107 getContext().showInformationMessage(message);
108 }
109
110 @Override
111 public TuttiUIContext getContext() {
112 return (TuttiUIContext) super.getContext();
113 }
114
115 public TuttiDataContext getDataContext() {
116 return getContext().getDataContext();
117 }
118
119 public TuttiConfiguration getConfig() {
120 return getContext().getConfig();
121 }
122
123 public PersistenceService getPersistenceService() {
124 return getContext().getPersistenceService();
125 }
126
127 public ValidationService getValidationService() {
128 return getContext().getValidationService();
129 }
130
131 public ValidateCruiseOperationsService getValidateCruiseOperationsService() {
132 return getContext().getValidateCruiseOperationsService();
133 }
134
135 @Override
136 public Component getTopestUI() {
137 Component result;
138
139 result = getContext().getActionUI();
140
141
142
143 return result;
144 }
145
146 public void clearValidators() {
147 MainUI main = getContext().getMainUI();
148 Preconditions.checkNotNull(
149 main, "No mainUI registred in application context");
150 MainUIHandler handler = main.getHandler();
151 handler.clearValidators();
152 }
153
154 public String getWeightStringValue(JComponent component, Float weight) {
155
156
157 WeightUnit weightUnit = (WeightUnit) component.getClientProperty("addWeightUnit");
158 Objects.requireNonNull(weightUnit, "can't find addWeightUnit client property on component: " + component);
159 return weightUnit.renderWeight(weight);
160 }
161
162
163 @Override
164 public <O> Decorator<O> getDecorator(Class<O> type, String name) {
165 DecoratorService decoratorService =
166 getContext().getDecoratorService();
167
168 Preconditions.checkNotNull(type);
169
170 Decorator decorator = decoratorService.getDecoratorByType(type, name);
171 if (decorator == null) {
172
173 if (LabelAware.class.isAssignableFrom(type)) {
174 decorator = getDecorator(LabelAware.class, null);
175 }
176 }
177 Preconditions.checkNotNull(decorator);
178 return decorator;
179 }
180
181 @Override
182 protected void addHighlighters(final JXTable table) {
183
184 HighlightPredicate notSelectedPredicate = new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED);
185 HighlightPredicate rowIsInvalidPredicate = (renderer, adapter) -> {
186
187 boolean result = false;
188 if (adapter.isEditable()) {
189 AbstractApplicationTableModel model = (AbstractApplicationTableModel) table.getModel();
190 int viewRow = adapter.row;
191 int modelRow = adapter.convertRowIndexToModel(viewRow);
192 AbstractTuttiBeanUIModel row = (AbstractTuttiBeanUIModel) model.getEntry(modelRow);
193 result = !row.isValid();
194 }
195 return result;
196 };
197 HighlightPredicate rowIsValidPredicate =
198 new HighlightPredicate.NotHighlightPredicate(rowIsInvalidPredicate);
199 Highlighter selectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
200 HighlightPredicate.IS_SELECTED,
201 getConfig().getColorSelectedRow());
202 table.addHighlighter(selectedHighlighter);
203
204
205 Highlighter readOnlyHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
206 new HighlightPredicate.AndHighlightPredicate(
207 HighlightPredicate.READ_ONLY,
208 notSelectedPredicate),
209 getConfig().getColorRowReadOnly());
210 table.addHighlighter(readOnlyHighlighter);
211
212
213 Highlighter readOnlySelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
214 new HighlightPredicate.AndHighlightPredicate(
215 HighlightPredicate.READ_ONLY,
216 HighlightPredicate.IS_SELECTED),
217 getConfig().getColorRowReadOnly().darker());
218 table.addHighlighter(readOnlySelectedHighlighter);
219
220
221 Highlighter validHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
222 new HighlightPredicate.AndHighlightPredicate(
223 HighlightPredicate.EDITABLE,
224 notSelectedPredicate,
225 rowIsInvalidPredicate),
226 getConfig().getColorRowInvalid());
227 table.addHighlighter(validHighlighter);
228
229
230 Highlighter validSelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
231 new HighlightPredicate.AndHighlightPredicate(
232 HighlightPredicate.EDITABLE,
233 HighlightPredicate.IS_SELECTED,
234 rowIsInvalidPredicate),
235 getConfig().getColorRowInvalid().darker());
236 table.addHighlighter(validSelectedHighlighter);
237
238
239 Highlighter evenHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
240 new HighlightPredicate.AndHighlightPredicate(
241 HighlightPredicate.ODD,
242 notSelectedPredicate,
243 rowIsValidPredicate,
244 HighlightPredicate.READ_ONLY),
245 getConfig().getColorAlternateRow().darker());
246 table.addHighlighter(evenHighlighter);
247
248 Highlighter evenNotReadOnlyHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
249 new HighlightPredicate.AndHighlightPredicate(
250 HighlightPredicate.ODD,
251 notSelectedPredicate,
252 rowIsValidPredicate,
253 HighlightPredicate.EDITABLE),
254 getConfig().getColorAlternateRow());
255 table.addHighlighter(evenNotReadOnlyHighlighter);
256
257
258 Highlighter evenSelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
259 new HighlightPredicate.AndHighlightPredicate(
260 HighlightPredicate.ODD,
261 HighlightPredicate.IS_SELECTED,
262 rowIsValidPredicate,
263 HighlightPredicate.EDITABLE),
264 getConfig().getColorSelectedRow());
265 table.addHighlighter(evenSelectedHighlighter);
266
267
268
269 Font font = table.getFont().deriveFont(Font.BOLD);
270 Highlighter selectHighlighter = new FontHighlighter(HighlightPredicate.IS_SELECTED, font);
271 table.addHighlighter(selectHighlighter);
272 }
273
274 protected void listenModelModifiy(AbstractTuttiBeanUIModel model) {
275 model.addPropertyChangeListener(AbstractTuttiBeanUIModel.PROPERTY_MODIFY, evt -> {
276 Boolean modify = (Boolean) evt.getNewValue();
277 if (modify != null && modify) {
278 ((AbstractTuttiBeanUIModel) getModel()).setModify(true);
279 }
280 });
281 }
282
283
284
285
286
287 @Override
288 protected void initUIComponent(Object component) {
289 if (component instanceof NumberEditor) {
290 initNumberEditor((NumberEditor) component);
291 } else if (component instanceof JXTitledPanel) {
292 initJXTitledPanel((JXTitledPanel) component);
293 } else if (component instanceof ButtonAttachment) {
294
295 initButtonAttachment((ButtonAttachment) component);
296 } else if (component instanceof JComboBox) {
297
298 initComboBox((JComboBox) component);
299 } else {
300 super.initUIComponent(component);
301 }
302 }
303
304 private void initComboBox(JComboBox<?> comboBox) {
305
306 super.initUIComponent(comboBox);
307 List<JButton> comboboxActions = (List<JButton>) comboBox.getClientProperty("comboboxActions");
308 if (comboboxActions != null) {
309
310 comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
311 comboBox.addMouseListener(TuttiUIUtil.GRAB_FOCUS_ON_ENTER_LISTENER);
312
313 comboBox.setRenderer(new ActionListCellRenderer());
314 comboBox.setModel(new DefaultComboBoxModel(comboboxActions.toArray()));
315 comboBox.addPropertyChangeListener("enabled", evt -> {
316 JComboBox source = (JComboBox) evt.getSource();
317 source.setFocusable((Boolean) evt.getNewValue());
318 });
319 comboBox.addMouseListener(new MouseAdapter() {
320 @Override
321 public void mouseEntered(MouseEvent e) {
322
323 JComboBox source = (JComboBox) e.getSource();
324
325 if (source.isEnabled()) {
326
327 ShowComboBoxPopupActions task = new ShowComboBoxPopupActions(source);
328
329 try {
330 getContext().getTimer().schedule(task, 300);
331 } catch (IllegalStateException e1) {
332 getContext().reloadTimer().schedule(task, 300);
333 }
334
335 }
336
337 }
338
339 @Override
340 public void mouseClicked(MouseEvent e) {
341
342 JComboBox source = (JComboBox) e.getSource();
343 if (source.isEnabled()) {
344
345 AbstractButton action = (AbstractButton) source.getItemAt(0);
346 getContext().getActionEngine().runAction(action);
347
348 }
349
350 }
351 });
352
353 comboBox.addActionListener(e -> {
354
355 JComboBox source = (JComboBox) e.getSource();
356 Boolean canEdit = (Boolean) source.getClientProperty(CAN_EDIT);
357 if (canEdit == null || canEdit) {
358
359 JButton selectedAction = (JButton) source.getSelectedItem();
360
361
362
363
364 source.setSelectedIndex(0);
365 source.hidePopup();
366 getContext().getActionEngine().runAction(selectedAction);
367
368 }
369 });
370 }
371 }
372
373 public void resetComboBoxAction(JComboBox source) {
374 source.putClientProperty(CAN_EDIT, false);
375
376 try {
377 source.setSelectedIndex(0);
378 } finally {
379 source.putClientProperty(CAN_EDIT, null);
380 }
381 }
382
383 protected void initJXTitledPanel(JXTitledPanel jTextField) {
384
385
386
387
388
389 }
390
391 @Override
392 protected void initTextField(JTextField jTextField) {
393 super.initTextField(jTextField);
394 Boolean computed = (Boolean) jTextField.getClientProperty("computed");
395 if (computed != null && computed) {
396 Font font = jTextField.getFont().deriveFont(Font.ITALIC);
397 jTextField.setFont(font);
398 jTextField.setEditable(false);
399 jTextField.setEnabled(false);
400 jTextField.setDisabledTextColor(getConfig().getColorComputedWeights());
401 }
402 }
403
404 protected void initButtonAttachment(ButtonAttachment component) {
405
406 component.init();
407 }
408
409 @Override
410 protected void initLabel(JLabel jLabel) {
411
412 WeightUnit weightUnit = (WeightUnit) jLabel.getClientProperty("addWeightUnit");
413 if (weightUnit != null) {
414 String text = weightUnit.decorateLabel(jLabel.getText());
415 jLabel.setText(text);
416
417 String tip = weightUnit.decorateTip(jLabel.getToolTipText());
418 jLabel.setToolTipText(tip);
419
420 Component labelFor = jLabel.getLabelFor();
421 if (labelFor instanceof ComputableDataEditor) {
422
423
424 ComputableDataEditor editor = (ComputableDataEditor) labelFor;
425 editor.setWeightUnit(weightUnit);
426 } else if (labelFor instanceof NumberEditor) {
427
428
429 NumberEditor editor = (NumberEditor) labelFor;
430 editor.setNumberPattern(weightUnit.getNumberEditorPattern());
431 }
432 }
433 }
434
435 @Override
436 protected void initButton(AbstractButton abstractButton) {
437
438 super.initButton(abstractButton);
439
440 TuttiUIUtil.initButton(getContext(), this.getUI(), abstractButton);
441
442 }
443
444 protected void initNumberEditor(NumberEditor editor) {
445 if (log.isDebugEnabled()) {
446 log.debug("init number editor " + editor.getName());
447 }
448 editor.init();
449
450
451 Number model = editor.getModel().getNumberValue();
452 if (model != null) {
453 editor.setNumberValue(null);
454 editor.setNumberValue(model);
455 }
456
457 if (isAutoSelectOnFocus(editor)) {
458
459 addAutoSelectOnFocus(editor.getTextField());
460 }
461 }
462
463
464 public String buildReminderLabelTitle(Species species,
465 Iterable<SampleCategory<?>> categories,
466 String prefix,
467 String suffix) {
468 return buildReminderLabelTitle(species, categories, prefix, suffix, true);
469
470 }
471
472 public String buildReminderLabelTitle(Species species,
473 Iterable<SampleCategory<?>> categories,
474 String prefix,
475 String suffix,
476 boolean html) {
477 return buildReminderLabelTitle(
478 decorate(species, DecoratorService.WITH_SURVEY_CODE),
479 categories,
480 prefix,
481 suffix,
482 html);
483
484 }
485
486
487
488
489
490 protected void registerValidators(SwingValidator... validators) {
491 MainUI main = getContext().getMainUI();
492 Preconditions.checkNotNull(
493 main, "No mainUI registred in application context");
494 MainUIHandler handler = main.getHandler();
495 handler.clearValidators();
496 for (SwingValidator validator : validators) {
497 handler.registerValidator(validator);
498 }
499 }
500
501 protected void listenValidatorValid(SimpleBeanValidator validator,
502 final AbstractTuttiBeanUIModel model) {
503 validator.addPropertyChangeListener(SimpleBeanValidator.VALID_PROPERTY, evt -> {
504 if (log.isDebugEnabled()) {
505 log.debug("Model [" + model +
506 "] pass to valid state [" +
507 evt.getNewValue() + "]");
508 }
509 model.setValid((Boolean) evt.getNewValue());
510 });
511 }
512
513 protected void listenValidationTableHasNoFatalError(final SimpleBeanValidator validator,
514 final AbstractTuttiBeanUIModel model) {
515 getContext().getMainUI().getValidatorMessageWidget().addTableModelListener(e -> {
516 boolean valid = !validator.hasFatalErrors();
517 if (log.isDebugEnabled()) {
518 log.debug("Model [" + model +
519 "] pass to valid state [" + valid + "]");
520 }
521 model.setValid(valid);
522 });
523 }
524
525 protected void listModelIsModify(AbstractTuttiBeanUIModel model) {
526 model.addPropertyChangeListener(new PropertyChangeListener() {
527
528 final Set<String> excludeProperties = getPropertiesToIgnore();
529
530 @Override
531 public void propertyChange(PropertyChangeEvent evt) {
532 if (!excludeProperties.contains(evt.getPropertyName())) {
533 ((AbstractTuttiBeanUIModel) evt.getSource()).setModify(true);
534 }
535 }
536 });
537 }
538
539 protected Set<String> getPropertiesToIgnore() {
540 return Sets.newHashSet(
541 AbstractTuttiBeanUIModel.PROPERTY_MODIFY,
542 AbstractTuttiBeanUIModel.PROPERTY_VALID);
543 }
544
545 protected void closeUI(TuttiUI ui) {
546 ui.getHandler().onCloseUI();
547 }
548
549 protected String buildReminderLabelTitle(String species,
550 Iterable<SampleCategory<?>> categories,
551 String prefix,
552 String suffix) {
553 return buildReminderLabelTitle(species, categories, prefix, suffix, true);
554 }
555
556 protected String buildReminderLabelTitle(String species,
557 Iterable<SampleCategory<?>> categories,
558 String prefix,
559 String suffix,
560 boolean html) {
561 StringBuilder title = new StringBuilder();
562 if (html) {
563 title.append("<html><body style='color:black;'>");
564 }
565 title.append(prefix).append(" - [");
566
567 if (html) {
568 title.append("<strong>");
569 }
570 title.append(species);
571
572 if (html) {
573 title.append("</strong>");
574 }
575 title.append("]");
576
577 if (categories != null) {
578 for (SampleCategory<?> sampleCategory : categories) {
579 if (sampleCategory.getCategoryValue() != null) {
580 title.append(" - ");
581 title.append(decorate(sampleCategory.getCategoryValue()));
582 }
583 }
584 }
585
586 title.append(" - ").append(suffix);
587 if (html) {
588 title.append("</body></html>");
589 }
590
591 return title.toString();
592 }
593
594 protected <R> TableColumnExt addFloatColumnToModel(TableColumnModel model,
595 ColumnIdentifier<R> identifier,
596 WeightUnit weightUnit,
597 JTable table) {
598
599 Preconditions.checkNotNull(weightUnit);
600 NumberCellEditor<Float> editor =
601 JAXXWidgetUtil.newNumberTableCellEditor(Float.class, false);
602 editor.getNumberEditor().setSelectAllTextOnError(true);
603 editor.getNumberEditor().getTextField().setBorder(new LineBorder(Color.GRAY, 2));
604 editor.getNumberEditor().setNumberPattern(weightUnit.getNumberEditorPattern());
605
606 TableCellRenderer renderer = newWeightCellRenderer(table.getDefaultRenderer(Number.class), weightUnit);
607
608 return addColumnToModel(model, editor, renderer, identifier, weightUnit);
609 }
610
611 protected <R> TableColumnExt addColumnToModel(TableColumnModel model,
612 TableCellEditor editor,
613 TableCellRenderer renderer,
614 ColumnIdentifier<R> identifier,
615 WeightUnit weightUnit) {
616
617 TableColumnExt col = new TableColumnExt(model.getColumnCount());
618 col.setCellEditor(editor);
619 col.setCellRenderer(renderer);
620 String label = t(identifier.getHeaderI18nKey());
621 if (weightUnit != null) {
622 label = weightUnit.decorateLabel(label);
623 }
624 col.setHeaderValue(label);
625 String tip = t(identifier.getHeaderTipI18nKey());
626 if (weightUnit != null) {
627 tip = weightUnit.decorateTip(tip);
628 }
629 col.setToolTipText(tip);
630
631 col.setIdentifier(identifier);
632 model.addColumn(col);
633
634 col.setSortable(false);
635 return col;
636 }
637
638 protected TableCellRenderer newWeightCellRenderer( TableCellRenderer delegate, WeightUnit weightUnit) {
639 return (table, value, isSelected, hasFocus, row, column) -> {
640 Component result = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
641 if(result instanceof JLabel) {
642 JLabel jLabel = (JLabel)result;
643 jLabel.setHorizontalTextPosition(4);
644 jLabel.setText(weightUnit.renderWeight((Float) value));
645 }
646
647 return result;
648 };
649 }
650 }