OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 151 | 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
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 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
 package org.openconcerto.sql.view;
15
 
16
import static javax.swing.JOptionPane.DEFAULT_OPTION;
17
import static javax.swing.JOptionPane.QUESTION_MESSAGE;
142 ilm 18
 
17 ilm 19
import org.openconcerto.openoffice.ContentType;
20
import org.openconcerto.openoffice.OOUtils;
19 ilm 21
import org.openconcerto.openoffice.XMLFormatVersion;
17 ilm 22
import org.openconcerto.sql.Configuration;
73 ilm 23
import org.openconcerto.sql.TM;
17 ilm 24
import org.openconcerto.sql.element.SQLComponent;
25
import org.openconcerto.sql.element.SQLElement;
26
import org.openconcerto.sql.model.SQLRow;
80 ilm 27
import org.openconcerto.sql.model.SQLRowAccessor;
17 ilm 28
import org.openconcerto.sql.sqlobject.SQLRequestComboBox;
29
import org.openconcerto.sql.users.rights.TableAllRights;
30
import org.openconcerto.sql.users.rights.UserRights;
31
import org.openconcerto.sql.users.rights.UserRightsManager;
32
import org.openconcerto.sql.view.list.IListe;
21 ilm 33
import org.openconcerto.sql.view.list.IListeAction;
151 ilm 34
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
17 ilm 35
import org.openconcerto.sql.view.list.ITableModel;
151 ilm 36
import org.openconcerto.sql.view.list.RowAction;
37
import org.openconcerto.sql.view.list.RowAction.PredicateRowAction;
182 ilm 38
import org.openconcerto.sql.view.list.action.SQLRowValuesAction;
17 ilm 39
import org.openconcerto.sql.view.search.SearchListComponent;
40
import org.openconcerto.ui.ContinuousButtonModel;
41
import org.openconcerto.ui.FrameUtil;
42
import org.openconcerto.ui.SwingThreadUtils;
41 ilm 43
import org.openconcerto.ui.component.JRadioButtons.JStringRadioButtons;
17 ilm 44
import org.openconcerto.utils.ExceptionHandler;
151 ilm 45
import org.openconcerto.utils.FileUtils;
142 ilm 46
import org.openconcerto.utils.StringUtils;
17 ilm 47
import org.openconcerto.utils.Tuple2;
93 ilm 48
import org.openconcerto.utils.Tuple2.List2;
19 ilm 49
import org.openconcerto.utils.cc.IClosure;
17 ilm 50
import org.openconcerto.utils.cc.ITransformer;
19 ilm 51
import org.openconcerto.utils.change.ListChangeIndex;
17 ilm 52
 
41 ilm 53
import java.awt.BorderLayout;
17 ilm 54
import java.awt.Container;
55
import java.awt.Dimension;
56
import java.awt.FileDialog;
57
import java.awt.Frame;
58
import java.awt.GridBagConstraints;
59
import java.awt.GridBagLayout;
60
import java.awt.Insets;
61
import java.awt.event.ActionEvent;
62
import java.awt.event.ActionListener;
19 ilm 63
import java.awt.event.HierarchyEvent;
64
import java.awt.event.HierarchyListener;
17 ilm 65
import java.beans.PropertyChangeEvent;
66
import java.beans.PropertyChangeListener;
67
import java.io.File;
68
import java.sql.SQLException;
41 ilm 69
import java.util.Arrays;
17 ilm 70
import java.util.Collections;
71
import java.util.HashMap;
72
import java.util.HashSet;
182 ilm 73
import java.util.IdentityHashMap;
17 ilm 74
import java.util.List;
75
import java.util.Map;
76
import java.util.Set;
21 ilm 77
import java.util.prefs.Preferences;
17 ilm 78
 
151 ilm 79
import javax.swing.AbstractAction;
17 ilm 80
import javax.swing.BorderFactory;
81
import javax.swing.Icon;
82
import javax.swing.ImageIcon;
83
import javax.swing.JButton;
84
import javax.swing.JComponent;
85
import javax.swing.JLabel;
86
import javax.swing.JOptionPane;
87
import javax.swing.JPanel;
88
 
89
/**
90
 * Un panel affichant une liste et des boutons pour la manipuler.
91
 *
92
 * @author ILM Informatique 11 juin 2004
93
 */
