OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 /*
15
 * Created on 5 nov. 2004
16
 */
17
package org.openconcerto.sql.element;
18
 
19
import org.openconcerto.sql.Configuration;
19 ilm 20
import org.openconcerto.sql.Log;
73 ilm 21
import org.openconcerto.sql.TM;
17 ilm 22
import org.openconcerto.sql.model.SQLField;
81 ilm 23
import org.openconcerto.sql.model.SQLFieldsSet;
17 ilm 24
import org.openconcerto.sql.model.SQLRow;
25
import org.openconcerto.sql.model.SQLRowAccessor;
26
import org.openconcerto.sql.model.SQLRowValues;
132 ilm 27
import org.openconcerto.sql.model.SQLSelect.LockStrength;
17 ilm 28
import org.openconcerto.sql.model.SQLType;
29
import org.openconcerto.sql.request.MutableRowItemView;
57 ilm 30
import org.openconcerto.sql.request.RowItemDesc;
17 ilm 31
import org.openconcerto.sql.request.RowNotFound;
73 ilm 32
import org.openconcerto.sql.request.SQLFieldTranslator;
17 ilm 33
import org.openconcerto.sql.request.SQLForeignRowItemView;
34
import org.openconcerto.sql.request.SQLRowItemView;
35
import org.openconcerto.sql.request.SQLRowView;
36
import org.openconcerto.sql.sqlobject.ElementComboBox;
37
import org.openconcerto.sql.sqlobject.SQLSearchableTextCombo;
38
import org.openconcerto.sql.sqlobject.SQLTextCombo;
39
import org.openconcerto.sql.sqlobject.itemview.SimpleRowItemView;
40
import org.openconcerto.sql.users.rights.UserRightsManager;
41
import org.openconcerto.ui.DisplayabilityListener;
19 ilm 42
import org.openconcerto.ui.FormLayouter;
17 ilm 43
import org.openconcerto.ui.JDate;
44
import org.openconcerto.ui.component.ComboLockedMode;
93 ilm 45
import org.openconcerto.ui.component.InteractionMode;
17 ilm 46
import org.openconcerto.ui.component.text.TextBehaviour;
47
import org.openconcerto.ui.component.text.TextComponentUtils;
48
import org.openconcerto.ui.coreanimation.Animator;
41 ilm 49
import org.openconcerto.ui.valuewrapper.BooleanValueWrapper;
21 ilm 50
import org.openconcerto.ui.valuewrapper.ValidatedValueWrapper;
51
import org.openconcerto.ui.valuewrapper.ValueWrapper;
17 ilm 52
import org.openconcerto.ui.valuewrapper.ValueWrapperFactory;
53
import org.openconcerto.utils.CollectionUtils;
21 ilm 54
import org.openconcerto.utils.DecimalUtils;
17 ilm 55
import org.openconcerto.utils.ExceptionHandler;
57 ilm 56
import org.openconcerto.utils.Tuple2;
21 ilm 57
import org.openconcerto.utils.cc.ITransformer;
58
import org.openconcerto.utils.cc.Transformer;
17 ilm 59
import org.openconcerto.utils.checks.EmptyListener;
60
import org.openconcerto.utils.checks.EmptyObj;
61
import org.openconcerto.utils.checks.ValidListener;
62
import org.openconcerto.utils.checks.ValidObject;
21 ilm 63
import org.openconcerto.utils.checks.ValidState;
17 ilm 64
 
41 ilm 65
import java.awt.BorderLayout;
57 ilm 66
import java.awt.Color;
17 ilm 67
import java.awt.Component;
68
import java.awt.Dimension;
69
import java.awt.Rectangle;
70
import java.awt.event.MouseAdapter;
71
import java.awt.event.MouseEvent;
80 ilm 72
import java.beans.PropertyChangeEvent;
17 ilm 73
import java.beans.PropertyChangeListener;
21 ilm 74
import java.math.BigDecimal;
17 ilm 75
import java.sql.SQLException;
76
import java.util.ArrayList;
80 ilm 77
import java.util.Collection;
17 ilm 78
import java.util.Collections;
79
import java.util.Date;
80 ilm 80
import java.util.HashMap;
17 ilm 81
import java.util.HashSet;
73 ilm 82
import java.util.LinkedHashSet;
17 ilm 83
import java.util.List;
80 ilm 84
import java.util.Map;
19 ilm 85
import java.util.Map.Entry;
17 ilm 86
import java.util.Set;
180 ilm 87
import java.util.function.Supplier;
17 ilm 88
 
89
import javax.swing.JCheckBox;
90
import javax.swing.JComponent;
57 ilm 91
import javax.swing.JLabel;
41 ilm 92
import javax.swing.JPanel;
17 ilm 93
import javax.swing.JViewport;
94
import javax.swing.Scrollable;
95
import javax.swing.SwingConstants;
73 ilm 96
import javax.swing.SwingUtilities;
57 ilm 97
import javax.swing.border.TitledBorder;
17 ilm 98
import javax.swing.text.JTextComponent;
99
 
100
/**
101
 * Base class for SQLComponent. This implements the ValidObject by defining that a component is
102
 * valid when all its objects are valid and all its required objects are not empty : <br/>
103
 * <img src="doc-files/listeners.png"/>. Also this class uses SQLRowItemView to represent items of a
104
 * row.
105
 *
106
 * @author ilm
107
 */
