OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

Rev Author Line No. Line
18 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.
18 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.erp.config;
15
 
16
import org.openconcerto.erp.core.common.ui.StatusPanel;
149 ilm 17
import org.openconcerto.erp.panel.UserExitConf;
18
import org.openconcerto.erp.panel.UserExitPanel;
93 ilm 19
import org.openconcerto.erp.rights.MenuComboRightEditor;
149 ilm 20
import org.openconcerto.sql.PropsConfiguration;
144 ilm 21
import org.openconcerto.sql.users.UserManager;
93 ilm 22
import org.openconcerto.sql.users.rights.UserRightsManager;
18 ilm 23
import org.openconcerto.task.TodoListPanel;
24
import org.openconcerto.task.config.ComptaBasePropsConfiguration;
20 ilm 25
import org.openconcerto.ui.AutoHideTabbedPane;
19 ilm 26
import org.openconcerto.ui.MenuUtils;
18 ilm 27
import org.openconcerto.ui.SwingThreadUtils;
67 ilm 28
import org.openconcerto.ui.group.Group;
29
import org.openconcerto.ui.group.Item;
20 ilm 30
import org.openconcerto.ui.state.WindowStateManager;
18 ilm 31
import org.openconcerto.utils.JImage;
32
import org.openconcerto.utils.OSXAdapter;
33
 
34
import java.awt.Color;
35
import java.awt.Container;
182 ilm 36
import java.awt.Desktop;
19 ilm 37
import java.awt.Dimension;
18 ilm 38
import java.awt.GridBagConstraints;
39
import java.awt.GridBagLayout;
61 ilm 40
import java.awt.Image;
18 ilm 41
import java.awt.event.ActionEvent;
42
import java.awt.event.WindowAdapter;
43
import java.awt.event.WindowEvent;
73 ilm 44
import java.beans.PropertyChangeEvent;
45
import java.beans.PropertyChangeListener;
20 ilm 46
import java.io.File;
182 ilm 47
import java.lang.reflect.InvocationHandler;
48
import java.lang.reflect.Method;
49
import java.lang.reflect.Proxy;
18 ilm 50
import java.util.ArrayList;
19 ilm 51
import java.util.Arrays;
73 ilm 52
import java.util.Collections;
18 ilm 53
import java.util.List;
54
 
55
import javax.swing.AbstractAction;
19 ilm 56
import javax.swing.Action;
18 ilm 57
import javax.swing.ImageIcon;
58
import javax.swing.JFrame;
59
import javax.swing.JMenu;
60
import javax.swing.JMenuBar;
61
import javax.swing.JMenuItem;
67 ilm 62
import javax.swing.JOptionPane;
18 ilm 63
import javax.swing.JSeparator;
80 ilm 64
import javax.swing.SwingUtilities;
18 ilm 65
 
80 ilm 66
import net.jcip.annotations.GuardedBy;
67
 