94
abstract public class IListPanel extends JPanel implements ActionListener {
95
 
65 ilm 96
    /**
97
     * System property to control the clone button. The value can be either <code>true</code>, in
98
     * which case the button will always appear, or it can be a list of table names.
99
     */
100
    public static final String CAN_CLONE = "org.openconcerto.sql.canCloneInList";
101
 
21 ilm 102
    static private final String EXPORT_DIR_KEY = "exportDir";
103
 
93 ilm 104
    protected static final String FALLBACK_KEY = "FALLBACK_ACTION";
105
 
142 ilm 106
    private static final String FILE_STRUCT_VERSION = "20161018";
107
 
17 ilm 108
    static public final File getConfigFile(final SQLElement elem, final Class<? extends Container> c) {
109
        return getConfigFile(elem, c, null);
110
    }
111
 
112
    static public final File getConfigFile(final SQLElement elem, final Class<? extends Container> c, final String variant) {
142 ilm 113
        final String suffix = StringUtils.isEmpty(variant, true) ? "" : "-" + variant;
182 ilm 114
        return getConfigFile(IListFrame.getConfDir(elem), c, elem.getCode() + suffix);
142 ilm 115
    }
116
 
117
    static public final File getConfigFile(final Class<? extends Container> c, String code) {
182 ilm 118
        return getConfigFile(IListFrame.getConfDir(null), c, code);
119
    }
120
 
121
    static public final File getConfigFile(final File rootDir, final Class<? extends Container> c, String code) {
122
        if (rootDir == null)
73 ilm 123
            return null;
142 ilm 124
 
182 ilm 125
        final File structFile = new File(rootDir, "jtableState-" + FILE_STRUCT_VERSION);
142 ilm 126
        return new File(structFile, c.getSimpleName() + File.separator + IListFrame.getConfigFileName(code));
17 ilm 127
    }
128
 
151 ilm 129
    public static enum FrameMode {
130
        NO_FRAME, READ_ONLY_FRAME, READ_WRITE_FRAME;
131
    }
132
 
133
    public static final String TWO_X_SUFFIX = "_2x";
134
    private static final String FLECHE_HAUT_PNG = "fleche_haut.png";
135
    private static final String FLECHE_BAS_PNG = "fleche_bas.png";
136
    private static final Icon UP_ARROW = getIcon(FLECHE_HAUT_PNG, false);
137
    private static final Icon UP_ARROW_2X = getIcon(FLECHE_HAUT_PNG, true);
138
    private static final Icon DOWN_ARROW = getIcon(FLECHE_BAS_PNG, false);
139
    private static final Icon DOWN_ARROW_2X = getIcon(FLECHE_BAS_PNG, true);
140
 
141
    static final String getResourceName(final String rsrcName, final boolean twoX) {
142
        if (!twoX)
143
            return rsrcName;
144
        final String extension = FileUtils.getExtension(rsrcName, true);
145
        if (extension == null)
146
            return rsrcName + TWO_X_SUFFIX;
147
        else
148
            return FileUtils.removeSuffix(rsrcName, extension) + TWO_X_SUFFIX + extension;
149
    }
150
 
151
    static final Icon getIcon(final String fileName, final boolean twoX) {
152
        return getIcon(IListPanel.class, fileName, twoX);
153
    }
154
 
155
    public static final Icon getIcon(final Class<?> clazz, final String fileName, final boolean twoX) {
156
        return new ImageIcon(clazz.getResource(getResourceName(fileName, twoX)));
157
    }
158
 
17 ilm 159
    private final IListe liste;
160
 
161
    protected final SQLElement element;
162
    protected final BtnTooltipMnger btnMngr;
163
 
164
    protected JButton buttonActualiser;
165
 
166
    // partagé par ListModifyFrame
167
    protected JButton buttonModifier;
168
    protected JButton buttonEffacer;
169
    protected JButton buttonAjouter;
170
    protected JButton buttonClone;
171
    protected JButton saveBtn;
172
    private JButton buttonPlus;
173
    private JButton buttonMoins;
174
    protected final JPanel searchPanel = new JPanel(new GridBagLayout());
182 ilm 175
 
17 ilm 176
    private static final JButton createBtn(Icon i) {
177
        final JButton res = new JButton(i);
178
        res.setMargin(new Insets(1, 1, 1, 1));
179
        res.setModel(new ContinuousButtonModel(300));
80 ilm 180
        res.setBorder(BorderFactory.createEmptyBorder());
17 ilm 181
        res.setOpaque(false);
182
        res.setFocusPainted(true);
183
        res.setContentAreaFilled(false);
184
        return res;
185
    }
186
 
151 ilm 187
    private final RowAction displayRowAction;
17 ilm 188
    protected EditFrame createFrame;
93 ilm 189
    private boolean selectRowOnAdd = true;
151 ilm 190
    // NO_FRAME since at creation the action isn't added to our IListe
191
    private FrameMode showFrameOnDoubleClick = FrameMode.NO_FRAME;
93 ilm 192
    private boolean deaf = Boolean.getBoolean("org.openconcerto.sql.listPanel.deafEditPanel");
17 ilm 193
 
194
    protected SearchListComponent searchComponent;
195
 
196
    public IListPanel(SQLElement elem) {
197
        this(elem, null);
198
    }
199
 
200
    public IListPanel(SQLElement elem, IListe list) {
201
        this(elem, list, null);
202
    }
203
 
204
    /**
205
     * Create a new instance. Often several panels for the same element are needed, in this case
206
     * <code>variant</code> should be used to identify them, this allows each panel to have its own
207
     * {@link IListe#getConfigFile() state}.
208
     *
209
     * @param elem the element that will be displayed.
210
     * @param list the list to use, if <code>null</code> <code>elem</code> will configure a new one.
211
     * @param variant this parameter should identify each panel on the same element, can be
212
     *        <code>null</code>.
213
     */
214
    public IListPanel(SQLElement elem, IListe list, String variant) {
215
        this.element = elem;
216
        this.btnMngr = new BtnTooltipMnger();
217
        // if the same conf is needed for subclasses, use IListPanel.class
218
        final File config = getConfigFile(this.getElement(), this.getClass(), variant);
219
        if (list == null) {
220
            list = new IListe(this.getElement().getTableSource(), config);
221
        } else {
222
            if (list.getSource().getPrimaryTable() != elem.getTable())
223
                throw new IllegalArgumentException("Different tables : " + elem.getTable() + " != " + list.getSource().getPrimaryTable());
224
            if (list.getConfigFile() == null)
225
                list.setConfigFile(config);
226
        }
227
        this.liste = list;
182 ilm 228
        final Map<SQLRowValuesAction, IListeAction> actionsMap = new IdentityHashMap<>();
229
        final IClosure<ListChangeIndex<SQLRowValuesAction>> l = new IClosure<ListChangeIndex<SQLRowValuesAction>>() {
19 ilm 230
            @Override
182 ilm 231
            public void executeChecked(ListChangeIndex<SQLRowValuesAction> input) {
232
                SwingThreadUtils.invoke(() -> {
233
                    for (final SQLRowValuesAction a : input.getItemsRemoved()) {
234
                        final IListeAction la = actionsMap.remove(a);
235
                        if (la != null)
236
                            getListe().removeIListeAction(la);
237
                    }
238
                    actionsMap.putAll(getListe().addRowValuesActions(getElement().getRowValuesActions()));
239
                });
19 ilm 240
            }
241
        };
242
        // remove listener if non displayable since getElement() never dies
243
        this.addHierarchyListener(new HierarchyListener() {
182 ilm 244
            @Override
19 ilm 245
            public void hierarchyChanged(HierarchyEvent e) {
246
                if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0)
247
                    if (isDisplayable()) {
182 ilm 248
                        assert actionsMap.isEmpty();
249
                        actionsMap.putAll(getListe().addRowValuesActions(getElement().getRowValuesActions()));
19 ilm 250
                        getElement().addRowActionsListener(l);
251
                    } else {
252
                        getElement().removeRowActionsListener(l);
182 ilm 253
                        getListe().removeIListeActions(actionsMap.values());
254
                        actionsMap.clear();
19 ilm 255
                    }
256
            }
257
        });
258
 
151 ilm 259
        this.displayRowAction = new PredicateRowAction(new AbstractAction(TM.tr("display")) {
260
 
261
            private EditFrame listeningFrame = null;
262
 
263
            private final EditFrame createFrame(final boolean listening) {
264
                final EditFrame res = new EditFrame(getElement(), IListPanel.this.showFrameOnDoubleClick == FrameMode.READ_ONLY_FRAME ? EditPanel.READONLY : EditPanel.MODIFICATION);
265
                if (listening)
266
                    getListe().addIListener(res);
267
                res.selectionId(getListe().getSelectedId());
268
                return res;
269
            }
270
 
271
            @Override
272
            public void actionPerformed(ActionEvent e) {
273
                final EditFrame frame;
274
                if ((e.getModifiers() & ActionEvent.ALT_MASK) != 0) {
275
                    if (this.listeningFrame == null)
276
                        this.listeningFrame = createFrame(true);
277
                    frame = this.listeningFrame;
278
                } else {
279
                    frame = createFrame(false);
280
                }
281
                FrameUtil.show(frame);
282
            }
283
        }, false).setPredicate(IListeEvent.getSingleSelectionPredicate());
284
 
17 ilm 285
        this.init();
286
    }
