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
80 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
 package org.openconcerto.erp.modules;
15
 
16
import org.openconcerto.erp.modules.ModuleManager.ModuleAction;
156 ilm 17
import org.openconcerto.erp.modules.ModuleManager.ModuleState;
80 ilm 18
import org.openconcerto.erp.modules.ModuleManager.NoChoicePredicate;
19
import org.openconcerto.erp.modules.ModuleTableModel.Columns;
20
import org.openconcerto.erp.modules.ModuleTableModel.ModuleRow;
21
import org.openconcerto.ui.DefaultGridBagConstraints;
151 ilm 22
import org.openconcerto.ui.FontUtils;
80 ilm 23
import org.openconcerto.ui.table.AlternateTableCellRenderer;
24
import org.openconcerto.ui.table.TableCellRendererDecorator.TableCellRendererDecoratorUtils;
25
import org.openconcerto.utils.ExceptionHandler;
26
import org.openconcerto.utils.JImage;
27
 
28
import java.awt.Color;
29
import java.awt.Component;
30
import java.awt.Container;
31
import java.awt.Dimension;
32
import java.awt.GridBagConstraints;
33
import java.awt.GridBagLayout;
34
import java.awt.Insets;
35
import java.awt.event.ActionEvent;
36
import java.awt.event.HierarchyEvent;
37
import java.awt.event.HierarchyListener;
38
import java.util.ArrayList;
39
import java.util.Collection;
40
import java.util.Collections;
41
import java.util.EnumSet;
42
import java.util.HashSet;
43
import java.util.List;
44
import java.util.Set;
45
import java.util.prefs.BackingStoreException;
46
 
47
import javax.swing.AbstractAction;
48
import javax.swing.Action;
49
import javax.swing.JButton;
50
import javax.swing.JComponent;
51
import javax.swing.JDialog;
52
import javax.swing.JOptionPane;
53
import javax.swing.JPanel;
54
import javax.swing.JScrollPane;
55
import javax.swing.JTable;
56
import javax.swing.JViewport;
57
import javax.swing.SwingUtilities;
58
import javax.swing.SwingWorker;
59
import javax.swing.event.TableModelEvent;
60
import javax.swing.event.TableModelListener;
61
import javax.swing.table.TableCellRenderer;
62
import javax.swing.table.TableColumn;
63
import javax.swing.table.TableColumnModel;
64
 