73 ilm 108
public abstract class BaseSQLComponent extends SQLComponent implements Scrollable, RIVPanel {
17 ilm 109
    protected static final String REQ = "required";
110
    protected static final String DEC = "notdecorated";
111
    protected static final String SEP = "noseparator";
112
 
21 ilm 113
    /**
83 ilm 114
     * Syntactic sugar for
115
     * {@link BaseSQLComponent#createSimpleRowItemView(String, Class, ITransformer)}.
21 ilm 116
     *
117
     * @author Sylvain CUAZ
118
     * @param <T> type parameter
119
     */
120
    public static interface VWTransformer<T> extends ITransformer<ValueWrapper<? extends T>, ValueWrapper<? extends T>> {
121
    }
122
 
17 ilm 123
    private final SQLRowView requete;
80 ilm 124
    // whether the application (as opposed to us, the framework) allow the edition
125
    private final Map<String, Boolean> allowEditable;
17 ilm 126
 
127
    private final Set<SQLRowItemView> required;
128
    // contains the SQL name of required SQLRowItemView
129
    private final Set<String> requiredNames;
130
 
131
    // [ValidListener]
132
    private final List<ValidListener> listeners;
133
 
93 ilm 134
    private InteractionMode editable;
17 ilm 135
    private boolean alwaysEditable;
136
    private final Set<SQLField> hide;
19 ilm 137
    private FormLayouter additionalFieldsPanel;
80 ilm 138
    private boolean displayFieldName;
17 ilm 139
 
140
    public BaseSQLComponent(SQLElement element) {
141
        super(element);
142
        // Obligatoire pour L&F Nymbus
143
        this.setOpaque(true);
144
 
145
        // pouvoir prendre le focus
146
        // ATTN marche pas toujours : qd disabled, clic sur ITextArea ne remonte pas jusqu'à nous.
147
        this.addMouseListener(new MouseAdapter() {
148
            public void mousePressed(MouseEvent e) {
149
                BaseSQLComponent.this.requestFocusInWindow();
150
            }
151
        });
152
        this.required = new HashSet<SQLRowItemView>();
153
        this.requiredNames = this.createRequiredNames();
154
        this.listeners = new ArrayList<ValidListener>();
155
        this.hide = new HashSet<SQLField>();
93 ilm 156
        this.editable = InteractionMode.READ_WRITE;
17 ilm 157
        this.setNonExistantEditable(false);
132 ilm 158
        this.requete = new SQLRowView(this.getElement());
80 ilm 159
        // enable or disable our views and forward event
160
        this.requete.addListener(new PropertyChangeListener() {
161
            @Override
162
            public void propertyChange(PropertyChangeEvent evt) {
163
                updateChildrenEditable();
164
                firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
165
            }
166
        }, SQLComponent.READ_ONLY_PROP);
167
        this.allowEditable = new HashMap<String, Boolean>();
168
        this.displayFieldName = false;
17 ilm 169
    }
170
 
171
    private final SQLRowView getRequest() {
172
        return this.requete;
173
    }
174
 
80 ilm 175
    protected final SQLField getField(String field) {
17 ilm 176
        return this.getTable().getField(field);
177
    }
178
 
179
    // *** add*
180
 
181
    public Component addView(String field) {
182
        return this.addView(field, null);
183
    }
184
 
185
    public Component addView(String field, String spec) {
186
        return this.addViewJComponent(field, spec);
187
    }
188
 
19 ilm 189
    private Component addViewJComponent(String field, Object spec) {
17 ilm 190
        if (getElement().getPrivateElement(field) != null) {
191
            // private
27 ilm 192
            final SQLComponent comp = this.getElement().getPrivateElement(field).createDefaultComponent();
17 ilm 193
 
194
            // TODO add a callback so that RowItemView is notified when added to a SQLComponent
19 ilm 195
            // avoiding the 'if instanceof ElementSQLObject' in addInitedView()
196
            final SpecParser parser = SpecParser.create(spec);
17 ilm 197
            DefaultElementSQLObject dobj = new DefaultElementSQLObject(this, comp);
198
            dobj.setDecorated(parser.isDecorated());
199
            dobj.showSeparator(parser.showSeparator());
19 ilm 200
            return this.addView((MutableRowItemView) dobj, field, parser);
21 ilm 201
        } else {
41 ilm 202
            return this.addView(createRowItemView(getComp(field, Object.class)), field, spec);
21 ilm 203
        }
204
    }
205
 
41 ilm 206
    @SuppressWarnings("unchecked")
207
    static private <T, U> ValueWrapper<? extends T> castVW(final ValueWrapper<? extends U> vw, final Class<U> vwType, final Class<T> wantedType) {
208
        if (wantedType.isAssignableFrom(vwType))
209
            return (ValueWrapper<? extends T>) vw;
210
        else
211
            throw new IllegalArgumentException("Value wrapper isn't of the wanted type");
212
    }
213
 
214
    private <T> ValueWrapper<? extends T> getComp(final String field, final Class<T> wantedType) {
21 ilm 215
        if (getElement().getPrivateElement(field) != null)
216
            // we create a MutableRowItemView and need SpecParser
217
            throw new IllegalArgumentException("Private fields not supported");
218
 
41 ilm 219
        final ValueWrapper<? extends T> comp;
21 ilm 220
        final SQLType type = getField(field).getType();
221
        if (getField(field).isKey()) {
17 ilm 222
            // foreign
81 ilm 223
            comp = createSubValueWrapper(new ElementComboBox(), type, wantedType);
17 ilm 224
        } else {
225
            if (Boolean.class.isAssignableFrom(type.getJavaType())) {
226
                // TODO hack to view the focus (should try to paint around the button)
41 ilm 227
                final JCheckBox cb = new JCheckBox(" ");
228
                cb.setOpaque(false);
67 ilm 229
                final JPanel panel = new JPanel(new BorderLayout()) {
230
                    @Override
231
                    public void setEnabled(boolean enabled) {
232
                        super.setEnabled(enabled);
233
                        cb.setEnabled(enabled);
234
                    }
235
                };
41 ilm 236
                panel.add(cb, BorderLayout.LINE_START);
237
                comp = addValidatedValueWrapper(castVW(new BooleanValueWrapper(panel, cb), Boolean.class, wantedType), type);
17 ilm 238
            } else if (Date.class.isAssignableFrom(type.getJavaType())) {
81 ilm 239
                comp = createSubValueWrapper(new JDate(), type, wantedType);
41 ilm 240
            } else if (String.class.isAssignableFrom(type.getJavaType()) && type.getSize() >= 512) {
81 ilm 241
                comp = createSubValueWrapper(new SQLSearchableTextCombo(ComboLockedMode.UNLOCKED, true), type, wantedType);
41 ilm 242
            } else {
17 ilm 243
                // regular
81 ilm 244
                comp = createSubValueWrapper(new SQLTextCombo(), type, wantedType);
41 ilm 245
            }
17 ilm 246
        }
41 ilm 247
        comp.getComp().setOpaque(false);
248
        return comp;
17 ilm 249
    }
250
 
251
    public final void addSQLObject(JComponent obj, String field) {
252
        this.addSQLObject(obj, field, null);
253
    }
254
 
255
    public Component addSQLObject(JComponent obj, String field, Object spec) {
256
        return this.addView(obj, field, spec);
257
    }
258
 
259
    public void addRequiredSQLObject(JComponent obj, String field, Object spec) {
260
        this.addSQLObject(obj, field, REQ + ";" + spec);
261
    }
262
 
263
    public void addRequiredSQLObject(JComponent obj, String field) {
264
        this.addSQLObject(obj, field, REQ);
265
    }
266
 
267
    public Component addView(JComponent comp, String fields) {
268
        return this.addView(comp, fields, null);
269
    }
270
 
271
    public Component addView(JComponent comp, String fields, Object specObj) {
272
        final MutableRowItemView rowItemView;
273
        if (comp instanceof MutableRowItemView) {
274
            rowItemView = (MutableRowItemView) comp;
275
        } else {
276
            final SQLField field = this.getField(SQLRow.toList(fields).get(0));
277
            rowItemView = createRowItemView(comp, field);
278
        }
279
        return this.addView(rowItemView, fields, specObj);
280
    }
281
 
282
    static public SimpleRowItemView<?> createRowItemView(JComponent comp, final SQLField field) {
283
        if (comp == null)
284
            throw new NullPointerException("comp for " + field + " is null");
21 ilm 285
        if (comp instanceof MutableRowItemView)
286
            throw new IllegalStateException("Comp is a MutableRowItemView, creating a SimpleRowItemView would ignore its methods : " + comp);
81 ilm 287
        return createRowItemView(createSubValueWrapper(comp, field.getType(), Object.class));
17 ilm 288
    }
289
 
290
    // just to make javac happy (type parameter for SimpleRowItemView)
21 ilm 291
    static private <T> SimpleRowItemView<T> createRowItemView(ValueWrapper<T> vw) {
41 ilm 292
        if (vw instanceof MutableRowItemView)
293
            throw new IllegalStateException("Comp is a MutableRowItemView, creating a SimpleRowItemView would ignore its methods : " + vw);
21 ilm 294
        return new SimpleRowItemView<T>(vw);
17 ilm 295
    }
296
 
81 ilm 297
    // useful when the exact type of field is known (e.g. String) and setValue() is needed
298
    static public final <T> ValueWrapper<T> createValueWrapper(JComponent comp, final SQLType type, final Class<T> wantedType) {
299
        // equality allow returned ValueWrapper to both produce and consume T
300
        if (!wantedType.equals(type.getJavaType()))
301
            throw new ClassCastException("wanted type " + wantedType + " is not " + type);
302
        final ValueWrapper<T> res = ValueWrapperFactory.create(comp, wantedType);
303
        return addValidatedValueWrapper(res, type);
304
    }
305
 
306
    // useful when the exact type of field isn't known (e.g. Number) and only getValue() is needed
307
    static public final <T> ValueWrapper<? extends T> createSubValueWrapper(JComponent comp, final SQLType type, final Class<T> wantedSuperType) {
21 ilm 308
        final Class<?> fieldClass = type.getJavaType();
81 ilm 309
        final ValueWrapper<? extends T> res = ValueWrapperFactory.create(comp, fieldClass.asSubclass(wantedSuperType));
41 ilm 310
        return addValidatedValueWrapper(res, type);
311
    }
312
 
81 ilm 313
    static private <T> ValueWrapper<T> addValidatedValueWrapper(ValueWrapper<T> res, final SQLType type) {
41 ilm 314
        final Class<?> fieldClass = type.getJavaType();
21 ilm 315
        if (String.class.isAssignableFrom(fieldClass)) {
316
            res = ValidatedValueWrapper.add(res, new ITransformer<T, ValidState>() {
317
                @Override
318
                public ValidState transformChecked(T t) {
319
                    final String s = (String) t;
320
                    final boolean ok = s == null || s.length() <= type.getSize();
321
                    // only compute string if needed
73 ilm 322
                    return ok ? ValidState.getTrueInstance() : ValidState.create(ok, TM.tr("sqlComp.stringValueTooLong", s.length() - type.getSize()));
21 ilm 323
                }
324
            });
325
            // other numeric SQL types are fixed size like their java counterparts
326
        } else if (BigDecimal.class.isAssignableFrom(fieldClass)) {
327
            final Integer decimalDigits = type.getDecimalDigits();
328
            final int intDigits = type.getSize() - decimalDigits;
73 ilm 329
            final String reason = TM.tr("sqlComp.bdTooHigh", intDigits, decimalDigits);
21 ilm 330
            res = ValidatedValueWrapper.add(res, new ITransformer<T, ValidState>() {
331
                @Override
332
                public ValidState transformChecked(T t) {
333
                    final BigDecimal bd = (BigDecimal) t;
334
                    // round first to get the correct number of integer digits, see
335
                    // http://www.postgresql.org/docs/8.4/interactive/datatype-numeric.html
336
                    return ValidState.create(bd == null || DecimalUtils.intDigits(DecimalUtils.round(bd, decimalDigits)) <= intDigits, reason);
337
                }
338
            });
339
        }
340
        return res;
341
    }
342
 
343
    public final <T> SimpleRowItemView<? extends T> createSimpleRowItemView(String fields, Class<T> clazz) {
344
        return this.createSimpleRowItemView(fields, clazz, Transformer.<ValueWrapper<? extends T>> nopTransformer());
345
    }
346
 
347
    /**
348
     * Create and initialize a SimpleRowItemView.
349
     *
350
     * @param <T> type of field.
351
     * @param field field name.
352
     * @param clazz java type for the field.
353
     * @param init to initialize the value wrapper.
354
     * @return the created row item view.
355
     */
356
    public final <T> SimpleRowItemView<? extends T> createSimpleRowItemView(String field, Class<T> clazz, final ITransformer<? super ValueWrapper<? extends T>, ValueWrapper<? extends T>> init) {
41 ilm 357
        final ValueWrapper<? extends T> vw = this.getComp(field, clazz);
358
        if (vw instanceof MutableRowItemView)
359
            throw new IllegalStateException("Comp is a MutableRowItemView, creating a SimpleRowItemView would ignore its methods : " + vw);
21 ilm 360
        return initRIV(createRowItemView(init.transformChecked(vw)), field);
361
    }
362
 
17 ilm 363
    public Component addView(MutableRowItemView rowItemView, String fields, Object specObj) {
21 ilm 364
        return this.addInitedView(initRIV(rowItemView, fields), specObj);
365
    }
366
 
367
    private final <R extends MutableRowItemView> R initRIV(R rowItemView, String fields) {
17 ilm 368
        final List<String> fieldListS = SQLRow.toList(fields);
73 ilm 369
        final Set<SQLField> fieldList = new LinkedHashSet<SQLField>(fieldListS.size());
17 ilm 370
        for (final String fieldName : fieldListS) {
371
            fieldList.add(this.getField(fieldName));
372
        }
373
 
374
        // sqlName
375
        final String sqlName = fields;
376
        rowItemView.init(sqlName, fieldList);
21 ilm 377
        return rowItemView;
17 ilm 378
    }
379
 
380
    public Component addInitedView(SQLRowItemView v, Object specObj) {
381
        // if (obj == null)
382
        // throw new IllegalArgumentException("obj is null");
383
 
19 ilm 384
        final Spec spec = SpecParser.create(specObj);
17 ilm 385
 
386
        // ParentForeignField is always required
73 ilm 387
        final Set<String> reqNames = this.getRequiredNames();
81 ilm 388
        if (spec.isRequired() || v.getFields().contains(getElement().getParentForeignField()) || reqNames == null || reqNames.contains(v.getSQLName())) {
17 ilm 389
            this.required.add(v);
390
            if (v instanceof ElementSQLObject)
391
                ((ElementSQLObject) v).setRequired(true);
73 ilm 392
            if (reqNames != null)
393
                this.requiredNames.add(v.getSQLName());
17 ilm 394
        }
395
        this.getRequest().add(v);
396
 
90 ilm 397
        if (v instanceof SQLComponentItem) {
398
            ((SQLComponentItem) v).added(this, v);
399
        }
144 ilm 400
        // don't call the method twice
401
        if (v.getComp() != v && v.getComp() instanceof SQLComponentItem) {
73 ilm 402
            ((SQLComponentItem) v.getComp()).added(this, v);
403
        }
404
        // reset just before adding to the UI :
405
        // - avoid updating value while displayed
406
        // - but still wait the longest to be sure the RIV is initialized (SQLComponentItem)
407
        v.resetValue();
408
 
81 ilm 409
        if (!CollectionUtils.containsAny(this.hide, v.getFields())) {
19 ilm 410
            if (spec.isAdditional()) {
411
                if (this.additionalFieldsPanel == null)
81 ilm 412
                    Log.get().warning("No additionalFieldsPanel for " + v.getFields() + " : " + v);
63 ilm 413
                else
19 ilm 414
                    this.additionalFieldsPanel.add(getDesc(v), v.getComp());
415
            } else {
416
                this.addToUI(v, spec.getWhere());
417
            }
17 ilm 418
        }
419
 
420
        final JTextComponent textComp = TextComponentUtils.getTextComp(v.getComp());
421
        if (textComp != null)
422
            TextBehaviour.manage(textComp);
423
 
424
        return v.getComp();
425
    }
426
 
427
    private boolean dontEdit(SQLRowItemView v) {
81 ilm 428
        final Set<String> fieldsNames = new SQLFieldsSet(v.getFields()).getFieldsNames(getTable());
132 ilm 429
        return this.getMode() == Mode.READ_ONLY || CollectionUtils.containsAny(this.getElement().getReadOnlyFields(), fieldsNames)
81 ilm 430
                || (this.getMode() != Mode.INSERTION && CollectionUtils.containsAny(this.getElement().getInsertOnlyFields(), fieldsNames));
17 ilm 431
    }
432
 
180 ilm 433
    @Override
17 ilm 434
    protected final void inited() {
19 ilm 435
        super.inited();
93 ilm 436
        if (!(this instanceof GroupSQLComponent)) {
180 ilm 437
            for (final Entry<String, Supplier<? extends JComponent>> e : this.getElement().getAdditionalFields().entrySet()) {
174 ilm 438
                final SpecParser spec;
439
                // FIXME Required à spécifier directement depuis le module
440
                if (this.requiredNames != null && this.requiredNames.contains(e.getKey())) {
441
                    spec = new SpecParser(REQ, true);
442
                } else {
443
                    spec = new SpecParser(null, true);
444
                }
180 ilm 445
                final Supplier<? extends JComponent> comp = e.getValue();
446
                if (comp == null) {
93 ilm 447
                    // infer component
448
                    this.addViewJComponent(e.getKey(), spec);
180 ilm 449
                } else {
450
                    // create component
451
                    this.addView(comp.get(), e.getKey(), spec);
452
                }
93 ilm 453
            }
19 ilm 454
        }
17 ilm 455
        // assure that added views are consistent with our editable status
80 ilm 456
        this.updateChildrenEditable();
17 ilm 457
        for (final SQLRowItemView v : this.getRequest().getViews()) {
458
            v.addEmptyListener(new EmptyListener() {
459
                public void emptyChange(EmptyObj src, boolean newValue) {
460
                    emptyOrValidChanged((SQLRowItemView) src);
461
                }
462
            });
463
            v.addValidListener(new ValidListener() {
21 ilm 464
                public void validChange(ValidObject src, ValidState newValue) {
17 ilm 465
                    emptyOrValidChanged((SQLRowItemView) src);
466
                }
467
            });
468
            // initial status
469
            updateAnimate(v);
470
        }
471
        this.addHierarchyListener(new DisplayabilityListener() {
472
            @Override
473
            protected void displayabilityChanged(Component c) {
474
                getRequest().activate(c.isDisplayable());
475
            }
476
        });
477
        getRequest().activate(this.isDisplayable());
478
        this.fireValidChange();
479
        this.initDone();
480
    }
481
 
482
    protected void initDone() {
483
 
484
    }
485
 
486
    private void updateAnimate(final SQLRowItemView v) {
487
        if (v.getComp() != null)
488
            Animator.getInstance().animate(v.getComp(), !isItemViewValid(v));
489
    }
490
 
491
    protected void emptyOrValidChanged(SQLRowItemView v) {
492
        this.fireValidChange();
493
        updateAnimate(v);
494
    }
495
 
496
    protected void addToUI(SQLRowItemView v, String where) {
497
        // implement it to do nothing since subclass may choose not to use it
498
    }
499
 
19 ilm 500
    protected final void setAdditionalFieldsPanel(FormLayouter panel) {
501
        this.additionalFieldsPanel = panel;
502
    }
503
 
80 ilm 504
    public final void setViewsOrder(final Collection<String> names) {
505
        this.getRequest().setViewsOrder(names);
506
    }
507
 
17 ilm 508
    public final SQLRowItemView getView(String name) {
509
        return this.getRequest().getView(name);
510
    }
511
 
512
    protected final SQLRowItemView getView(Component comp) {
513
        return this.getRequest().getView(comp);
514
    }
515
 
132 ilm 516
    public final Map<String, SQLRowItemView> getViews() {
517
        return this.getRequest().getViewsMap();
518
    }
519
 
90 ilm 520
    /**
521
     * Return each view that has at least one of {@link SQLRowItemView#getFields() its fields} in
522
     * the passed set.
523
     *
524
     * @param fields the fields to search for.
525
     * @return all views that contains <code>fields</code>.
526
     */
527
    public final List<SQLRowItemView> getViews(final Set<SQLField> fields) {
528
        final List<SQLRowItemView> res = new ArrayList<SQLRowItemView>();
529
        for (final SQLRowItemView v : this.getRequest().getViews()) {
530
            if (!Collections.disjoint(fields, v.getFields())) {
531
                res.add(v);
532
            }
533
        }
534
        return res;
535
    }
536
 
17 ilm 537
    protected final SQLForeignRowItemView getForeignView(SQLRowItemView v) {
538
        if (v instanceof SQLForeignRowItemView)
539
            return (SQLForeignRowItemView) v;
540
        else if (v.getComp() instanceof SQLForeignRowItemView)
541
            return (SQLForeignRowItemView) v.getComp();
542
        else
543
            throw new IllegalArgumentException("no SQLForeignRowItemView found for " + v);
544
    }
545
 
546
    public void addValidListener(ValidListener l) {
547
        this.listeners.add(l);
548
    }
549
 
19 ilm 550
    @Override
551
    public void removeValidListener(ValidListener l) {
552
        this.listeners.remove(l);
553
    }
554
 
17 ilm 555
    protected synchronized final void fireValidChange() {
556
        // ATTN called very often during a select() (for each SQLObject empty & value change)
21 ilm 557
        final ValidState validated = this.getValidState();
17 ilm 558
        for (final ValidListener l : this.listeners) {
559
            l.validChange(this, validated);
560
        }
561
    }
562
 
563
    private boolean isItemViewValid(final SQLRowItemView v) {
21 ilm 564
        return v.getValidState().isValid() && !(this.getRequired().contains(v) && v.isEmpty());
17 ilm 565
    }
566
 
21 ilm 567
    @Override
568
    public synchronized ValidState getValidState() {
17 ilm 569
        boolean res = true;
570
        final List<String> pbs = new ArrayList<String>();
571
        // tous nos objets sont valides ?
572
        for (final SQLRowItemView obj : this.getRequest().getViews()) {
21 ilm 573
            final ValidState state = obj.getValidState();
574
            if (!state.isValid()) {
575
                final String txt = state.getValidationText();
73 ilm 576
                final String explanation = TM.tr("sqlComp.invalidItem", "'" + getDesc(obj) + "'", txt != null ? 1 : 0, txt);
17 ilm 577
                pbs.add(explanation);
578
                res = false;
579
                // ne regarder si vide que pour les valides (souvent les non-valides sont vides car
580
                // il ne peuvent renvoyer de valeur)
581
            } else if (this.getRequired().contains(obj) && obj.isEmpty()) {
73 ilm 582
                pbs.add(TM.tr("sqlComp.emptyItem", "'" + getDesc(obj) + "'"));
17 ilm 583
                res = false;
584
            }
585
        }
21 ilm 586
        return ValidState.create(res, CollectionUtils.join(pbs, "\n"));
17 ilm 587
    }
588
 
57 ilm 589
    protected final String getDesc(final SQLRowItemView obj) {
73 ilm 590
        return getLabelFor(obj.getSQLName());
17 ilm 591
    }
592
 
73 ilm 593
    public final String getLabelFor(String itemName) {
594
        return getDesc(itemName, getRIVDesc(itemName)).get0();
595
    }
596
 
597
    // not public since desc could be different from getRIVDesc(itemName)
598
    protected final Tuple2<String, Boolean> getDesc(final String itemName, final RowItemDesc desc) {
57 ilm 599
        final boolean emptyLabel = desc.getLabel() == null || desc.getLabel().trim().length() == 0;
80 ilm 600
        final String l;
601
        if (emptyLabel) {
602
            l = itemName;
603
        } else {
604
            l = getLabel(itemName, desc) + (this.displayFieldName ? " [" + itemName + "]" : "");
605
        }
606
        return Tuple2.create(l, !emptyLabel);
57 ilm 607
    }
608
 
80 ilm 609
    protected final void toggleDisplayFieldsNames() {
610
        this.displayFieldName = !this.displayFieldName;
611
        this.updateUIAll();
612
    }
613
 
73 ilm 614
    protected String getLabel(final String itemName, final RowItemDesc desc) {
615
        return desc.getLabel();
616
    }
617
 
80 ilm 618
    /**
619
     * Allow the views of this component to be edited. For this to be editable other framework
620
     * predicates must be <code>true</code> (e.g. {@link #isSelectionReadOnly()}).
17 ilm 621
     *
93 ilm 622
     * @param mode how the application allow the views to be edited.
132 ilm 623
     * @return <code>true</code> if the mode was changed.
17 ilm 624
     */
80 ilm 625
    @Override
132 ilm 626
    public final boolean setEditable(InteractionMode mode) {
627
        final boolean changed = mode != this.editable;
628
        if (changed) {
93 ilm 629
            this.editable = mode;
132 ilm 630
            this.interactionModeChanged(mode);
17 ilm 631
        }
132 ilm 632
        return changed;
17 ilm 633
    }
634
 
132 ilm 635
    protected void interactionModeChanged(InteractionMode mode) {
636
        this.updateChildrenEditable();
637
    }
638
 
80 ilm 639
    private final void updateChildrenEditable() {
17 ilm 640
        for (final SQLRowItemView o : this.getRequest().getViews()) {
80 ilm 641
            updateEditable(o);
17 ilm 642
        }
643
    }
644
 
93 ilm 645
    public final InteractionMode getEditable() {
17 ilm 646
        return this.editable;
647
    }
648
 
80 ilm 649
    public void allowEditable(final String name, final boolean b) {
650
        final SQLRowItemView view = this.getView(name);
651
        if (view == null)
652
            throw new IllegalArgumentException("No view named " + name);
653
        this.allowEditable(view, b);
654
    }
655
 
656
    /**
657
     * Allow the passed view to be edited. For the view to be editable other framework predicates
93 ilm 658
     * must allow it (e.g. {@link #getEditable()}).
80 ilm 659
     *
660
     * @param view the view.
93 ilm 661
     * @param b <code>true</code> if the application allow the view to be edited.
80 ilm 662
     */
663
    public void allowEditable(final SQLRowItemView view, final boolean b) {
664
        if (view == null)
665
            throw new NullPointerException();
666
        this.allowEditable.put(view.getSQLName(), b);
667
        updateEditable(view);
668
    }
669
 
132 ilm 670
    @Override
671
    protected void modeChanged() {
672
        super.modeChanged();
673
        this.updateChildrenEditable();
674
    }
675
 
80 ilm 676
    private void updateEditable(final SQLRowItemView o) {
132 ilm 677
        // a view can only be editable if its parent is too and if the selected row is
678
        // if nonExistantEditable : always editable, otherwise id must exist
679
        // a view can be disabled for other arbitrary application (non framework) reasons
680
        final InteractionMode mode;
681
        if (this.getEditable() != InteractionMode.READ_WRITE) {
682
            mode = this.getEditable();
683
        } else {
684
            final boolean editable = !this.dontEdit(o) && !this.isSelectionReadOnly() && (this.isNonExistantEditable() || this.getSelectedID() != SQLRow.NONEXISTANT_ID)
685
                    && this.allowEditable.get(o.getSQLName()) != Boolean.FALSE;
686
            mode = editable ? InteractionMode.READ_WRITE : InteractionMode.READ_ONLY;
80 ilm 687
        }
132 ilm 688
        o.setEditable(mode);
80 ilm 689
    }
690
 
17 ilm 691
    // final : overload select() if need be
692
    public final void resetValue() {
693
        this.select(null);
694
    }
695
 
80 ilm 696
    public final void partialReset() {
697
        final Set<String> names = this.getPartialResetNames();
698
        if (names == null || names.size() > 0)
699
            this.select(null, names);
700
    }
701
 
17 ilm 702
    public final int insert() {
703
        return this.insert(null);
704
    }
705
 
706
    public int insert(SQLRow order) {
707
        try {
708
            if (!UserRightsManager.getCurrentUserRights().canAdd(getTable()))
709
                throw new SQLException("forbidden");
710
            if (order == null)
711
                return this.getRequest().insert().getID();
712
            else
713
                return this.getRequest().insert(order).getID();
714
        } catch (SQLException e) {
73 ilm 715
            ExceptionHandler.handle(this, TM.tr("sqlComp.insertError"), e);
17 ilm 716
            return -1;
717
        }
718
    }
719
 
132 ilm 720
    /**
721
     * Return the last known values of the {@link #getSelectedID() selection} in the DB.
722
     *
723
     * @return the last known values.
724
     */
80 ilm 725
    @Override
132 ilm 726
    public final SQLRowValues getLastKnownDBVals() {
727
        return this.getRequest().getLastKnownDBVals();
728
    }
729
 
730
    @Override
731
    public final boolean updateLastKnownDBVals(final LockStrength ls) {
732
        final SQLRowValues current = this.getLastKnownDBVals();
733
        if (current == null)
734
            throw new IllegalStateException("No values to update");
735
        return this.getRequest().setLastKnownDBVals(current, this.getRequest().fetchRow(current.getID(), ls));
736
    }
737
 
738
    @Override
17 ilm 739
    public final void select(int id) {
132 ilm 740
        // TODO in an executor outside of the EDT
741
        this.select(this.getRequest().fetchRow(id, LockStrength.NONE));
17 ilm 742
    }
743
 
80 ilm 744
    @Override
17 ilm 745
    public void select(SQLRowAccessor r) {
80 ilm 746
        this.select(r, null);
747
    }
748
 
749
    public void select(SQLRowAccessor r, Set<String> views) {
17 ilm 750
        try {
751
            // allow null to pass, ease some code (eg new ListOfSomething().getTable() even if we
752
            // can't see the table)
753
            if (r != null && !UserRightsManager.getCurrentUserRights().canView(getTable()))
754
                throw new IllegalStateException("forbidden");
755
            // MAYBE prevent repaints of each SQLObject with
756
            // this.setIgnoreRepaint(true) or RepaintManager
80 ilm 757
            this.getRequest().select(r, views);
758
            this.updateChildrenEditable();
17 ilm 759
 
760
            // don't put defaults if non-editable (eg bottom part of ListeModifyPanel)
761
            if (r == null && this.isNonExistantEditable()) {
762
                // selectDefaults() after select() so that we have the final word
763
                // eg default DESIGNATION of OBS is "ok", but default DESIGNATION of TRANSFO.ID_OBS
764
                // is "good".
80 ilm 765
                this.selectDefaults(views);
17 ilm 766
            }
767
        } catch (RowNotFound e) {
768
            // l'id demandé n'existe pas : prévenir tout le monde
769
            this.getTable().fireRowDeleted(e.getRow().getID());
73 ilm 770
            ExceptionHandler.handle(this, TM.tr("sqlComp.deletedRow", e.getRow()), e);
17 ilm 771
        } catch (IllegalStateException e) {
73 ilm 772
            ExceptionHandler.handle(this, TM.tr("sqlComp.selectError", r), e);
17 ilm 773
        }
774
    }
775
 
776
    // private since it only changes views having default values : use #resetValue()
80 ilm 777
    private final void selectDefaults(Set<String> views) {
132 ilm 778
        final SQLRowValues defaults = this.createValidDefaults();
17 ilm 779
        if (defaults != null && defaults.getFields().size() > 0)
80 ilm 780
            this.getRequest().select(defaults, views);
17 ilm 781
    }
782
 
783
    public final void addFillingListener(final PropertyChangeListener l) {
784
        this.getRequest().addListener(l, "filling");
785
    }
786
 
787
    public final void rmFillingListener(final PropertyChangeListener l) {
788
        this.getRequest().rmListener(l, "filling");
789
    }
790
 
791
    public final void addSelectionListener(final PropertyChangeListener l) {
792
        this.getRequest().addListener(l, "selectedID");
793
    }
794
 
795
    public final void rmSelectionListener(final PropertyChangeListener l) {
796
        this.getRequest().rmListener(l, "selectedID");
797
    }
798
 
799
    @Override
800
    public void detach() {
801
        this.getRequest().detach();
802
    }
803
 
804
    /**
805
     * Are we filling our objects.
806
     *
807
     * @return <code>true</code> if we're filling in values from the DB.
808
     */
809
    public final boolean isFilling() {
810
        return this.getRequest().isFilling();
811
    }
812
 
813
    public int getSelectedID() {
73 ilm 814
        assert (SwingUtilities.isEventDispatchThread());
17 ilm 815
        return this.getRequest().getSelectedID();
816
    }
817
 
80 ilm 818
    @Override
819
    public final boolean isSelectionReadOnly() {
820
        return this.getRequest().isReadOnlySelection();
821
    }
822
 
17 ilm 823
    public void update() {
824
        try {
825
            if (!UserRightsManager.getCurrentUserRights().canModify(getTable()))
826
                throw new SQLException("forbidden");
827
            this.getRequest().update();
828
        } catch (SQLException e) {
73 ilm 829
            ExceptionHandler.handle(this, TM.tr("sqlComp.updateError"), e);
17 ilm 830
        }
831
    }
832
 
132 ilm 833
    @Override
17 ilm 834
    public void archive() {
835
        try {
836
            if (!UserRightsManager.getCurrentUserRights().canDelete(getTable()))
837
                throw new SQLException("forbidden");
80 ilm 838
            if (this.isSelectionReadOnly())
839
                throw new SQLException("read only");
17 ilm 840
            // MAYBE for performance (avoid searching for references to cut):
841
            // if (this.isPrivate()) {
842
            // ((BaseSQLComponent) this.getSQLParent().getSQLParent()).getSelectedID();
843
            // this.getElement().archivePrivate(this.getSelectedID(), parentID);
844
            // } else
845
            this.getElement().archive(this.getSelectedID());
132 ilm 846
            this.resetValue();
17 ilm 847
        } catch (SQLException e) {
73 ilm 848
            ExceptionHandler.handle(this, TM.tr("sqlComp.archiveError", this), e);
17 ilm 849
        }
850
    }
851
 
80 ilm 852
    /**
853
     * The SQL names that are always reset after an insert. This implementation returns an empty set
854
     * : the component is only reset on {@link #getResetMode() visibility change}.
855
     *
856
     * @return the names to reset, <code>null</code> meaning all.
857
     */
858
    public Set<String> getPartialResetNames() {
859
        return Collections.emptySet();
860
    }
861
 
17 ilm 862
    // ** required
863
 
864
    protected final Set<SQLRowItemView> getRequired() {
865
        return this.required;
866
    }
867
 
73 ilm 868
    /**
869
     * The SQL names that are required.
870
     *
871
     * @return the required SQL names, <code>null</code> meaning all of them.
872
     */
17 ilm 873
    protected final Set<String> getRequiredNames() {
73 ilm 874
        return this.requiredNames == null ? null : Collections.unmodifiableSet(this.requiredNames);
17 ilm 875
    }
876
 
877
    /**
878
     * The sql names that are required. This implementation includes "DESIGNATION".
879
     *
880
     * @return the required sql names, <code>null</code> meaning all of them.
881
     */
882
    protected Set<String> createRequiredNames() {
883
        return new HashSet<String>(Collections.singleton("DESIGNATION"));
884
    }
885
 
886
    public String toString() {
887
        return this.getClass() + " on " + this.getTable() + " " + this.getSelectedID();
888
    }
889
 
890
    public final boolean isNonExistantEditable() {
891
        return this.alwaysEditable;
892
    }
893
 
894
    public final void setNonExistantEditable(boolean alwaysEditable) {
895
        this.alwaysEditable = alwaysEditable;
896
    }
897
 
57 ilm 898
    public final RowItemDesc getRIVDesc(String field) {
73 ilm 899
        final Configuration conf = Configuration.getInstance();
900
        if (conf == null)
901
            return SQLFieldTranslator.NULL_DESC;
902
        else
903
            return conf.getTranslator().getDescFor(this.getTable(), getCode(), getElement().getMDPath(), field);
57 ilm 904
    }
905
 
906
    public final void setRIVDesc(String itemName, RowItemDesc desc) {
907
        try {
908
            Configuration.getTranslator(this.getTable()).storeDescFor(this.getTable(), getCode(), itemName, desc);
909
            updateUI(itemName, desc);
910
        } catch (SQLException e) {
73 ilm 911
            ExceptionHandler.handle(this, TM.tr("sqlComp.saveDocError", itemName), e);
57 ilm 912
        }
913
    }
914
 
915
    protected void updateUI(final String itemName, final RowItemDesc desc) {
916
    }
917
 
73 ilm 918
    protected final void updateUI(final String itemName, final JComponent label, final RowItemDesc desc) {
57 ilm 919
        updateUI(itemName, label, desc, null);
920
    }
921
 
73 ilm 922
    protected final void updateUI(final String itemName, final JComponent label, final RowItemDesc desc, final Color emptyLabelColor) {
57 ilm 923
        label.setToolTipText(desc.getDocumentation().trim().length() == 0 ? null : desc.getDocumentation());
924
        final Tuple2<String, Boolean> tuple = getDesc(itemName, desc);
925
        final String s = tuple.get0();
926
        if (label instanceof JLabel) {
927
            ((JLabel) label).setText(s);
928
        } else if (label instanceof JTextComponent) {
929
            ((JTextComponent) label).setText(s);
930
        } else if (label.getBorder() instanceof TitledBorder) {
931
            ((TitledBorder) label.getBorder()).setTitle(s);
932
        } else {
933
            Log.get().warning("Couldn't change label for " + itemName);
934
        }
935
        if (emptyLabelColor != null && !tuple.get1())
936
            label.setForeground(emptyLabelColor);
937
        label.repaint();
938
    }
939
 
80 ilm 940
    protected final void updateUIAll() {
941
        for (final SQLRowItemView v : this.getRequest().getViews()) {
942
            final String sqlName = v.getSQLName();
943
            updateUI(sqlName, getRIVDesc(sqlName));
944
        }
945
    }
946
 
17 ilm 947
    public void doNotShow(SQLField f) {
948
        this.hide.add(f);
949
    }
950
 
951
    private static interface Spec {
952
 
953
        boolean isRequired();
954
 
955
        String getWhere();
19 ilm 956
 
957
        boolean isAdditional();
17 ilm 958
    }
959
 
960
    private static final class SpecParser implements Spec {
961
 
19 ilm 962
        static public SpecParser create(Object specObj) {
963
            final SpecParser spec;
964
            if (specObj == null || specObj instanceof String) {
965
                spec = new SpecParser((String) specObj);
966
            } else {
967
                spec = (SpecParser) specObj;
968
            }
969
            return spec;
970
        }
971
 
17 ilm 972
        private boolean isRequired;
973
        private String where;
974
        private boolean showSeparator = true;
975
        private boolean isDecorated = true;
19 ilm 976
        private final boolean isAdditional;
17 ilm 977
 
978
        public SpecParser(String spec) {
19 ilm 979
            this(spec, false);
980
        }
981
 
982
        public SpecParser(String spec, final boolean isAdditional) {
983
            this.isAdditional = isAdditional;
17 ilm 984
            // empty string treated as null
985
            if (spec == null || spec.length() == 0) {
986
                this.isRequired = false;
987
                this.where = null;
988
            } else {
989
                final String[] specs = spec.split(";");
990
                if (specs.length > 4)
991
                    throw new IllegalArgumentException(spec);
992
                for (int i = 0; i < specs.length; i++) {
993
                    final String sp = specs[i];
994
                    if (sp.equals(REQ)) {
995
                        this.isRequired = true;
996
                    } else if (sp.equals(DEC)) {
997
                        this.isDecorated = false;
998
                    } else if (sp.equals(SEP)) {
999
                        this.showSeparator = false;
1000
                    } else {
1001
                        this.where = sp;
1002
                    }
1003
                }
1004
            }
1005
        }
1006
 
1007
        public boolean showSeparator() {
1008
            return this.showSeparator;
1009
        }
1010
 
1011
        public boolean isDecorated() {
1012
            return this.isDecorated;
1013
        }
1014
 
1015
        @Override
1016
        public final boolean isRequired() {
1017
            return this.isRequired;
1018
        }
1019
 
1020
        @Override
1021
        public final String getWhere() {
1022
            return this.where;
1023
        }
19 ilm 1024
 
1025
        @Override
1026
        public final boolean isAdditional() {
1027
            return this.isAdditional;
1028
        }
17 ilm 1029
    }
1030
 
73 ilm 1031
    @Override
1032
    public SQLElementDirectory getDirectory() {
1033
        return this.getElement().getDirectory();
1034
    }
1035
 
1036
    @Override
1037
    public SQLComponent getSQLComponent() {
1038
        return this;
1039
    }
1040
 
17 ilm 1041
    // *** scrollable
1042
 
1043
    @Override
1044
    public Dimension getPreferredScrollableViewportSize() {
1045
        return this.getPreferredSize();
1046
    }
1047
 
1048
    @Override
1049
    public boolean getScrollableTracksViewportHeight() {
1050
        if (getParent() instanceof JViewport) {
1051
            // since getPreferredScrollableViewportSize() returns getPreferredSize() this component
1052
            // will be shown by default at its preferred size, but if the viewport's height is
1053
            // reduced this component will first shrink to its minimum size, and then below that
1054
            // scrollbars will be displayed.
1055
            return (((JViewport) getParent()).getHeight() >= getMinimumSize().height);
1056
        }
1057
        return false;
1058
    }
1059
 
1060
    @Override
1061
    public boolean getScrollableTracksViewportWidth() {
1062
        // no horizontal scroll, too much pb with JTextArea when setLineWrap(true) : its minimum
1063
        // width grows but never shrinks
1064
        return true;
1065
    }
1066
 
1067
    @Override
1068
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
1069
        // difficult to tell because of the textAreas and the private components
1070
        return 15;
1071
    }
1072
 
1073
    @Override
1074
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
1075
        final double length = orientation == SwingConstants.VERTICAL ? visibleRect.getHeight() : visibleRect.getWidth();
1076
        // keep some common area
1077
        return (int) (length - 30);
1078
    }
1079
 
1080
}