287
 
288
    protected final void init() {
289
        this.uiInit();
290
 
291
        // on écoute pour mettre à jour les boutons effacer et modifier
292
        this.liste.addIListener(new IListener() {
293
            @Override
294
            public void selectionId(int id, int field) {
295
                IListPanel.this.listSelectionChanged(id);
296
            }
297
        });
80 ilm 298
        this.liste.addSelectionDataListener(new PropertyChangeListener() {
299
            @Override
300
            public void propertyChange(PropertyChangeEvent evt) {
301
                listSelectionDataChanged();
302
            }
303
        });
304
 
17 ilm 305
        this.liste.addModelListener(new PropertyChangeListener() {
306
            @Override
307
            public void propertyChange(PropertyChangeEvent evt) {
308
                IListPanel.this.searchComponent.reset((ITableModel) evt.getNewValue());
309
            }
310
        });
151 ilm 311
        // don't need to display in a separate frame, if it's already displayed in this panel
312
        final FrameMode m;
313
        if (this.modifyIsImmediate())
314
            m = FrameMode.NO_FRAME;
315
        else if (Boolean.getBoolean("org.openconcerto.sql.listPanel.rwOnDoubleClick"))
316
            m = FrameMode.READ_WRITE_FRAME;
317
        else
318
            m = FrameMode.READ_ONLY_FRAME;
319
        this.setShowFrameOnDoubleClickMode(m);
93 ilm 320
 
17 ilm 321
        // selectID() alone won't init us if NONEXISTANT_ID is already the selected id
322
        this.btnMngr.updateBtns();
323
        this.getListe().selectID(SQLRow.NONEXISTANT_ID);
324
    }
325
 
326
    final private void uiInit() {
327
        this.createUI();
328
 
329
        Container container = this;
330
        container.setLayout(new GridBagLayout());
331
        final GridBagConstraints c = createConstraints();
332
        c.weighty = 1;
333
        container.add(this.liste, c);
334
        c.gridy++;
335
 
336
        c.weighty = 0;
337
        c.insets = new Insets(4, 1, 2, 1);
338
        container.add(this.getMiddlePanel(), c);
339
 
340
        this.addComponents(container, c);
341
        this.setOpaque(false);
342
    }
343
 
344
    /**
345
     * The constraints used to add the list and its panel. The {@link #getListe() list} will be
346
     * added at the coordinates returned, and the {@link #getMiddleCompsLayout() panel} will be
347
     * added underneath (i.e. gridy++).
348
     *
349
     * @return the constraints.
350
     */
351
    protected GridBagConstraints createConstraints() {
352
        final GridBagConstraints c = new GridBagConstraints();
353
        c.weightx = 1;
354
        c.fill = GridBagConstraints.BOTH;
355
        c.gridx = 0;
356
        c.gridy = 0;
357
        return c;
358
    }
359
 