18 ilm 68
public class MainFrame extends JFrame {
69
 
19 ilm 70
    // menus
73 ilm 71
    public static final String STRUCTURE_MENU = "menu.organization";
72
    public static final String PAYROLL_MENU = "menu.payroll";
73
    public static final String PAYMENT_MENU = "menu.payment";
74
    public static final String STATS_MENU = "menu.stats";
75
    public static final String DECLARATION_MENU = "menu.report";
76
    public static final String STATE_MENU = "menu.accounting";
77
    public static final String LIST_MENU = "menu.list";
78
    public static final String CREATE_MENU = "menu.create";
79
    public static final String FILE_MENU = "menu.file";
80
    public static final String HELP_MENU = "menu.help";
19 ilm 81
 
182 ilm 82
    public static final String PREFS_MENU_ITEM = "preferences";
83
    public static final String QUIT_MENU_ITEM = "quit";
84
    public static final String ABOUT_MENU_ITEM = "information";
85
 
18 ilm 86
    static private final List<Runnable> runnables = new ArrayList<Runnable>();
80 ilm 87
    @GuardedBy("MainFrame")
18 ilm 88
    static private MainFrame instance = null;
89
 
80 ilm 90
    // thread safe
91
    public synchronized static MainFrame getInstance() {
18 ilm 92
        return instance;
93
    }
94
 
80 ilm 95
    public synchronized static MainFrame resetInstance() {
96
        final MainFrame res = instance;
97
        setInstance(null);
98
        if (res != null) {
99
            res.setVisible(false);
100
            res.dispose();
101
        }
102
        return res;
103
    }
104
 
105
    private synchronized static void setInstance(MainFrame f) {
106
        assert SwingUtilities.isEventDispatchThread();
18 ilm 107
        if (f != null && instance != null)
108
            throw new IllegalStateException("More than one main frame");
109
        instance = f;
110
        if (f != null) {
111
            for (final Runnable r : runnables)
112
                r.run();
113
            runnables.clear();
114
        }
115
    }
116
 
117
    /**
118
     * Execute the runnable in the EDT after the main frame has been created. Thus if the main frame
119
     * has already been created and we're in the EDT, execute <code>r</code> immediately.
120
     *
121
     * @param r the runnable to run.
122
     * @see #getInstance()
123
     */
124
    public static void invoke(final Runnable r) {
125
        SwingThreadUtils.invoke(new Runnable() {
126
            @Override
127
            public void run() {
80 ilm 128
                if (getInstance() == null) {
18 ilm 129
                    runnables.add(r);
130
                } else {
131
                    r.run();
132
                }
133
            }
134
        });
135
    }
136
 
20 ilm 137
    private final AutoHideTabbedPane tabContainer;
138
    private TodoListPanel todoPanel;
18 ilm 139
    private JImage image;
140
 
141
    public TodoListPanel getTodoPanel() {
142
        return this.todoPanel;
143
    }
144
 
156 ilm 145
    public MainFrame(final PropsConfiguration conf) {
18 ilm 146
        super();
147
 
80 ilm 148
        this.setIconImage(new ImageIcon(MainFrame.class.getResource("frameicon.png")).getImage());
67 ilm 149
 
18 ilm 150
        Container co = this.getContentPane();
151
        co.setLayout(new GridBagLayout());
152
        GridBagConstraints c = new GridBagConstraints();
153
        c.fill = GridBagConstraints.HORIZONTAL;
154
        c.gridx = 0;
155
        c.gridy = 0;
156
        // // co.add(new RangeSlider(2005), c);
157
        c.weightx = 1;
158
        c.weighty = 0;
20 ilm 159
        this.image = new JImage(ComptaBasePropsConfiguration.class.getResource("logo.png"));
160
        this.image.setBackground(Color.WHITE);
161
        this.image.check();
162
        co.add(this.image, c);
18 ilm 163
        c.weighty = 0;
164
        c.gridy++;
165
        c.fill = GridBagConstraints.BOTH;
166
        co.add(new JSeparator(JSeparator.HORIZONTAL), c);
167
        c.gridy++;
168
        c.weighty = 1;
20 ilm 169
        this.tabContainer = new AutoHideTabbedPane();
170
        co.add(this.tabContainer, c);
171
        Dimension minSize;
172
        final String confSuffix;
173
        if (!Gestion.isMinimalMode()) {
144 ilm 174
            this.todoPanel = new TodoListPanel(UserManager.getInstance());
20 ilm 175
            this.getTabbedPane().addTab("Tâches", this.todoPanel);
176
            minSize = new Dimension(800, 600);
177
            confSuffix = "";
178
        } else {
179
            minSize = null;
180
            confSuffix = "-minimal";
181
        }
18 ilm 182
        c.weighty = 0;
183
        c.gridy++;
184
        c.fill = GridBagConstraints.HORIZONTAL;
185
        co.add(StatusPanel.getInstance(), c);
186
 
20 ilm 187
        if (minSize == null) {
188
            this.pack();
189
            minSize = new Dimension(this.getSize());
190
        }
191
        this.setMinimumSize(minSize);
192
 
80 ilm 193
        final File confFile = new File(conf.getConfDir(), "Configuration" + File.separator + "Frame" + File.separator + "mainFrame" + confSuffix + ".xml");
20 ilm 194
        new WindowStateManager(this, confFile).loadState();
195
 
18 ilm 196
        this.addWindowListener(new WindowAdapter() {
20 ilm 197
            @Override
18 ilm 198
            public void windowClosing(WindowEvent arg0) {
199
                quit();
200
            }
201
        });
202
 
203
        setInstance(this);
149 ilm 204
 
156 ilm 205
        final Thread dbConn = conf.createDBCheckThread(this, new Runnable() {
149 ilm 206
            @Override
207
            public void run() {
208
                // the user has already clicked "Quit" don't ask him again
209
                // Further the DB is unavailable so most actions (e.g. backup) cannot run
210
                UserExitPanel.exit(UserExitConf.DEFAULT);
211
            }
212
        });
213
        dbConn.start();
214
 
61 ilm 215
        // Overrive logo
80 ilm 216
        final Image im = conf instanceof ComptaPropsConfiguration ? ((ComptaPropsConfiguration) conf).getCustomLogo() : null;
61 ilm 217
        if (im != null) {
218
            image.setImage(im);
219
        }
182 ilm 220
 
18 ilm 221
    }
222
 
182 ilm 223
    // create the UI menu from the MenuAndActions and hide items handled by java.awt.Desktop
73 ilm 224
    public final void initMenuBar() {
225
        final MenuManager mm = MenuManager.getInstance();
226
        final PropertyChangeListener listener = new PropertyChangeListener() {
227
            @Override
228
            public void propertyChange(PropertyChangeEvent evt) {
229
                // TODO change only if needed (mind the actions)
230
                setJMenuBar(createMenu(mm));
231
                getLayeredPane().revalidate();
232
            }
233
        };
234
        mm.addPropertyChangeListener(listener);
235
        listener.propertyChange(null);
20 ilm 236
    }
237
 
73 ilm 238
    private final JMenuBar createMenu(final MenuManager mm) {
67 ilm 239
        final JMenuBar result = new JMenuBar();
73 ilm 240
        final Group g = mm.getGroup();
67 ilm 241
        for (int i = 0; i < g.getSize(); i++) {
242
            final Item item = g.getItem(i);
93 ilm 243
            if (item.getLocalHint().isVisible() && UserRightsManager.getCurrentUserRights().haveRight(MenuComboRightEditor.ID_RIGHT, item.getId()))
73 ilm 244
                result.add(createJMenuFrom(item, mm));
18 ilm 245
        }
28 ilm 246
        return result;
247
    }
18 ilm 248
 
73 ilm 249
    private final JMenu createJMenuFrom(final Item item, final MenuManager mm) {
250
        assert item.getLocalHint().isVisible();
67 ilm 251
        final String id = item.getId();
73 ilm 252
        final String name = mm.getLabelForId(id);
253
        final String menuLabel;
254
        final Color c;
67 ilm 255
        if (name == null || name.trim().isEmpty()) {
256
            menuLabel = id;
73 ilm 257
            c = new Color(200, 65, 20);
258
        } else {
259
            menuLabel = name;
260
            c = null;
18 ilm 261
        }
73 ilm 262
        assert menuLabel != null;
67 ilm 263
        final JMenu m = new JMenu(menuLabel);
73 ilm 264
        if (c != null)
265
            m.setForeground(c);
266
 
67 ilm 267
        if (item instanceof Group) {
73 ilm 268
            createMenuItemsRec(mm, m, Collections.<String> emptyList(), (Group) item);
93 ilm 269
        } else if (UserRightsManager.getCurrentUserRights().haveRight(MenuComboRightEditor.ID_RIGHT, item.getId())) {
73 ilm 270
            m.add(createJMenuItemForId(id, mm));
18 ilm 271
        }
67 ilm 272
        return m;
28 ilm 273
    }
18 ilm 274
 
73 ilm 275
    private final void createMenuItemsRec(final MenuManager mm, final JMenu topLevelMenu, final List<String> path, final Group g) {
276
        assert g.getLocalHint().isVisible();
277
        for (int i = 0; i < g.getSize(); i++) {
278
            final Item child = g.getItem(i);
93 ilm 279
            if (!child.getLocalHint().isVisible() || !UserRightsManager.getCurrentUserRights().haveRight(MenuComboRightEditor.ID_RIGHT, child.getId()))
73 ilm 280
                continue;
281
 
282
            if (child instanceof Group) {
283
                final List<String> newPath = new ArrayList<String>(path);
284
                newPath.add(child.getId());
285
                createMenuItemsRec(mm, topLevelMenu, newPath, (Group) child);
286
            } else {
287
                final List<String> newPath;
288
                if (path.size() % 2 == 0) {
289
                    newPath = new ArrayList<String>(path);
290
                    newPath.add(null);
291
                } else {
292
                    newPath = path;
67 ilm 293
                }
73 ilm 294
                MenuUtils.addMenuItem(createJMenuItemForId(child.getId(), mm), topLevelMenu, newPath);
67 ilm 295
            }
18 ilm 296
        }
28 ilm 297
    }
298
 
73 ilm 299
    static public String getFirstNonEmpty(final List<String> l) {
300
        for (final String s : l) {
301
            if (s != null && !s.trim().isEmpty()) {
302
                return s;
303
            }
18 ilm 304
        }
73 ilm 305
        return null;
306
    }
18 ilm 307
 
73 ilm 308
    private final JMenuItem createJMenuItemForId(final String id, final MenuManager mm) {
309
        final String mngrLabel = mm.getLabelForId(id);
310
        final Action mngrAction = mm.getActionForId(id);
311
        final String mngrActionName = mngrAction == null || mngrAction.getValue(Action.NAME) == null ? null : mngrAction.getValue(Action.NAME).toString();
312
 
313
        final String label = getFirstNonEmpty(Arrays.asList(mngrLabel, mngrActionName, id));
314
        assert label != null;
315
        final Action action;
316
        final Color fg;
317
        if (mngrAction == null) {
318
            action = new AbstractAction(label) {
67 ilm 319
                @Override
320
                public void actionPerformed(ActionEvent e) {
321
                    JOptionPane.showMessageDialog(MainFrame.this, "No action for " + id);
322
                }
323
            };
73 ilm 324
            fg = new Color(200, 65, 95);
325
        } else {
326
            action = mngrAction;
177 ilm 327
            // Allow to leave the name blank at action creation, then set it here so that it can be
328
            // used in actionPerformed()
329
            if (mngrActionName == null)
330
                action.putValue(Action.NAME, label);
73 ilm 331
            fg = label.equals(id) ? new Color(20, 65, 200) : null;
28 ilm 332
        }
18 ilm 333
 
73 ilm 334
        final JMenuItem res = new JMenuItem(action);
335
        // don't use action name but the provided label
336
        res.setHideActionText(true);
337
        res.setText(label);
338
        if (fg != null)
339
            res.setForeground(fg);
340
        return res;
18 ilm 341
    }
342
 
19 ilm 343
    public JMenuItem addMenuItem(final Action action, final String... path) {
344
        return this.addMenuItem(action, Arrays.asList(path));
345
    }
346
 
347
    /**
348
     * Adds a menu item to this menu. The path should be an alternation of menu and group within
349
     * that menu. All items within the same group will be grouped together inside separators. Menus
350
     * will be created as needed.
351
     *
352
     * @param action the action to perform.
353
     * @param path where to add the menu item.
354
     * @return the newly created item.
355
     * @throws IllegalArgumentException if path is not even.
356
     */
357
    public JMenuItem addMenuItem(final Action action, final List<String> path) throws IllegalArgumentException {
358
        if (path.size() == 0 || path.size() % 2 != 0)
359
            throw new IllegalArgumentException("Path should be of the form menu/group/menu/group/... : " + path);
360
        final JMenu topLevelMenu = getMenu(path.get(0));
361
        return MenuUtils.addMenuItem(action, topLevelMenu, path.subList(1, path.size()));
362
    }
363
 
364
    // get or create (at the end) a top level menu
365
    private JMenu getMenu(final String name) {
366
        final JMenu existing = MenuUtils.findChild(this.getJMenuBar(), name, JMenu.class);
367
        final JMenu res;
368
        if (existing == null) {
369
            res = new JMenu(name);
20 ilm 370
            // insert before the help menu
371
            this.getJMenuBar().add(res, this.getJMenuBar().getComponentCount() - 1);
19 ilm 372
        } else {
373
            res = existing;
374
        }
375
        return res;
376
    }
377
 
378
    /**
379
     * Remove the passed item from this menu. This method handles the cleanup of separators and
380
     * empty menus.
381
     *
382
     * @param item the item to remove.
383
     * @throws IllegalArgumentException if <code>item</code> is not in this menu.
384
     */
385
    public void removeMenuItem(final JMenuItem item) throws IllegalArgumentException {
386
        if (SwingThreadUtils.getAncestorOrSelf(JMenuBar.class, item) != this.getJMenuBar())
387
            throw new IllegalArgumentException("Item not in this menu " + item);
388
        MenuUtils.removeMenuItem(item);
389
    }
390
 
182 ilm 391
    public void registerDesktopEvents(final MenuAndActions menuAndActions) {
392
        // First try standard >=9 way, otherwise fall back to com.apple.eawt.Application
393
        if (!this.registerAWTDesktopEvents(menuAndActions))
394
            this.registerForMacOSXEvents(menuAndActions);
395
    }
396
 
397
    private boolean registerAWTDesktopEvents(final MenuAndActions menuAndActions) {
398
        if (!Desktop.isDesktopSupported())
399
            return false;
400
        final Desktop desktop = Desktop.getDesktop();
401
        final boolean quitHandled = registerAWTDesktopEvent(desktop, "QuitHandler", "APP_QUIT_HANDLER", menuAndActions, QUIT_MENU_ITEM);
402
        final boolean aboutHandled = registerAWTDesktopEvent(desktop, "AboutHandler", "APP_ABOUT", menuAndActions, ABOUT_MENU_ITEM);
403
        final boolean prefsHandled = registerAWTDesktopEvent(desktop, "PreferencesHandler", "APP_PREFERENCES", menuAndActions, PREFS_MENU_ITEM);
404
        return quitHandled && aboutHandled && prefsHandled;
405
    }
406
 
407
    private boolean registerAWTDesktopEvent(final Desktop desktop, final String handlerClassName, final String desktopActionName, final MenuAndActions menuAndActions, final String menuActionName) {
408
        final Desktop.Action appPrefsAction;
409
        try {
410
            appPrefsAction = Desktop.Action.valueOf(desktopActionName);
411
        } catch (Exception e) {
412
            // JRE <9, fallback to the old way
413
            return false;
414
        }
415
        if (!desktop.isSupported(appPrefsAction)) {
416
            // JRE >= 9, but not supported
417
            return false;
418
        }
419
        try {
420
            final Class<?> prefHandlerClass = Class.forName("java.awt.desktop." + handlerClassName);
421
            final Object prefHandler = Proxy.newProxyInstance(prefHandlerClass.getClassLoader(), new Class<?>[] { prefHandlerClass }, new InvocationHandler() {
422
                @Override
423
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
424
                    menuAndActions.getAction(menuActionName).actionPerformed(null);
425
                    return null;
426
                }
427
            });
428
            desktop.getClass().getMethod("set" + handlerClassName, prefHandlerClass).invoke(desktop, prefHandler);
429
            menuAndActions.setMenuItemVisible(menuActionName, false);
430
            return true;
431
        } catch (Exception e) {
432
            // Unknown error, still try to fallback to the old way
433
            e.printStackTrace();
434
            return false;
435
        }
436
    }
