// GraphTea Project: http://github.com/graphtheorysoftware/GraphTea
// Copyright (C) 2012 Graph Theory Software Foundation: http://GraphTheorySoftware.com
// Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology
// Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/
package graphtea.ui.xml;
import graphtea.platform.StaticUtils;
import graphtea.platform.core.AbstractAction;
import graphtea.platform.core.BlackBoard;
import graphtea.platform.core.exception.ExceptionHandler;
import graphtea.platform.extension.ExtensionLoader;
import graphtea.platform.lang.Pair;
import graphtea.platform.preferences.lastsettings.StorableOnExit;
import graphtea.ui.UIUtils;
import graphtea.ui.actions.UIEventData;
import graphtea.ui.components.*;
import graphtea.ui.components.gmenu.GMenuBar;
import graphtea.ui.components.gmenu.GMenuItem;
import graphtea.ui.components.gmenu.KeyBoardShortCut;
import graphtea.ui.components.gmenu.KeyBoardShortCutProvider;
import graphtea.ui.components.gsidebar.GSidebar;
import graphtea.ui.extension.AbstractExtensionAction;
import graphtea.ui.extension.UIActionExtensionAction;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
/**
* @author Azin Azadi, M. Ali Rostami
*/
public class UIHandlerImpl implements UIHandler, StorableOnExit {
public static final boolean DEBUG = false;
public GToolbar toolbar;
public BlackBoard blackboard;
Class resourceClass;
HashMap<String, graphtea.platform.core.AbstractAction> actions = null;
/**
* determines the character which if put before a character in the string of the label, that character will be set to it's mnemonics
*/
public static final char menueIndexChar = '_';
public UIHandlerImpl(GFrame gFrame, BlackBoard bb
, HashMap<String, graphtea.platform.core.AbstractAction> actions, Class resClass) {
this.blackboard = bb;
this.toolbar = gFrame.getToolbar();
this.sidebar = gFrame.getSidebar();
this.statusbar = gFrame.getStatusbar();
//this.blackboard = gFrame.blackboard;
this.menubar = gFrame.getMenu();
this.frame = gFrame;
this.actions = actions;
this.resourceClass = resClass;
// toolbar.add(lastToolbar);
}
//********** TOOLBARS handling ---------------------
private JToolBar lastToolbar = new JToolBar();
private int lastToolbarPlace;
public void start_toolbar(final Attributes meta) throws SAXException {
// lastToolbar = new JToolBar();
// lastToolbar.setFloatable(false);
lastToolbar = toolbar.createToolBar();
lastToolbarPlace = extractPlace(meta);
if (DEBUG) System.err.println("start_toolbar: " + meta);
}
public void handle_tool(final Attributes meta) throws SAXException {
String label = meta.getValue("label");
String icon = meta.getValue("image");
String action = meta.getValue("action");
String _place = meta.getValue("place");
int place = -1;
if (_place != null)
place = Integer.parseInt(_place);
GButton b;
if (resourceClass != null && icon != null)
System.out.println("[handle_tool]" + icon + " : " + resourceClass.getResource(icon));
if (icon == null || icon.equals("") || resourceClass == null ||
resourceClass.getResource(icon) == null)
b = new GButton(label, icon, blackboard, action);
else
b = new GButton(label, resourceClass.getResource(icon), blackboard, action);
b.setBorderPainted(false);
// b.setBorder(new EmptyBorder(0, 1, 0, 2));
lastToolbar.add(b);
if (DEBUG) System.err.println("handle_tool: " + meta);
}
public void end_toolbar() throws SAXException {
lastToolbar.add(new JSeparator(JSeparator.VERTICAL));
toolbar.addIndexed(lastToolbar, lastToolbarPlace);
// toolbar.add(lastToolbar);
if (DEBUG) System.err.println("end_toolbar()");
}
public void start_toolbars(Attributes meta) throws SAXException {
// lastToolbar = toolbar.getLastToolBar();
}
public void end_toolbars() throws SAXException {
}
//***************** status handling --------------------
public GStatusBar statusbar;
public void handle_bar(Attributes meta) throws SAXException {
String clazz = meta.getValue("class");
String id = meta.getValue("id");
System.out.println("Adding the Bar with id:" + id + " ,class:" + clazz);
Component component = getComponent(clazz);
//we put the component in the blackboad and later (in Actions) we can fetch it if we like, i guess that it is the only way to do the loading of side bar and actions dynamically
UIUtils.setComponent(blackboard, id, component);
statusbar.addComponent(component);
}
//*****************menu handling------------------------
public GMenuBar menubar;
private GFrame frame;
private JMenu currentMenu;
public void start_menues(Attributes meta) throws SAXException {
}
static HashMap<JMenuItem, Integer> places = new HashMap<>();
private Pair<Integer, String> extractLabelInfo(String label) {
int index = Math.max(label.indexOf(menueIndexChar), 0);
label = label.replace(menueIndexChar + "", "");
return new Pair<>(index, label);
}
int lastMenuPlace;
public void start_submenu(final Attributes meta) throws SAXException {
String label = meta.getValue("label");
String accel = meta.getValue("accelerator");
Pair<Integer, String> lInfo = extractLabelInfo(label);
lastMenuPlace = extractPlace(meta);
int index = lInfo.first;
label = lInfo.second;
currentMenu = menubar.getUniqueMenu(label, lastMenuPlace);
// currentMenu = new JMenu(label);
KeyBoardShortCut shortcut = KeyBoardShortCutProvider.registerKeyBoardShortcut(accel, label, index);
if (shortcut != null) {
if (!shortcut.isAccelerator()) {
currentMenu.setMnemonic(shortcut.getKeyMnemonic());
currentMenu.setDisplayedMnemonicIndex(shortcut.getKeyWordIndex());
}
}
if (DEBUG) System.err.println("start_submenu: " + meta);
}
public void handle_menu(final Attributes meta) throws SAXException {
String label = meta.getValue("label");
String action = meta.getValue("action");
String accel = meta.getValue("accelerator");
int place = extractPlace(meta);
if (label.equals("seperator_menu")) {
JSeparator js = new JSeparator(JSeparator.HORIZONTAL);
menubar.insert(currentMenu, js, place);
return;
}
Pair<Integer, String> lInfo = extractLabelInfo(label);
int index = lInfo.first;
label = lInfo.second;
GMenuItem item;
/*
* extension handling part:
* if the menu action was an extension removes the menu
* that the extension created in UI and set it to this menu.
*/
graphtea.platform.core.AbstractAction targetAction = actions.get(action);
if (targetAction instanceof AbstractExtensionAction) {
AbstractExtensionAction targetExt = (AbstractExtensionAction) targetAction;
item = targetExt.menuItem;
/*
* set the label properties according to XML
*/
item.setText(label);
//todo: BUG the mnemotic doesn't set
System.out.println(item + " " + index);
KeyBoardShortCut shortcut = null;
String desc =targetExt.getTarget().getDescription();
if(desc.contains("HotKey:(")) {
String tmp = desc.substring(desc.indexOf("HotKey:(") + 1);
tmp = tmp.substring(0,tmp.indexOf(")"));
shortcut = KeyBoardShortCutProvider.registerKeyBoardShortcut(tmp, label, index);
}
if(shortcut != null) {
item.setAccelerator(KeyStroke.getKeyStroke(shortcut.getKeyEvent(), shortcut.getKeyModifiers()));
item.setDisplayedMnemonicIndex(shortcut.getKeyWordIndex());
item.setMnemonic(shortcut.getKeyMnemonic());
}
} else {
item = new GMenuItem(label, action, blackboard, accel, index);
}
menubar.insert(currentMenu, item, place);
if (DEBUG) System.err.println("handle_menu: " + meta);
}
private int extractPlace(Attributes meta) {
String _place = meta.getValue("place");
int place = -1; //place -1 means no idea given for place,
try {
if (_place != null)
place = Integer.parseInt(_place);
}
catch (NumberFormatException e) {
System.err.println("the place given for menu " + meta.getValue("label") + " is not a valid number:" + _place);
}
return place;
}
public void end_submenu() throws SAXException {
// currentMenu.add(new JSeparator(JSeparator.VERTICAL));
// GMenuBar.insert(currentMenu, new JSeparator(JSeparator.VERTICAL), -1);
//todo: add ability of adding JSeperator to UI
if (DEBUG) System.err.println("end_submenu()");
}
public void end_menues() throws SAXException {
//pak kardan e menu e action e ezafe
GMenuBar menu = frame.getMenu();
for (int i = 0; i < menu.getMenuCount(); i++) {
if (menu.getMenu(i).getText().equals("Operators")
&& menu.getMenu(i).getSubElements().length == 1) {
menu.remove(i);
return;
}
}
}
//***************** action handling ----------------------
// If action has a group, then its default value for enable will be true,
// else it will true if enable property equals to true.
public void handle_action(Attributes meta) throws SAXException {
String clazz = meta.getValue("class");
String id = meta.getValue("id");
String group = meta.getValue("group");
//todo: is it good to remove the action wich loaded twice, (2 of same action are working together)
System.err.println(" Adding action " + clazz + " (" + id + "," + group + ") ...");
Class<?> clazzz = null;
try {
clazzz = Class.forName(clazz);
} catch (ClassNotFoundException e) {
System.err.println("the given class name can't be loaded: " + clazz);
ExceptionHandler.catchException(e);
return;
}
boolean b = false;
graphtea.platform.core.AbstractAction x = loadAbstractAction(clazz);
/*
* handling extensions here
* they are not AbstractAction, but their handlers should
* return an AbstractAction(if it support the extension)
* and after that the program know the
* extension by that AbstractAction
*/
// if (clazzz.isAssignableFrom(Extension.class)) {
// for (ExtensionHandler s : ExtensionLoader.getRegisteredExtensionHandlers()) {
if (x == null) {
Object e = ExtensionLoader.loadExtension(clazzz);
// SETTINGS.registerSetting(e,"Extention Options"); //Moved to Extension Loader
if (e != null)
x = ExtensionLoader.handleExtension(blackboard, e);
}
// x = s.handle(blackboard, clazzz);
// b = b | x != null;
if (x != null) {
if (id == null) {
id = x.getLastListenedEventKey();
id = id.replaceFirst(UIEventData.name(""), "");
}
if (x instanceof UIActionExtensionAction) {
UIActionExtensionAction action = (UIActionExtensionAction) x;
action.setUIEvent(id);
}
addAction(id, x, group);
// }
// }
// }
// if (b) {
// return;
}
if (x == null) {
System.err.println("Error while loading " + clazz + ". skipped.");
StaticUtils.addExceptiontoLog(new Throwable("Error while loading " + clazz + ". skipped."), blackboard);
return; //error, skip it.
}
addAction(id, x, group);
}
private void addAction(String id, graphtea.platform.core.AbstractAction x, String group) {
if ((id != null) && !id.equals(""))
actions.put(id, x);
// if (group != null && !group.equals("")) {
// //configuration age group vojood nadashte bashe khodesh ijadesh mikone. inja be ghole omid "error prone hast"
// //todo: bara hamin be zehnam resid ke biaim bebinim esme gorooha masalan age kamtar az 2harf ekhtelaf daran, pas ehtemalan eshtebahe typi boode , ie jooraii kashf konim eroro :D
// conf.addToGroup(group, x);
// }
}
//****************** side bar handling -------------------------
public GSidebar sidebar;
public void start_sidebar(Attributes meta) throws SAXException {
}
public void handle_sidebar(Attributes meta) throws SAXException {
String image = meta.getValue("image") + "";//to getting it not null
String clazz = meta.getValue("class");
String id = meta.getValue("id");
String label = meta.getValue("label");
Component component = getComponent(clazz);
UIUtils.setComponent(blackboard, id, component);
if (resourceClass == null || resourceClass.getResource(image) == null) {
sidebar.addButton(image, component, label);
} else
sidebar.addButton(resourceClass.getResource(image), component, label);
}
public void end_sidebar() throws SAXException {
}
//************* body handling ------------------------------------
public void handle_body(Attributes meta) throws SAXException {
String clazz = meta.getValue("class");
String id = meta.getValue("id");
Component gci = getComponent(clazz);
frame.getBody().setBodyPane(gci);
UIUtils.setComponent(blackboard, id, gci);
}
//************** utilities +++++++++++++++++++++
AbstractAction loadAbstractAction(String abstractActionclazz) {
String clazz = abstractActionclazz;
if (!(clazz == null) && !(clazz.equals(""))) {
Class t = clazz2Class(clazz);
if (graphtea.platform.core.AbstractAction.class.isAssignableFrom(t)) {
Object[] o = {blackboard};
try {
Constructor c = t.getConstructor(BlackBoard.class);
Object o1 = c.newInstance(o);
return (graphtea.platform.core.AbstractAction) o1;
}
catch (Exception e) {
// System.err.println("Error while loading " + clazz);
ExceptionHandler.catchException(e);
}
}
}
// System.err.println("Error while loading " + clazz);
return null;
}
//todo: it is possible to also get a component from xml by it's direct class name, like javax.swing.JLabel . but i decided not to do it for cleaner codes! i am not sure is it good or not?
Component getComponent(String GComponentInterfaceClassName) {
String clazz = GComponentInterfaceClassName;
if (!(clazz == null) && !(clazz.equals(""))) {
Class t = clazz2Class(clazz);
Constructor c = null;
Object[] o = {blackboard};
try {
c = t.getConstructor(BlackBoard.class);
} catch (NoSuchMethodException e) {
try {
c = t.getConstructor(new Class[]{});
o = new Object[]{};
} catch (NoSuchMethodException e1) {
System.err.println("the clazz " + clazz + "does not have a constructor(blackboard) or constructor(), how can i load it?");
e1.printStackTrace();
}
// ExceptionHandler.catchException(e);
}
//if it had a constructor(blackboard)
try {
Object o1 = c.newInstance(o);
if (o1 instanceof GComponentInterface) {
//load was successfull
return ((GComponentInterface) o1).getComponent(blackboard);
} else {
System.err.println("the class " + clazz + " doesn't implement the interface GComponentInterface, so it can't be put on the UI.");
}
} catch (InstantiationException | IllegalAccessException e) {
System.err.println("There was an error while initializing the class" + clazz + "may be in it's constructor or in one of classes it instantiate in its constructor");
ExceptionHandler.catchException(e);
} catch (InvocationTargetException e) {
System.err.println("There was an error while initializing the class" + clazz + "may be in it's constructor or in one of classes it instantiate in its constructor");
e.getTargetException().printStackTrace();
}
}
return null;
}
private Class clazz2Class(String clazz) {
Class t = null;
try {
t = Class.forName(clazz);
} catch (ClassNotFoundException e) {
System.err.println("the Class" + clazz + "didn't found in class path");
ExceptionHandler.catchException(e);
}
return t;
}
}