360
    protected void createUI() {
151 ilm 361
        // int sca
362
        final boolean twoX = this.getFont().getSize() > 16;
363
        final int buttonSize = twoX ? 36 : 20;
364
 
17 ilm 365
        this.setOpaque(false);
151 ilm 366
        this.buttonActualiser = new JButton(getIcon("reload.png", twoX));
17 ilm 367
        this.buttonActualiser.setBorderPainted(false);
368
        this.buttonActualiser.setFocusPainted(false);
369
        this.buttonActualiser.setOpaque(false);
370
        this.buttonActualiser.setContentAreaFilled(false);
151 ilm 371
        this.buttonActualiser.setMinimumSize(new Dimension(buttonSize, buttonSize));
372
        this.buttonActualiser.setPreferredSize(new Dimension(buttonSize, buttonSize));
373
        this.buttonActualiser.setMaximumSize(new Dimension(buttonSize, buttonSize));
17 ilm 374
        this.searchComponent = new SearchListComponent(this.liste.getModel());
28 ilm 375
        this.searchComponent.setFormats(this.liste.getSearchFormats());
17 ilm 376
 
73 ilm 377
        this.buttonModifier = new JButton(TM.tr("modify"));
17 ilm 378
        this.buttonModifier.setOpaque(false);
93 ilm 379
        this.btnMngr.addBtn(this.buttonModifier, "noRightToModify", TableAllRights.MODIFY_ROW_TABLE, true, true);
73 ilm 380
        this.buttonEffacer = new JButton(TM.tr("remove"));
17 ilm 381
        this.buttonEffacer.setOpaque(false);
73 ilm 382
        this.btnMngr.addBtn(this.buttonEffacer, "noRightToDel", TableAllRights.DELETE_ROW_TABLE, true);
383
        this.buttonAjouter = new JButton(TM.tr("add"));
17 ilm 384
        this.buttonAjouter.setOpaque(false);
80 ilm 385
        this.btnMngr.addBtn(this.buttonAjouter, "noRightToAdd", TableAllRights.ADD_ROW_TABLE, false, false);
73 ilm 386
        this.buttonClone = new JButton(TM.tr("duplicate"));
17 ilm 387
        this.buttonClone.setOpaque(false);
80 ilm 388
        this.btnMngr.addBtn(this.buttonClone, "noRightToClone", TableAllRights.ADD_ROW_TABLE, true, false);
73 ilm 389
        this.btnMngr.setOKToolTip(this.buttonClone, TM.tr("listPanel.cloneToolTip"));
17 ilm 390
 
151 ilm 391
        this.saveBtn = new JButton(getIcon("save.png", twoX));
17 ilm 392
        this.saveBtn.setFocusPainted(false);
393
        this.saveBtn.setOpaque(false);
394
        this.saveBtn.setContentAreaFilled(false);
395
        this.saveBtn.setBorderPainted(false);
151 ilm 396
        this.saveBtn.setMinimumSize(new Dimension(buttonSize, buttonSize));
397
        this.saveBtn.setPreferredSize(new Dimension(buttonSize, buttonSize));
398
        this.saveBtn.setMaximumSize(new Dimension(buttonSize, buttonSize));
17 ilm 399
 
151 ilm 400
        this.buttonMoins = createBtn(twoX ? UP_ARROW_2X : UP_ARROW);
401
        this.buttonPlus = createBtn(twoX ? DOWN_ARROW_2X : DOWN_ARROW);
402
 
17 ilm 403
        // needSelection = false since we handle it with the transformer
73 ilm 404
        this.btnMngr.addBtn(this.buttonMoins, "noRightToReorder", TableAllRights.MODIFY_ROW_TABLE, false);
405
        this.btnMngr.addBtn(this.buttonPlus, "noRightToReorder", TableAllRights.MODIFY_ROW_TABLE, false);
17 ilm 406
        final ITransformer<JButton, String> transf = new ITransformer<JButton, String>() {
407
            @Override
408
            public String transformChecked(JButton input) {
151 ilm 409
                final boolean ok = getListe().hasSelection() && !getListe().isSorted() && getListe().getSource().getReq().isTableOrder();
17 ilm 410
                // keep them enabled when armed otherwise they will be disabled when used
411
                // since they refresh the list which in turn does a clearSelection()
73 ilm 412
                return input.getModel().isArmed() || ok ? null : TM.tr("listPanel.noSelectionOrSort");
17 ilm 413
            }
414
        };
415
        this.btnMngr.setAdditional(this.buttonMoins, transf);
416
        this.btnMngr.setAdditional(this.buttonPlus, transf);
417
 
418
        this.searchPanel.setOpaque(false);
419
        // ne pas permettre de changer l'ordre quand on trie
420
        this.getListe().addSortListener(new PropertyChangeListener() {
421
            public void propertyChange(PropertyChangeEvent evt) {
422
                updateOrderButtons();
423
            }
424
        });
425
        if (Boolean.getBoolean("org.openconcerto.listpanel.simpleui")) {
426
            this.setAdjustVisible(false);
427
            this.setSearchFullMode(false);
428
        } else {
429
            this.searchPanel.setBorder(BorderFactory.createEtchedBorder());
430
        }
431
        final GridBagConstraints c = new GridBagConstraints();
432
        c.gridx = 0;
433
        c.gridy = 0;
434
        c.insets = new Insets(0, 2, 0, 2);
435
        c.fill = GridBagConstraints.HORIZONTAL;
436
        c.weightx = 0;
437
 
73 ilm 438
        final JLabel label = new JLabel(TM.tr("search"));
17 ilm 439
        label.setOpaque(false);
440
        this.searchPanel.add(label, c);
441
        c.gridx++;
442
 
443
        c.weightx = 1;
444
        this.searchComponent.setOpaque(false);
445
        this.searchPanel.setOpaque(false);
446
        this.searchPanel.add(this.searchComponent, c);
447
 
448
    }