437
 
18 ilm 438
    // Generic registration with the Mac OS X application menu
439
    // Checks the platform, then attempts to register with the Apple EAWT
440
    // See OSXAdapter.java to see how this is done without directly referencing any Apple APIs
182 ilm 441
    // Remove once depending on Java 11.
442
    @Deprecated
443
    public void registerForMacOSXEvents(final MenuAndActions menuAndActions) {
18 ilm 444
        if (Gestion.MAC_OS_X) {
445
            try {
446
                // Generate and register the OSXAdapter, passing it a hash of all the methods we
447
                // wish to use as delegates for various com.apple.eawt.ApplicationListener methods
448
                OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("quit", new Class[0]));
449
                OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("about", new Class[0]));
450
                OSXAdapter.setPreferencesHandler(this, getClass().getDeclaredMethod("preferences", new Class[0]));
182 ilm 451
                menuAndActions.setMenuItemVisible(QUIT_MENU_ITEM, false);
452
                menuAndActions.setMenuItemVisible(ABOUT_MENU_ITEM, false);
453
                menuAndActions.setMenuItemVisible(PREFS_MENU_ITEM, false);
18 ilm 454
                // no OSXAdapter.setFileHandler() for now
455
            } catch (Exception e) {
456
                System.err.println("Error while loading the OSXAdapter:");
457
                e.printStackTrace();
458
            }
459
        }
460
    }
461
 
462
    // used by OSXAdapter
182 ilm 463
    @Deprecated
18 ilm 464
    public final void preferences() {
182 ilm 465
        MenuManager.getInstance().getActionForId(PREFS_MENU_ITEM).actionPerformed(null);
18 ilm 466
    }
467
 
182 ilm 468
    @Deprecated
18 ilm 469
    public final void about() {
182 ilm 470
        MenuManager.getInstance().getActionForId(ABOUT_MENU_ITEM).actionPerformed(null);
18 ilm 471
    }
472
 
473
    public boolean quit() {
20 ilm 474
        if (this.getTodoPanel() != null)
475
            this.getTodoPanel().stopUpdate();
18 ilm 476
        Gestion.askForExit();
477
        return false;
478
    }
20 ilm 479
 
480
    public final AutoHideTabbedPane getTabbedPane() {
481
        return this.tabContainer;
482
    }
18 ilm 483
}