OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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