449
 
80 ilm 450
    protected abstract boolean modifyIsImmediate();
451
 
17 ilm 452
    /**
453
     * Permet aux sous classes d'ajouter d'autres composants.
454
     *
455
     * @param container le conteneur dans lequel ajouter.
456
     * @param c les contraintes actuelles.
457
     */
458
    protected void addComponents(Container container, GridBagConstraints c) {
459
        // par défaut ne fait rien
460
    }
461
 
462
    protected Object[] getMiddleCompsLayout() {
41 ilm 463
        final JComponent[] comps = { this.buttonPlus, this.buttonMoins, this.buttonActualiser, canSave() ? this.saveBtn : null, this.searchPanel, this.buttonAjouter,
464
                canClone() ? this.buttonClone : null, this.buttonModifier, this.buttonEffacer };
17 ilm 465
        // le champ de recherche prend toute la largeur disponible
466
        return new Object[] { comps, this.searchPanel };
467
    }
468
 
469
    private boolean canClone() {
65 ilm 470
        final String prop = System.getProperty(CAN_CLONE, "");
17 ilm 471
        return Boolean.parseBoolean(prop) || SQLRow.toList(prop).contains(getElement().getTable().getName());
472
    }
473
 
41 ilm 474
    private boolean canSave() {
475
        // TODO use default right from UserRightsManager (see issue #79)
476
        final String prop = System.getProperty("org.openconcerto.sql.canSaveInList", "true");
477
        return Boolean.parseBoolean(prop) || TableAllRights.currentUserHasRight(TableAllRights.SAVE_ROW_TABLE, getElement().getTable());
478
    }
479
 
17 ilm 480
    final private JPanel getMiddlePanel() {
481
        JPanel container = new JPanel();
482
        container.setLayout(new GridBagLayout());
483
        GridBagConstraints c = new GridBagConstraints();
484
        c.insets = new Insets(0, 2, 0, 2);
485
        c.fill = GridBagConstraints.HORIZONTAL;
486
        c.gridx = 0;
487
        c.gridy = 0;
488
        c.anchor = GridBagConstraints.CENTER;
489
        Object[] middleCompsL = this.getMiddleCompsLayout();
490
        JComponent[] comps = (JComponent[]) middleCompsL[0];
491
        JComponent largest = (JComponent) middleCompsL[1];
492
        for (int i = 0; i < comps.length; i++) {
493
            final JComponent component = comps[i];
494
            if (component != null) {
495
                c.weightx = (component == largest) ? 1 : 0;
496
                container.add(component, c);
497
                if (component instanceof JButton)
498
                    ((JButton) component).addActionListener(this);
499
                c.gridx++;
500
            }
501
        }
502
        container.setOpaque(false);
503
        return container;
504
    }
505
 
506
    /**
507
     * Recherche s dans ce panneau.
508
     *
509
     * @param s la chaine à rechercher.
510
     */
511
    public void search(String s) {
512
        this.searchComponent.setSearchString(s);
513
    }
514
 
515
    /**
516
     * Recherche <code>s</code> dans ce panneau et s'assure que <code>r</code> soit exécuté dans la
517
     * thread Swing une fois la recherche effectuée.
518
     *
519
     * @param s la chaine à rechercher.
520
     * @param r le runnable à exécuter.
521
     */
522
    public synchronized void search(final String s, final Runnable r) {
523
        this.search(s);
524
        // attendre les modifs en cours (update de la base + recherche que l'on vient d'effectuer)
525
        this.getListe().getModel().invokeLater(r);
526
    }
527
 
528
    public final void actionPerformed(ActionEvent e) {
529
        this.handleAction((JButton) e.getSource(), e);
530
    }
531
 
