Dépôt officiel du code source de l'ERP OpenConcerto
Rev 177 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.erp.config;
import org.openconcerto.erp.core.common.ui.StatusPanel;
import org.openconcerto.erp.panel.UserExitConf;
import org.openconcerto.erp.panel.UserExitPanel;
import org.openconcerto.erp.rights.MenuComboRightEditor;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.task.TodoListPanel;
import org.openconcerto.task.config.ComptaBasePropsConfiguration;
import org.openconcerto.ui.AutoHideTabbedPane;
import org.openconcerto.ui.MenuUtils;
import org.openconcerto.ui.SwingThreadUtils;
import org.openconcerto.ui.group.Group;
import org.openconcerto.ui.group.Item;
import org.openconcerto.ui.state.WindowStateManager;
import org.openconcerto.utils.JImage;
import org.openconcerto.utils.OSXAdapter;
import java.awt.Color;
import java.awt.Container;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import net.jcip.annotations.GuardedBy;
public class MainFrame extends JFrame {
// menus
public static final String STRUCTURE_MENU = "menu.organization";
public static final String PAYROLL_MENU = "menu.payroll";
public static final String PAYMENT_MENU = "menu.payment";
public static final String STATS_MENU = "menu.stats";
public static final String DECLARATION_MENU = "menu.report";
public static final String STATE_MENU = "menu.accounting";
public static final String LIST_MENU = "menu.list";
public static final String CREATE_MENU = "menu.create";
public static final String FILE_MENU = "menu.file";
public static final String HELP_MENU = "menu.help";
public static final String PREFS_MENU_ITEM = "preferences";
public static final String QUIT_MENU_ITEM = "quit";
public static final String ABOUT_MENU_ITEM = "information";
static private final List<Runnable> runnables = new ArrayList<Runnable>();
@GuardedBy("MainFrame")
static private MainFrame instance = null;
// thread safe
public synchronized static MainFrame getInstance() {
return instance;
}
public synchronized static MainFrame resetInstance() {
final MainFrame res = instance;
setInstance(null);
if (res != null) {
res.setVisible(false);
res.dispose();
}
return res;
}
private synchronized static void setInstance(MainFrame f) {
assert SwingUtilities.isEventDispatchThread();
if (f != null && instance != null)
throw new IllegalStateException("More than one main frame");
instance = f;
if (f != null) {
for (final Runnable r : runnables)
r.run();
runnables.clear();
}
}
/**
* Execute the runnable in the EDT after the main frame has been created. Thus if the main frame
* has already been created and we're in the EDT, execute <code>r</code> immediately.
*
* @param r the runnable to run.
* @see #getInstance()
*/
public static void invoke(final Runnable r) {
SwingThreadUtils.invoke(new Runnable() {
@Override
public void run() {
if (getInstance() == null) {
runnables.add(r);
} else {
r.run();
}
}
});
}
private final AutoHideTabbedPane tabContainer;
private TodoListPanel todoPanel;
private JImage image;
public TodoListPanel getTodoPanel() {
return this.todoPanel;
}
public MainFrame(final PropsConfiguration conf) {
super();
this.setIconImage(new ImageIcon(MainFrame.class.getResource("frameicon.png")).getImage());
Container co = this.getContentPane();
co.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
// // co.add(new RangeSlider(2005), c);
c.weightx = 1;
c.weighty = 0;
this.image = new JImage(ComptaBasePropsConfiguration.class.getResource("logo.png"));
this.image.setBackground(Color.WHITE);
this.image.check();
co.add(this.image, c);
c.weighty = 0;
c.gridy++;
c.fill = GridBagConstraints.BOTH;
co.add(new JSeparator(JSeparator.HORIZONTAL), c);
c.gridy++;
c.weighty = 1;
this.tabContainer = new AutoHideTabbedPane();
co.add(this.tabContainer, c);
Dimension minSize;
final String confSuffix;
if (!Gestion.isMinimalMode()) {
this.todoPanel = new TodoListPanel(UserManager.getInstance());
this.getTabbedPane().addTab("Tâches", this.todoPanel);
minSize = new Dimension(800, 600);
confSuffix = "";
} else {
minSize = null;
confSuffix = "-minimal";
}
c.weighty = 0;
c.gridy++;
c.fill = GridBagConstraints.HORIZONTAL;
co.add(StatusPanel.getInstance(), c);
if (minSize == null) {
this.pack();
minSize = new Dimension(this.getSize());
}
this.setMinimumSize(minSize);
final File confFile = new File(conf.getConfDir(), "Configuration" + File.separator + "Frame" + File.separator + "mainFrame" + confSuffix + ".xml");
new WindowStateManager(this, confFile).loadState();
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent arg0) {
quit();
}
});
setInstance(this);
final Thread dbConn = conf.createDBCheckThread(this, new Runnable() {
@Override
public void run() {
// the user has already clicked "Quit" don't ask him again
// Further the DB is unavailable so most actions (e.g. backup) cannot run
UserExitPanel.exit(UserExitConf.DEFAULT);
}
});
dbConn.start();
// Overrive logo
final Image im = conf instanceof ComptaPropsConfiguration ? ((ComptaPropsConfiguration) conf).getCustomLogo() : null;
if (im != null) {
image.setImage(im);
}
}
// create the UI menu from the MenuAndActions and hide items handled by java.awt.Desktop
public final void initMenuBar() {
final MenuManager mm = MenuManager.getInstance();
final PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
// TODO change only if needed (mind the actions)
setJMenuBar(createMenu(mm));
getLayeredPane().revalidate();
}
};
mm.addPropertyChangeListener(listener);
listener.propertyChange(null);
}
private final JMenuBar createMenu(final MenuManager mm) {
final JMenuBar result = new JMenuBar();
final Group g = mm.getGroup();
for (int i = 0; i < g.getSize(); i++) {
final Item item = g.getItem(i);
if (item.getLocalHint().isVisible() && UserRightsManager.getCurrentUserRights().haveRight(MenuComboRightEditor.ID_RIGHT, item.getId()))
result.add(createJMenuFrom(item, mm));
}
return result;
}
private final JMenu createJMenuFrom(final Item item, final MenuManager mm) {
assert item.getLocalHint().isVisible();
final String id = item.getId();
final String name = mm.getLabelForId(id);
final String menuLabel;
final Color c;
if (name == null || name.trim().isEmpty()) {
menuLabel = id;
c = new Color(200, 65, 20);
} else {
menuLabel = name;
c = null;
}
assert menuLabel != null;
final JMenu m = new JMenu(menuLabel);
if (c != null)
m.setForeground(c);
if (item instanceof Group) {
createMenuItemsRec(mm, m, Collections.<String> emptyList(), (Group) item);
} else if (UserRightsManager.getCurrentUserRights().haveRight(MenuComboRightEditor.ID_RIGHT, item.getId())) {
m.add(createJMenuItemForId(id, mm));
}
return m;
}
private final void createMenuItemsRec(final MenuManager mm, final JMenu topLevelMenu, final List<String> path, final Group g) {
assert g.getLocalHint().isVisible();
for (int i = 0; i < g.getSize(); i++) {
final Item child = g.getItem(i);
if (!child.getLocalHint().isVisible() || !UserRightsManager.getCurrentUserRights().haveRight(MenuComboRightEditor.ID_RIGHT, child.getId()))
continue;
if (child instanceof Group) {
final List<String> newPath = new ArrayList<String>(path);
newPath.add(child.getId());
createMenuItemsRec(mm, topLevelMenu, newPath, (Group) child);
} else {
final List<String> newPath;
if (path.size() % 2 == 0) {
newPath = new ArrayList<String>(path);
newPath.add(null);
} else {
newPath = path;
}
MenuUtils.addMenuItem(createJMenuItemForId(child.getId(), mm), topLevelMenu, newPath);
}
}
}
static public String getFirstNonEmpty(final List<String> l) {
for (final String s : l) {
if (s != null && !s.trim().isEmpty()) {
return s;
}
}
return null;
}
private final JMenuItem createJMenuItemForId(final String id, final MenuManager mm) {
final String mngrLabel = mm.getLabelForId(id);
final Action mngrAction = mm.getActionForId(id);
final String mngrActionName = mngrAction == null || mngrAction.getValue(Action.NAME) == null ? null : mngrAction.getValue(Action.NAME).toString();
final String label = getFirstNonEmpty(Arrays.asList(mngrLabel, mngrActionName, id));
assert label != null;
final Action action;
final Color fg;
if (mngrAction == null) {
action = new AbstractAction(label) {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(MainFrame.this, "No action for " + id);
}
};
fg = new Color(200, 65, 95);
} else {
action = mngrAction;
// Allow to leave the name blank at action creation, then set it here so that it can be
// used in actionPerformed()
if (mngrActionName == null)
action.putValue(Action.NAME, label);
fg = label.equals(id) ? new Color(20, 65, 200) : null;
}
final JMenuItem res = new JMenuItem(action);
// don't use action name but the provided label
res.setHideActionText(true);
res.setText(label);
if (fg != null)
res.setForeground(fg);
return res;
}
public JMenuItem addMenuItem(final Action action, final String... path) {
return this.addMenuItem(action, Arrays.asList(path));
}
/**
* Adds a menu item to this menu. The path should be an alternation of menu and group within
* that menu. All items within the same group will be grouped together inside separators. Menus
* will be created as needed.
*
* @param action the action to perform.
* @param path where to add the menu item.
* @return the newly created item.
* @throws IllegalArgumentException if path is not even.
*/
public JMenuItem addMenuItem(final Action action, final List<String> path) throws IllegalArgumentException {
if (path.size() == 0 || path.size() % 2 != 0)
throw new IllegalArgumentException("Path should be of the form menu/group/menu/group/... : " + path);
final JMenu topLevelMenu = getMenu(path.get(0));
return MenuUtils.addMenuItem(action, topLevelMenu, path.subList(1, path.size()));
}
// get or create (at the end) a top level menu
private JMenu getMenu(final String name) {
final JMenu existing = MenuUtils.findChild(this.getJMenuBar(), name, JMenu.class);
final JMenu res;
if (existing == null) {
res = new JMenu(name);
// insert before the help menu
this.getJMenuBar().add(res, this.getJMenuBar().getComponentCount() - 1);
} else {
res = existing;
}
return res;
}
/**
* Remove the passed item from this menu. This method handles the cleanup of separators and
* empty menus.
*
* @param item the item to remove.
* @throws IllegalArgumentException if <code>item</code> is not in this menu.
*/
public void removeMenuItem(final JMenuItem item) throws IllegalArgumentException {
if (SwingThreadUtils.getAncestorOrSelf(JMenuBar.class, item) != this.getJMenuBar())
throw new IllegalArgumentException("Item not in this menu " + item);
MenuUtils.removeMenuItem(item);
}
public void registerDesktopEvents(final MenuAndActions menuAndActions) {
// First try standard >=9 way, otherwise fall back to com.apple.eawt.Application
if (!this.registerAWTDesktopEvents(menuAndActions))
this.registerForMacOSXEvents(menuAndActions);
}
private boolean registerAWTDesktopEvents(final MenuAndActions menuAndActions) {
if (!Desktop.isDesktopSupported())
return false;
final Desktop desktop = Desktop.getDesktop();
final boolean quitHandled = registerAWTDesktopEvent(desktop, "QuitHandler", "APP_QUIT_HANDLER", menuAndActions, QUIT_MENU_ITEM);
final boolean aboutHandled = registerAWTDesktopEvent(desktop, "AboutHandler", "APP_ABOUT", menuAndActions, ABOUT_MENU_ITEM);
final boolean prefsHandled = registerAWTDesktopEvent(desktop, "PreferencesHandler", "APP_PREFERENCES", menuAndActions, PREFS_MENU_ITEM);
return quitHandled && aboutHandled && prefsHandled;
}
private boolean registerAWTDesktopEvent(final Desktop desktop, final String handlerClassName, final String desktopActionName, final MenuAndActions menuAndActions, final String menuActionName) {
final Desktop.Action appPrefsAction;
try {
appPrefsAction = Desktop.Action.valueOf(desktopActionName);
} catch (Exception e) {
// JRE <9, fallback to the old way
return false;
}
if (!desktop.isSupported(appPrefsAction)) {
// JRE >= 9, but not supported
return false;
}
try {
final Class<?> prefHandlerClass = Class.forName("java.awt.desktop." + handlerClassName);
final Object prefHandler = Proxy.newProxyInstance(prefHandlerClass.getClassLoader(), new Class<?>[] { prefHandlerClass }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
menuAndActions.getAction(menuActionName).actionPerformed(null);
return null;
}
});
desktop.getClass().getMethod("set" + handlerClassName, prefHandlerClass).invoke(desktop, prefHandler);
menuAndActions.setMenuItemVisible(menuActionName, false);
return true;
} catch (Exception e) {
// Unknown error, still try to fallback to the old way
e.printStackTrace();
return false;
}
}
// Generic registration with the Mac OS X application menu
// Checks the platform, then attempts to register with the Apple EAWT
// See OSXAdapter.java to see how this is done without directly referencing any Apple APIs
// Remove once depending on Java 11.
@Deprecated
public void registerForMacOSXEvents(final MenuAndActions menuAndActions) {
if (Gestion.MAC_OS_X) {
try {
// Generate and register the OSXAdapter, passing it a hash of all the methods we
// wish to use as delegates for various com.apple.eawt.ApplicationListener methods
OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("quit", new Class[0]));
OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("about", new Class[0]));
OSXAdapter.setPreferencesHandler(this, getClass().getDeclaredMethod("preferences", new Class[0]));
menuAndActions.setMenuItemVisible(QUIT_MENU_ITEM, false);
menuAndActions.setMenuItemVisible(ABOUT_MENU_ITEM, false);
menuAndActions.setMenuItemVisible(PREFS_MENU_ITEM, false);
// no OSXAdapter.setFileHandler() for now
} catch (Exception e) {
System.err.println("Error while loading the OSXAdapter:");
e.printStackTrace();
}
}
}
// used by OSXAdapter
@Deprecated
public final void preferences() {
MenuManager.getInstance().getActionForId(PREFS_MENU_ITEM).actionPerformed(null);
}
@Deprecated
public final void about() {
MenuManager.getInstance().getActionForId(ABOUT_MENU_ITEM).actionPerformed(null);
}
public boolean quit() {
if (this.getTodoPanel() != null)
this.getTodoPanel().stopUpdate();
Gestion.askForExit();
return false;
}
public final AutoHideTabbedPane getTabbedPane() {
return this.tabContainer;
}
}