65
public class ModulePanel extends JPanel {
66
 
67
    private final ModuleTableModel tm;
68
 
156 ilm 69
    ModulePanel(final ModuleManager mngr, final boolean onlyInstall) {
80 ilm 70
        this.setOpaque(false);
71
        this.setLayout(new GridBagLayout());
72
        GridBagConstraints c = new DefaultGridBagConstraints();
73
 
156 ilm 74
        this.tm = new ModuleTableModel(mngr);
80 ilm 75
        final JTable t = new JTable(this.tm) {
76
            // only force JTable size above its minimal size (i.e. otherwise use its preferred size
77
            // and display scroll bars accordingly). Without this, some columns can be invisible
78
            // since the JTable always respect columns minimum size.
79
            @Override
80
            public boolean getScrollableTracksViewportWidth() {
81
                // ScrollPaneLayout.layoutContainer() set the size of the view port and then call
82
                // Scrollable methods
83
                final Container viewPort = SwingUtilities.getAncestorOfClass(JViewport.class, this);
84
                return viewPort.getSize().width >= this.getMinimumSize().width;
85
            }
86
        };
81 ilm 87
        // perhaps pass custom sorter to be able to unsort
88
        t.setAutoCreateRowSorter(true);
80 ilm 89
        t.setShowGrid(false);
90
        t.setShowVerticalLines(false);
91
        t.setFocusable(false);
92
        t.setRowSelectionAllowed(false);
93
        t.setColumnSelectionAllowed(false);
94
        t.setCellSelectionEnabled(false);
151 ilm 95
        t.setRowHeight(FontUtils.getPreferredRowHeight(t));
80 ilm 96
        final TableColumnModel columnModel = t.getColumnModel();
97
        final TableCellRenderer headerDefaultRenderer = t.getTableHeader().getDefaultRenderer();
98
        final EnumSet<Columns> booleanCols = EnumSet.of(ModuleTableModel.Columns.LOCAL, ModuleTableModel.Columns.REMOTE, ModuleTableModel.Columns.DB_REQUIRED, ModuleTableModel.Columns.ADMIN_REQUIRED);
99
        final JImage trueComp = new JImage(TableCellRendererDecoratorUtils.class.getResource("okay.png"));
100
        trueComp.setBackground(Color.WHITE);
101
        trueComp.setCenterImage(true);
102
        final JComponent falseComp = new JPanel();
103
        falseComp.setBackground(Color.WHITE);
104
        final TableCellRenderer booleanRenderer = new TableCellRenderer() {
105
            @Override
106
            public Component getTableCellRendererComponent(JTable table, final Object value, final boolean isSelected, boolean hasFocus, int row, int column) {
107
                return ((Boolean) value) ? trueComp : falseComp;
108
            }
109
        };
110
 
111
        for (int i = 0; i < columnModel.getColumnCount(); i++) {
112
            final TableColumn col = columnModel.getColumn(i);
113
            col.setIdentifier(ModuleTableModel.Columns.values()[i]);
114
 
115
            final int minCellWidth = this.tm.getColumnClass(i) == Boolean.class ? 32 : 48;
116
            col.setMinWidth(minCellWidth);
117
            final int prefCellWidth;
118
            if (col.getIdentifier() == ModuleTableModel.Columns.NAME || col.getIdentifier() == ModuleTableModel.Columns.STATE)
81 ilm 119
                prefCellWidth = 192;
80 ilm 120
            else
121
                prefCellWidth = minCellWidth;
122
            // makes sure the column can display its label
123
            final Component headerComp = headerDefaultRenderer.getTableCellRendererComponent(null, col.getHeaderValue(), false, false, 0, 0);
124
            col.setPreferredWidth(Math.max(headerComp.getMinimumSize().width, prefCellWidth));
125
            if (col.getIdentifier() == ModuleTableModel.Columns.CB) {
126
                col.setMaxWidth(minCellWidth);
127
            } else {
128
                col.setMaxWidth(Integer.MAX_VALUE);
129
            }
130
            col.setWidth(col.getPreferredWidth());
131
            if (booleanCols.contains(col.getIdentifier())) {
132
                col.setCellRenderer(new AlternateTableCellRenderer(booleanRenderer));
133
            } else {
134
                AlternateTableCellRenderer.setRenderer(col);
135
            }
136
        }
137
        // JTable returns by default a hard coded dimension (e.g. the table can be too small and its
138
        // column labels truncated)
139
        t.setPreferredScrollableViewportSize(new Dimension(t.getPreferredSize().width, 400));
140
        t.getTableHeader().setReorderingAllowed(false);
141
        // Better look
142
        t.setShowHorizontalLines(false);
143
        t.setGridColor(new Color(230, 230, 230));
144
        t.setRowHeight(t.getRowHeight() + 4);
145
        // allow the table model to be gc'd
146
        t.addHierarchyListener(new HierarchyListener() {
147
            @Override
148
            public void hierarchyChanged(HierarchyEvent e) {
149
                if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
150
                    // keep the table model to keep our renderers and maybe later the selection
151
                    // (though we could overload createDefaultColumnsFromModel() to set the
152
                    // renderers)
153
                    if (!e.getChanged().isDisplayable()) {
154
                        ModulePanel.this.tm.clear();
155
                    } else {
156
                        reload();
157
                    }
158
                }
159
            }
160
        });
161
 
162
        JScrollPane scroll = new JScrollPane(t);
163
        c.weighty = 1;
164
        c.weightx = 1;
165
        // 4 buttons + space
166
        c.gridheight = 5;
167
        c.fill = GridBagConstraints.BOTH;
168
        this.add(scroll, c);
169
        // Right column
170
        c.weightx = 0;
171
        c.weighty = 0;
172
        c.gridx++;
173
        c.fill = GridBagConstraints.NONE;
174
        c.gridheight = 1;
175
 
81 ilm 176
        final JButton installButton = new JButton(AvailableModulesPanel.createInstallAction(this, onlyInstall));
80 ilm 177
        installButton.setOpaque(false);
178
        this.add(installButton, c);
179
 
180
        if (!onlyInstall) {
181
            c.gridy++;
182
            JButton activateButton = new JButton(new StartStopAction(ModuleAction.START));
183
            activateButton.setOpaque(false);
184
            this.add(activateButton, c);
185
            JButton desactivateButton = new JButton(new StartStopAction(ModuleAction.STOP));
186
            desactivateButton.setOpaque(false);
187
            c.gridy++;
188
            this.add(desactivateButton, c);
189
        }
190
        c.insets = new Insets(20, 3, 2, 2);
191
        JButton uninstallButton = new JButton(new AbstractAction("Désinstaller") {
192
            @Override
193
            public void actionPerformed(ActionEvent evt) {
194
                final String dialogTitle = "Désinstallation de modules";
81 ilm 195
                final Collection<ModuleRow> checkedRows = getSelection();
80 ilm 196
                if (checkedRows.isEmpty()) {
197
                    JOptionPane.showMessageDialog(ModulePanel.this, "Aucune ligne cochée", dialogTitle, JOptionPane.INFORMATION_MESSAGE);
198
                    return;
199
                }
200
 
156 ilm 201
                final ModuleManager mngr = getModuleManager();
80 ilm 202
                final boolean forceUninstall = (evt.getModifiers() & ActionEvent.CTRL_MASK) != 0 && mngr.currentUserIsAdmin();
203
                try {
204
                    final Set<ModuleReference> ids = new HashSet<ModuleReference>();
205
                    final Set<ModuleReference> deniedRefs = new HashSet<ModuleReference>();
206
                    for (final ModuleRow f : checkedRows) {
207
                        if (!mngr.canCurrentUser(ModuleAction.UNINSTALL, f))
208
                            deniedRefs.add(f.getRef());
209
                        else
210
                            ids.add(f.getRef());
211
                    }
212
 
213
                    final String denied = deniedRefs.size() == 0 ? "" : "Désinstallation refusée pour les modules :\n" + formatList(deniedRefs) + ".\n";
214
                    if (ids.size() == 0) {
215
                        // since checkedRows is not empty
216
                        assert denied.length() > 0;
217
                        JOptionPane.showMessageDialog(ModulePanel.this, denied, dialogTitle, JOptionPane.WARNING_MESSAGE);
218
                        return;
219
                    }
220
                    assert ids.size() > 0;
221
                    final int answer = JOptionPane.showConfirmDialog(ModulePanel.this, "Êtes-vous sûr de vouloir désinstaller ces modules "
222
                            + (forceUninstall ? "*en ce passant des modules si nécessaire* " : "") + "?\n" + denied + "Toutes les données seront irrémédiablement effacées.", dialogTitle,
223
                            JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
224
                    if (answer == JOptionPane.NO_OPTION)
225
                        return;
226
 
227
                    final JDialog dialog = AvailableModulesPanel.displayDialog(ModulePanel.this, "Calcul des dépendences");
228
                    new SwingWorker<ModulesStateChange, Object>() {
229
                        protected ModulesStateChange doInBackground() throws Exception {
156 ilm 230
                            // MAYBE find other solutions (e.g. instead of removing all modules
231
                            // depending on ids, find other modules that can fulfill their
232
                            // dependencies)
80 ilm 233
                            return mngr.getUninstallSolution(ids, true, forceUninstall);
234
                        }
235
 
236
                        protected void done() {
237
                            dialog.dispose();
238
                            try {
239
                                final ModulesStateChange solution = this.get();
240
 
241
                                final Set<ModuleReference> dependentModules = new HashSet<ModuleReference>(solution.getReferencesToRemove());
242
                                dependentModules.removeAll(ids);
243
                                if (!dependentModules.isEmpty()) {
244
                                    deniedRefs.clear();
245
                                    // ids have already been checked
246
                                    for (final ModuleReference depModule : dependentModules) {
247
                                        if (!mngr.canCurrentUserInstall(ModuleAction.UNINSTALL, depModule, solution.getInstallState())) {
248
                                            deniedRefs.add(depModule);
249
                                        }
250
                                    }
251
 
252
                                    if (deniedRefs.size() > 0) {
151 ilm 253
                                        JOptionPane.showMessageDialog(ModulePanel.this, "Désinstallation refusée pour les modules :" + formatList(deniedRefs), dialogTitle,
254
                                                JOptionPane.WARNING_MESSAGE);
80 ilm 255
                                        return;
256
                                    }
257
 
151 ilm 258
                                    final int selectAnswer = JOptionPane.showConfirmDialog(ModulePanel.this,
259
                                            "Les modules suivants doivent être désinstallés : \n" + formatList(dependentModules) + ".\nVoulez-vous également désinstaller ces modules ?", dialogTitle,
260
                                            JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
80 ilm 261
                                    if (selectAnswer == JOptionPane.NO_OPTION)
262
                                        return;
263
                                }
264
 
156 ilm 265
                                AvailableModulesPanel.applySolution(mngr, ModulePanel.this, solution, ModuleState.NOT_CREATED);
80 ilm 266
                            } catch (Exception e) {
267
                                ExceptionHandler.handle(ModulePanel.this, "Impossible de désinstaller les modules", e);
268
                            }
269
                        }
270
                    }.execute();
271
                } catch (Exception e) {
272
                    ExceptionHandler.handle(ModulePanel.this, "Impossible de trouver les modules à désinstaller", e);
273
                }
274
            }
275
        });
276
        uninstallButton.setOpaque(false);
277
        c.gridy++;
278
        this.add(uninstallButton, c);
279
 
280
        JPanel space = new JPanel();
281
        space.setOpaque(false);
282
        c.weighty = 1;
283
        c.gridy++;
284
        this.add(space, c);
285
 
286
        this.tm.addTableModelListener(new TableModelListener() {
287
            @Override
288
            public void tableChanged(TableModelEvent e) {
289
                installButton.setEnabled(ModulePanel.this.tm.isValid());
290
                // uninstall to make it valid
291
            }
292
        });
293
        this.setTransferHandler(AvailableModulesPanel.createTransferHandler(this));
294
    }
295
 
156 ilm 296
    public final ModuleManager getModuleManager() {
297
        return this.tm.getModuleManager();
298
    }
299
 
80 ilm 300
    public final void reload() {
301
        try {
302
            this.tm.reload();
303
        } catch (Exception e) {
304
            ExceptionHandler.handle(ModulePanel.this, "Impossible de recharger la liste des modules", e);
305
        }
306
    }
307
 
81 ilm 308
    protected final Collection<ModuleRow> getSelection() {
309
        // don't use native selection since users have some difficulty to select multiple rows
310
        // NOTE: since we use a RowSorter this also frees us from converting indexes
311
        return this.tm.getCheckedRows();
312
    }
313
 
80 ilm 314
    // this doesn't change the installation state, only start/stop
315
    private final class StartStopAction extends AbstractAction {
316
        private final boolean start;
317
        private final ModuleAction action;
318
 
319
        public StartStopAction(final ModuleAction action) {
320
            super(action == ModuleAction.START ? "Activer" : "Désactiver");
321
            if (action != ModuleAction.START && action != ModuleAction.STOP)
322
                throw new IllegalArgumentException(action + " is neither START nor STOP");
323
            this.action = action;
324
            this.start = action == ModuleAction.START;
151 ilm 325
            this.putValue(Action.SHORT_DESCRIPTION,
326
                    this.start ? "Démarrer le(s) module(s), maintenir CTRL pour rendre obligatoire le démarrage" : "Arrête le(s) module(s), maintenir CTRL pour rendre facultatif le démarrage");
80 ilm 327
        }
328
 
329
        @Override
330
        public void actionPerformed(ActionEvent evt) {
156 ilm 331
            final ModuleManager mngr = getModuleManager();
80 ilm 332
            final String dialogTitle = this.start ? "Démarrage de modules" : "Arrêt de modules";
81 ilm 333
            final Collection<ModuleRow> checkedRows = getSelection();
80 ilm 334
            if (checkedRows.isEmpty()) {
335
                JOptionPane.showMessageDialog(ModulePanel.this, "Aucune ligne cochée", dialogTitle, JOptionPane.INFORMATION_MESSAGE);
336
                return;
337
            }
338
            final boolean userIsAdmin = mngr.currentUserIsAdmin();
339
            // on Ubuntu ALT-Click is used to move windows
340
            final boolean changeRequired = (evt.getModifiers() & ActionEvent.CTRL_MASK) != 0 && userIsAdmin;
341
            final Set<ModuleReference> adminRequired = changeRequired ? new HashSet<ModuleReference>() : Collections.<ModuleReference> emptySet();
342
            try {
343
                final Set<ModuleReference> refs = new HashSet<ModuleReference>();
344
                final Set<ModuleReference> deniedRefs = new HashSet<ModuleReference>();
345
                for (final ModuleRow f : checkedRows) {
346
                    if (!mngr.canCurrentUser(this.action, f)) {
347
                        deniedRefs.add(f.getRef());
348
                    } else {
349
                        refs.add(f.getRef());
350
                    }
351
                    if (changeRequired) {
352
                        adminRequired.add(f.getRef());
353
                    }
354
                }
355
                if (deniedRefs.size() > 0) {
356
                    final String msg = getDeniedMessage(deniedRefs);
357
                    if (AvailableModulesPanel.displayDenied(ModulePanel.this, dialogTitle, msg, refs.size() == 0))
358
                        return;
359
                }
360
                assert refs.size() > 0;
361
                if (this.start) {
362
                    // pass NO_CHANGE so that we don't block the EDT, use install button to install
363
                    // (resolve dependencies) and start modules
364
                    final Set<ModuleReference> notStarted = mngr.startModules(refs, NoChoicePredicate.NO_CHANGE, true);
365
                    if (notStarted.size() > 0)
366
                        JOptionPane.showMessageDialog(ModulePanel.this, getDeniedMessage(notStarted) + "\nEssayer d'abord de les installer", dialogTitle, JOptionPane.WARNING_MESSAGE);
367
                } else {
368
                    for (final ModuleReference ref : refs) {
369
                        final List<ModuleReference> runningDepModules = mngr.getRunningDependentModulesRecursively(ref.getID());
370
                        deniedRefs.clear();
371
                        for (final ModuleReference runningDepModule : runningDepModules) {
372
                            if (!mngr.canCurrentUser(this.action, ModulePanel.this.tm.getRow(runningDepModule))) {
373
                                deniedRefs.add(runningDepModule);
374
                            }
375
                        }
376
                        if (!deniedRefs.isEmpty()) {
377
                            JOptionPane.showMessageDialog(ModulePanel.this, getDeniedMessage(deniedRefs), dialogTitle, JOptionPane.WARNING_MESSAGE);
378
                        } else {
379
                            for (final ModuleReference runningDepModule : runningDepModules) {
380
                                mngr.stopModule(runningDepModule.getID());
381
                            }
382
                        }
383
                    }
384
                }
385
            } catch (Exception e) {
386
                ExceptionHandler.handle(ModulePanel.this, "Impossible " + (this.start ? "d'activer" : "de désactiver") + " les modules", e);
387
            } finally {
388
                try {
389
                    mngr.setAdminRequiredModules(adminRequired, this.start);
390
                } catch (BackingStoreException e1) {
391
                    ExceptionHandler.handle(ModulePanel.this, "Impossible de rendre " + (this.start ? "obligatoires" : "facultatifs") + " les modules", e1);
392
                }
393
                reload();
394
            }
395
        }
396
 
397
        private final String getDeniedMessage(final Set<ModuleReference> deniedRefs) {
398
            return "Les modules suivants ne peuvent être " + (this.start ? "démarrés" : "arrêtés") + " : \n" + formatList(deniedRefs);
399
        }
400
 
401
    }
402
 
403
    public static String formatList(Collection<ModuleReference> refs) {
404
        String str = "";
405
        final List<ModuleReference> lInstall = new ArrayList<ModuleReference>(refs);
406
        Collections.sort(lInstall, ModuleReference.COMP_ID_ASC_VERSION_DESC);
407
        for (ModuleReference moduleReference : refs) {
408
            str += "- " + format(moduleReference);
409
        }
410
        return str;
411
    }
412
 
413
    public static String format(ModuleReference moduleReference) {
414
        String str = moduleReference.getID();
415
        if (moduleReference.getVersion() != null) {
416
            str += " (" + moduleReference.getVersion().toString() + ")";
417
        }
418
        return str;
419
    }
420
}