532
    private final int askSerious(Object msg, String title) {
533
        return JOptionPane.showConfirmDialog(this, msg, title, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
534
    }
535
 
536
    private final JPanel createClonePanel(final int selectedLines, final boolean rec, final SQLRequestComboBox combo) {
80 ilm 537
        final String msg = TM.getInstance().trM("listPanel.cloneRows", "rowCount", selectedLines, "rec", rec);
17 ilm 538
 
539
        final JPanel p = new JPanel(new GridBagLayout());
540
        final GridBagConstraints c = new GridBagConstraints();
541
        c.gridx = 0;
542
        c.gridy = 0;
543
        c.fill = GridBagConstraints.HORIZONTAL;
544
        c.anchor = GridBagConstraints.LINE_START;
545
        c.insets = new Insets(2, 2, 2, 2);
546
 
547
        p.add(new JLabel(msg), c);
548
 
549
        if (combo != null) {
550
            c.gridx = 0;
551
            c.gridy++;
73 ilm 552
            p.add(new JLabel(TM.tr("clone.newPlace")), c);
17 ilm 553
 
554
            c.gridy++;
555
            combo.uiInit(getElement().getParentElement().getComboRequest());
556
            combo.setPreferredSize(new Dimension(600, combo.getPreferredSize().height));
557
            p.add(combo, c);
558
        }
559
 
560
        return p;
561
    }
562
 
563
    protected void handleAction(JButton source, ActionEvent evt) {
564
        if (source == this.buttonMoins) {
565
            this.getListe().deplacerDe(-getInc(evt));
566
        } else if (source == this.buttonPlus) {
567
            this.getListe().deplacerDe(getInc(evt));
568
        } else if (source == this.buttonActualiser) {
569
            this.liste.update();
570
        } else if (source == this.buttonAjouter) {
571
            final boolean deaf = isDeaf();
572
            // toujours remplir la createFrame avec la ligne sélectionnée
573
            // car la frame écoute la sélection mais pas les modif, et se reset qd on la ferme
574
            // donc si on clic ajouter, on ferme, on modif la ligne, on clic ajouter
575
            // on doit reremplir l'EditFrame
576
            final int selectedId = this.getListe().getSelectedId();
577
            if (!deaf && this.selectRowOnAdd && selectedId >= 0) {
578
                this.getCreateFrame().selectionId(selectedId);
579
            }
580
            FrameUtil.show(this.getCreateFrame());
581
        } else if (source == this.buttonClone) {
582
            final List<Integer> selectedIDs = this.getListe().getSelection().getSelectedIDs();
583
            final boolean rec = (evt.getModifiers() & ActionEvent.CTRL_MASK) != 0;
584
            // on Ubuntu ALT-Click is used to move windows
585
            final boolean showParent = (evt.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
586
            final SQLRequestComboBox combo = showParent && getElement().getParentElement() != null ? new SQLRequestComboBox() : null;
73 ilm 587
            if (askSerious(createClonePanel(selectedIDs.size(), rec, combo), TM.tr("duplication")) == JOptionPane.YES_OPTION) {
17 ilm 588
                final SQLRow parent = combo == null ? null : combo.getSelectedRow();
589
                for (final int id : selectedIDs) {
590
                    final SQLRow row = this.getElement().getTable().getRow(id);
591
                    try {
592
                        if (rec)
593
                            this.getElement().copyRecursive(row, parent);
594
                        else
595
                            this.getElement().copy(row, parent);
596
                    } catch (SQLException e) {
73 ilm 597
                        ExceptionHandler.handle(this, TM.tr("listPanel.duplicationError", row), e);
17 ilm 598
                    }
599
                }
600
            }
601
        } else if (source == this.buttonEffacer) {
602
            this.getElement().askArchive(this, this.getListe().getSelection().getSelectedIDs());
603
        } else if (source == this.saveBtn) {
604
            try {
73 ilm 605
                final String allRows = TM.tr("listPanel.wholeList");
606
                final String selectedRows = TM.tr("listPanel.selection");
41 ilm 607
                final JStringRadioButtons radios = new JStringRadioButtons(false, Arrays.asList(allRows, selectedRows));
608
                // we rarely mean to save one row or less
609
                radios.setValue(this.getListe().getSelection().getSelectedIDs().size() <= 1 ? allRows : selectedRows);
610
                final JPanel p = new JPanel(new BorderLayout());
73 ilm 611
                p.add(new JLabel(TM.tr("export")), BorderLayout.PAGE_START);
41 ilm 612
                p.add(radios, BorderLayout.LINE_START);
73 ilm 613
                final Object[] options = { TM.tr("open"), TM.tr("save") + "...", TM.tr("cancel") };
614
                final int answer = JOptionPane.showOptionDialog(this, p, TM.tr("listPanel.export"), DEFAULT_OPTION, QUESTION_MESSAGE, null, options, options[0]);
17 ilm 615
                if (answer == 0 || answer == 1) {
41 ilm 616
                    final boolean tmp = answer == 0;
19 ilm 617
                    final XMLFormatVersion version = XMLFormatVersion.getDefault();
41 ilm 618
                    final String prefix = this.element.getPluralName().replace('/', '-');
619
                    final String suffix = "." + ContentType.SPREADSHEET.getVersioned(version.getXMLVersion()).getExtension();
620
                    final File file;
621
                    if (tmp) {
622
                        file = File.createTempFile(prefix, suffix);
623
                    } else {
73 ilm 624
                        final FileDialog fd = new FileDialog(SwingThreadUtils.getAncestorOrSelf(Frame.class, this), TM.tr("listPanel.save"), FileDialog.SAVE);
41 ilm 625
                        final Preferences prefs = Preferences.userNodeForPackage(this.getClass());
626
                        fd.setDirectory(prefs.get(EXPORT_DIR_KEY, Configuration.getInstance().getWD().getAbsolutePath()));
627
                        fd.setFile(prefix + suffix);
628
                        fd.setVisible(true);
629
                        if (fd.getFile() != null) {
630
                            file = new File(fd.getDirectory(), fd.getFile());
631
                            prefs.put(EXPORT_DIR_KEY, fd.getDirectory());
632
                        } else {
633
                            file = null;
17 ilm 634
                        }
635
                    }
41 ilm 636
                    if (file != null) {
637
                        final File exportedFile = this.liste.exporter(file, radios.getValue().equals(selectedRows), version);
638
                        if (tmp) {
639
                            exportedFile.setWritable(false, false);
640
                            exportedFile.deleteOnExit();
641
                        }
642
                        OOUtils.open(exportedFile);
643
                    }
17 ilm 644
                }
645
            } catch (Exception e) {
73 ilm 646
                ExceptionHandler.handle(this, TM.tr("saveError"), e);
17 ilm 647
            }
648
        } else {
649
            throw new IllegalStateException("button '" + source.getText() + "' not implemented");
650
        }
651
    }
652
 
653
    /**
654
     * Whether the create panel listen to the selection of its list.
655
     *
656
     * @return <code>true</code> if the create panel should be empty.
657
     */
93 ilm 658
    public final boolean isDeaf() {
659
        return this.deaf;
17 ilm 660
    }
661
 
93 ilm 662
    public final void setDeaf(final boolean b) {
663
        this.deaf = b;
664
    }
665
 
17 ilm 666
    protected final EditFrame getCreateFrame() {
667
        if (this.createFrame == null) {
668
            this.createFrame = new EditFrame(this.element, EditPanel.CREATION);
132 ilm 669
            this.createFrame.getPanel().setIListe(this.getListe());
17 ilm 670
            if (!isDeaf()) {
671
                // la frame d'ajout se remplit suivant la sélection de cette frame
672
                this.liste.addIListener(this.createFrame);
673
            }
674
        }
675
        return this.createFrame;
676
    }
677
 
678
    // notre liste a changé de sélection
679
    protected void listSelectionChanged(int id) {
80 ilm 680
    }
681
 
682
    // selection or selection content changed
683
    protected void listSelectionDataChanged() {
684
        // even if the same row is selected, its content can change (e.g. get locked)
17 ilm 685
        this.btnMngr.updateBtns();
686
    }
687
 
688
    // handle enabled and tooltip properties of our buttons
689
    protected final class BtnTooltipMnger {
690
 
691
        private final Map<JButton, Tuple2<String, String>> code;
80 ilm 692
        private final Set<JButton> needSelection, needRWSelection;
17 ilm 693
        // btn -> tooltip
694
        private final Map<JButton, ITransformer<JButton, String>> additional;
695
        private final Map<JButton, String> okTooltip;
93 ilm 696
        // if the normal action is denied, offer an alternate one
697
        private final Map<JButton, List2<String>> alternateLabels;
17 ilm 698
 
699
        public BtnTooltipMnger() {
700
            super();
701
            this.needSelection = new HashSet<JButton>();
80 ilm 702
            this.needRWSelection = new HashSet<JButton>();
17 ilm 703
            this.code = new HashMap<JButton, Tuple2<String, String>>();
704
            this.additional = new HashMap<JButton, ITransformer<JButton, String>>();
705
            this.okTooltip = new HashMap<JButton, String>();
93 ilm 706
            this.alternateLabels = new HashMap<JButton, List2<String>>(4);
17 ilm 707
        }
708
 
709
        public void addBtn(final JButton btn, String desc, String rightCode, final boolean needSelection) {
80 ilm 710
            this.addBtn(btn, desc, rightCode, needSelection, true);
711
        }
712
 
713
        public void addBtn(final JButton btn, String desc, String rightCode, final boolean needSelection, final boolean needRWSelection) {
17 ilm 714
            // otherwise have to remove from other attributes
715
            if (this.code.containsKey(btn))
716
                throw new IllegalStateException("already in");
717
            this.code.put(btn, Tuple2.create(desc, rightCode));
718
            if (needSelection)
719
                this.needSelection.add(btn);
80 ilm 720
            if (needRWSelection && getElement().getTable().contains(SQLComponent.READ_ONLY_FIELD))
721
                this.needRWSelection.add(btn);
17 ilm 722
        }
723
 
724
        public void setAdditional(final JButton btn, ITransformer<JButton, String> additional) {
725
            if (additional != null)
726
                this.additional.put(btn, additional);
727
            else
728
                this.additional.remove(btn);
729
        }
730
 
93 ilm 731
        public void setFallback(final JButton btn, final String normalLabel, final String altLabel) {
732
            if (normalLabel == null || altLabel == null)
733
                this.alternateLabels.remove(btn);
734
            else
735
                this.alternateLabels.put(btn, new List2<String>(normalLabel, altLabel));
736
        }
737
 
17 ilm 738
        public void setOKToolTip(final JButton btn, String tooltip) {
739
            if (tooltip != null)
740
                this.okTooltip.put(btn, tooltip);
741
            else
742
                this.okTooltip.remove(btn);
743
        }
744
 
745
        void updateBtn(final JButton btn) {
746
            if (!this.code.containsKey(btn))
747
                throw new IllegalArgumentException();
748
            this.updateBtns(Collections.singleton(btn));
749
        }
750
 
751
        void updateBtns() {
752
            this.updateBtns(this.code.keySet());
753
        }
754
 
755
        private void updateBtns(final Set<JButton> btns) {
756
            final boolean hasSelection = getListe().getSelectedId() >= SQLRow.MIN_VALID_ID;
757
            final UserRights rights = UserRightsManager.getCurrentUserRights();
758
            for (final JButton btn : btns) {
759
                final Tuple2<String, String> t = this.code.get(btn);
760
 
761
                final boolean ok;
762
                final String tooltip;
93 ilm 763
                boolean fallBack = false;
17 ilm 764
                if (!TableAllRights.hasRight(rights, t.get1(), getElement().getTable())) {
765
                    ok = false;
73 ilm 766
                    tooltip = TM.tr(t.get0());
93 ilm 767
                    fallBack = true;
80 ilm 768
                } else if (this.needRWSelection.contains(btn) && isRO()) {
769
                    ok = false;
770
                    tooltip = TM.tr("editPanel.readOnlySelection");
93 ilm 771
                    fallBack = true;
17 ilm 772
                } else if (this.needSelection.contains(btn) && !hasSelection) {
773
                    ok = false;
73 ilm 774
                    tooltip = TM.tr("noSelection");
17 ilm 775
                } else if (this.additional.containsKey(btn)) {
776
                    tooltip = this.additional.get(btn).transformChecked(btn);
777
                    ok = tooltip == null;
778
                } else {
779
                    ok = true;
780
                    tooltip = this.okTooltip.get(btn);
781
                }
93 ilm 782
                boolean finalOK = ok;
783
                final List2<String> fallbackLabels = this.alternateLabels.get(btn);
784
                if (fallbackLabels != null) {
785
                    final String label;
786
                    // the main action is denied (e.g. modify) but an alternate one is allowed (e.g.
787
                    // display)
788
                    if (fallBack) {
789
                        assert !ok;
790
                        finalOK = true;
791
                        label = fallbackLabels.get1();
792
                    } else {
793
                        label = fallbackLabels.get0();
794
                    }
795
                    btn.setText(TM.tr(label));
796
                    btn.putClientProperty(FALLBACK_KEY, fallBack);
797
                }
17 ilm 798
                btn.setToolTipText(tooltip);
93 ilm 799
                btn.setEnabled(finalOK);
17 ilm 800
            }
801
        }
80 ilm 802
 
803
        private boolean isRO() {
182 ilm 804
            final SQLRowAccessor r = getListe().getSelectedRowAccessor();
80 ilm 805
            return r != null && SQLComponent.isReadOnly(r);
806
        }
17 ilm 807
    }
808
 
809
    protected final void updateOrderButtons() {
810
        this.btnMngr.updateBtn(this.buttonMoins);
811
        this.btnMngr.updateBtn(this.buttonPlus);
812
    }
813
 
814
    public final IListe getListe() {
815
        return this.liste;
816
    }
817
 
818
    public final SQLElement getElement() {
819
        return this.element;
820
    }
821
 
822
    /**
823
     * The SQLComponent inside this panel, if any.
824
     *
825
     * @return our child component, or <code>null</code>.
826
     */
827
    protected SQLComponent getSQLComponent() {
828
        return null;
829
    }
830
 
831
    public abstract SQLComponent getModifComp();
832
 
833
    public final SQLComponent getAddComp() {
834
        return this.getCreateFrame().getSQLComponent();
835
    }
836
 
837
    public void grabFocus() {
838
        this.liste.grabFocus();
839
    }
840
 
841
    // compute the increment from the event
842
    private int getInc(ActionEvent evt) {
843
        // move only 1 by 1 for the first 3
844
        final int times = (int) evt.getWhen() - 3;
845
        return times < 2 ? 1 : (int) Math.pow(times, 2);
846
    }
847
 
848
    public void setUpAndDownVisible(boolean b) {
849
        this.buttonPlus.setVisible(b);
850
        this.buttonMoins.setVisible(b);
80 ilm 851
        // also disable move by drag and drop
852
        this.getListe().getJTable().setDragEnabled(b);
17 ilm 853
    }
854
 
151 ilm 855
    @Deprecated
94 ilm 856
    public void setShowReadOnlyFrameOnDoubleClick(boolean showReadOnlyFrameOnDoubleClick) {
151 ilm 857
        this.setShowFrameOnDoubleClickMode(showReadOnlyFrameOnDoubleClick ? FrameMode.READ_ONLY_FRAME : FrameMode.NO_FRAME);
94 ilm 858
    }
859
 
151 ilm 860
    public void setShowFrameOnDoubleClickMode(FrameMode m) {
861
        if (this.showFrameOnDoubleClick != m) {
862
            this.showFrameOnDoubleClick = m;
863
            if (this.showFrameOnDoubleClick != FrameMode.NO_FRAME)
864
                this.liste.setDefaultRowAction(this.displayRowAction);
865
            else
866
                this.liste.removeIListeAction(this.displayRowAction);
867
        }
868
    }
869
 
17 ilm 870
    public void setAddVisible(boolean b) {
871
        this.buttonAjouter.setVisible(b);
872
    }
873
 
874
    public void setAdjustVisible(boolean b) {
875
        this.liste.setAdjustVisible(b);
876
    }
877
 
878
    public void setReloadVisible(boolean b) {
879
        this.buttonActualiser.setVisible(b);
880
    }
881
 
882
    public void setSaveVisible(boolean b) {
883
        this.saveBtn.setVisible(b);
884
    }
885
 
886
    public void setSearchVisible(boolean b) {
887
        this.searchPanel.setVisible(b);
888
    }
889
 
890
    public void setDeleteVisible(boolean b) {
891
        this.buttonEffacer.setVisible(b);
892
    }
893
 
894
    public void setModifyVisible(boolean b) {
895
        this.buttonModifier.setVisible(b);
896
    }
897
 
898
    public void setCloneVisible(boolean b) {
899
        this.buttonClone.setVisible(b);
900
    }
901
 
80 ilm 902
    public void setReadWriteButtonsVisible(final boolean b) {
903
        this.setUpAndDownVisible(b);
904
        this.setAddVisible(b);
905
        this.setDeleteVisible(b);
906
        this.setModifyVisible(b);
907
        this.setCloneVisible(b);
908
    }
909
 
17 ilm 910
    public void setSearchFullMode(boolean b) {
911
        if (b)
912
            this.searchPanel.setBorder(BorderFactory.createEtchedBorder());
913
        else
80 ilm 914
            this.searchPanel.setBorder(BorderFactory.createEmptyBorder());
17 ilm 915
        this.searchComponent.setSearchFullMode(b);
916
    }
917
 
918
    /**
919
     * Récupérer les valeurs de la row sélectionnée lors de l'ajout
920
     *
921
     * @param b
922
     */
923
    public void setSelectRowOnAdd(boolean b) {
924
        this.selectRowOnAdd = b;
925
    }
926
 
83 ilm 927
    public JButton getButtonAdd() {
928
        return buttonAjouter;
929
    }
930
 
931
    public JButton getButtonModify() {
932
        return buttonModifier;
933
    }
934
 
935
    public JButton getButtonDelete() {
936
        return buttonEffacer;
937
    }
938
 
17 ilm 939
}