package vg.modules.desktop;
import java.awt.BorderLayout;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import vg.core.AUserInterfaceElement;
import vg.core.IGraphView;
import vg.core.VisualGraph;
import vg.core.event.AUIEvent;
import vg.core.event.UIEventChangeView;
import vg.core.event.UIEventCloseTab;
import vg.core.event.UIEventCreateNewConnection;
import vg.core.event.UIEventDeleteConnection;
import vg.core.event.UIEventOpenNewGraph;
import vg.core.event.UIEventOpenNewTab;
import vg.core.plugin.PluginParameter;
import vg.core.request.AUIRequest;
import vg.core.request.EUIRequestType;
import vg.core.request.IUIRequestOwner;
import vg.core.request.UIRequestCloseTab;
import vg.core.request.UIRequestOpenSubGraph;
import vg.core.request.UIRequestReplaceCurrentTab;
import vg.core.request.UIRequestSelectTab;
import vg.core.storableGraph.StorableSubGraph;
import vg.userInterface.core.GraphView;
import vg.userInterface.swingComponents.SimpleTabWithCloseButton;
/**
* This class realizes desktop.
* @author tzolotuhin
*/
public class DesktopPanel extends AUserInterfaceElement {
private static AtomicInteger counterId = new AtomicInteger(0);
// Components
private JPanel view;
private JTabbedPane tabs;
// Main data
private final List<Connection> connections;
private final PluginParameter parameter;
private final Map<Integer, String> comTabIdAndTabTitle;
private Map<Integer, Integer> comTabIdAndTab;
private final Map<Integer, IGraphView> comTabIdAndGraphView;
// Additional data
private final Map<String, ArrayList<Integer>> tabTitleStatistic;//statistic of titles
// Mutex
private final Object theMutexObject;
/**
* Constructor.
*/
public DesktopPanel(final PluginParameter param) {
super("Desktop", null);
// init mutex
this.theMutexObject = new Object();
// init main data
this.parameter = param;
this.connections = new ArrayList<Connection>();
this.comTabIdAndTabTitle = new HashMap<Integer, String>();
this.comTabIdAndTab = new HashMap<Integer, Integer>();
this.comTabIdAndGraphView = new HashMap<Integer, IGraphView>();
// init additional data
this.tabTitleStatistic = new HashMap<String, ArrayList<Integer>>();
// init components
if(SwingUtilities.isEventDispatchThread()) {
init();
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
synchronized (theMutexObject) {
init();
}
}
});
}
}
/**
* This method returns component which represent desktop.
*/
public void update(Observable o, Object arg) {
synchronized (this.theMutexObject) {
if(arg instanceof AUIEvent) {
AUIEvent event = (AUIEvent)arg;
switch(event.getType()) {
case DEF_OPEN_NEW_GRAPH:
{
UIEventOpenNewGraph bufEvent = (UIEventOpenNewGraph)event;
Integer graphId = bufEvent.getGraphId();
Date d = new Date();
StorableSubGraph ssg = this.parameter.model.getRootStorableSubGraph(graphId);
VisualGraph.log.printInfo("[" + this.getClass().getName() + ".update] Root subGraph select time: " + (new Date().getTime() - d.getTime()) / 1000.0 + "sec");
if(ssg != null) {
IGraphView graphView = GraphView.newGraphView(ssg, DesktopPanel.this.parameter.userInterface);
addTab(graphView.getTitle(), graphView);
} else {
VisualGraph.log.printError("[" + this.getClass().getName() + ".update] Desktop. Openning of root subgraph(DEF_OPEN_NEW_GRAPH). GraphId = " + graphId.toString());
}
break;
}
case DEF_CREATE_NEW_CONNECT:
{
UIEventCreateNewConnection bufEvent = (UIEventCreateNewConnection)event;
addNewConnect(bufEvent.getConnectionId(), bufEvent.getConnectionName(), bufEvent.getGraphId(), bufEvent.getAnchor());
break;
}
case DEF_DELETE_CONNECT:
{
UIEventDeleteConnection bufEvent = (UIEventDeleteConnection)event;
removeConnect(bufEvent.getConnectId());
break;
}
case DEF_CHANGE_UI_STYLE:
{
updateUITheme();
break;
}
case DEF_RESET:
{
this.connections.clear();
this.comTabIdAndTabTitle.clear();
this.comTabIdAndTab.clear();
this.comTabIdAndGraphView.clear();
this.tabTitleStatistic.clear();
this.tabs.removeAll();
break;
}
}
} else if(arg instanceof AUIRequest) {
AUIRequest request = (AUIRequest)arg;
switch(request.getType()) {
case DEF_CLOSE_CURRENT_TAB:
{
// call request owner method (PROCESS REQUEST)
IUIRequestOwner bufOwner = request.getOwner();
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
}
// execute query
int index = this.tabs.getSelectedIndex();
if(index >= 0) {
for(Integer buf : this.comTabIdAndTab.keySet()) {
Integer value = this.comTabIdAndTab.get(buf);
if(value != null && value.equals(index)) {
closeTab(buf);
break;
}
}
}
// call request owner method (OK REQUEST)
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
}
break;
}
case DEF_CLOSE_TAB: {
// call request owner method (PROCESS REQUEST)
IUIRequestOwner bufOwner = request.getOwner();
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
}
// execute query
UIRequestCloseTab req = (UIRequestCloseTab)request;
Set<Integer>tabSet = req.getTabSet();
if(tabSet != null) {
for(Integer buf : tabSet) {
closeTab(buf);
}
}
// call request owner method (OK REQUEST)
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
}
break;
}
case DEF_OPEN_SUB_GRAPH:
{
// call request owner method (PROCESS REQUEST)
IUIRequestOwner bufOwner = request.getOwner();
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
}
// execute query
UIRequestOpenSubGraph req = (UIRequestOpenSubGraph)request;
final IGraphView igv = req.getSubGraphView();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if(igv != null) {
addTab(igv.getTitle(), igv);
}
}
});
// call request owner method (OK REQUEST)
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
}
break;
}
case DEF_SELECT_TAB:
{
// call request owner method (PROCESS REQUEST)
IUIRequestOwner bufOwner = request.getOwner();
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
}
// execute query
UIRequestSelectTab req = (UIRequestSelectTab)request;
final Integer tabId = req.getTabId();
// find tab
SwingUtilities.invokeLater(new Runnable() {
public void run() {
tabs.setSelectedIndex(DesktopPanel.this.comTabIdAndTab.get(tabId));
}
});
// call request owner method (OK REQUEST)
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
}
break;
}
case DEF_REPLACE_CURRENT_TAB:
{
// call request owner method (PROCESS REQUEST)
IUIRequestOwner bufOwner = request.getOwner();
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
}
// execute query
UIRequestReplaceCurrentTab req = (UIRequestReplaceCurrentTab)request;
int index = this.tabs.getSelectedIndex();
if(index >= 0) {
for(Integer buf : this.comTabIdAndTab.keySet()) {
Integer value = this.comTabIdAndTab.get(buf);
if(value != null && value.equals(index)) {
closeTab(buf);
IGraphView igv = req.getSubGraphView();
if(igv != null) {
addTab(igv.getTitle(), igv);
}
break;
}
}
}
// call request owner method (OK REQUEST)
if(bufOwner != null) {
bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
}
break;
}
}
}
}
};
public JComponent getView() {
return(this.view);
}
///////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////////////////
/**
* Init swing components.
*/
private void init() {
this.view = new JPanel(new BorderLayout());
this.tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
this.view.add(this.tabs, BorderLayout.CENTER);
this.tabs.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
int index = tabs.getSelectedIndex();
if(index >= 0) {
for(Integer buf : comTabIdAndTab.keySet()) {
if((int)comTabIdAndTab.get(buf) == index) {
UIEventChangeView event = new UIEventChangeView(buf, comTabIdAndGraphView.get(buf));
parameter.userInterface.addEvent(event);
break;
}
}
} else {
//if all tabs close
UIEventChangeView event = new UIEventChangeView(-1, null);
parameter.userInterface.addEvent(event);
}
}
});
// ctrl-tab and ctrl-shift-tab
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_TAB && e.isControlDown()) {
int tabcount = tabs.getTabCount();
if (tabcount > 0) {
int sel = tabs.getSelectedIndex();
if (e.isShiftDown()) {
sel = sel - 1;
if (sel < 0)
sel = tabcount - 1;
} else {
sel = sel + 1;
if (sel >= tabcount)
sel = 0;
}
tabs.setSelectedIndex(sel);
}
return(true);
}
return(false);
}
});
}
/**
* This method adds new connect to each graph view.
*/
private void addNewConnect(final int connectId, final String connectName, final int graphId, final String anchor) {
for(IGraphView buf : this.comTabIdAndGraphView.values()) {
buf.addNewConnect(connectId, connectName, anchor, graphId);
}
this.connections.add(new Connection(connectId, connectName, graphId, anchor));
}
/**
* This method removes exist connect from each graph view.
*/
private void removeConnect(final int connectId) {
for(IGraphView buf : this.comTabIdAndGraphView.values()) {
buf.removeConnect(connectId);
}
for(Connection buf : this.connections) {
if(buf.getConnectId() == connectId) {
this.connections.remove(buf);
break;
}
}
}
/**
* This method adds new tab on the desktop.
* @param title - title of the tab.
* @param graphView - view of graph, which we want open in new tab.
*/
private void addTab(final String title, final IGraphView graphView) {
String localTitle = title;
ArrayList<Integer>array = DesktopPanel.this.tabTitleStatistic.get(title);
String renamingTitle = null;
int renamingId = -1;
if(array == null) {
array = new ArrayList<Integer>();
array.add(0);
DesktopPanel.this.tabTitleStatistic.put(title, array);
} else {
if(array.size() == 1 && array.get(0) == 0) {
// renaming of existing tab
array.remove(0);
for(int i = 0 ; i < this.tabs.getTabCount(); i++) {
if(this.tabs.getTitleAt(i).equals(title)) {
renamingTitle = buildTabTitle(title, 1);
renamingId = i;
break;
}
}
array.add(1);
}
// find free index
int index = 1; // without zero
for(int i = 0 ; i < array.size() ; i++) {
if(!array.contains(index)) {
break;
}
index++;
}
array.add(index);
localTitle = buildTabTitle(localTitle, index);
}
// add connections
for(Connection buf : DesktopPanel.this.connections) {
graphView.addNewConnect(buf.getConnectId(), buf.getConnectName(), buf.getAnchor(), buf.getGraphId());
}
// add information about new tab to data
int tabId = getNextId(); // get new id
this.comTabIdAndGraphView.put(tabId, graphView);
this.comTabIdAndTab.put(tabId, this.tabs.getTabCount());
this.comTabIdAndTabTitle.put(tabId, title);
// send event
UIEventOpenNewTab event = new UIEventOpenNewTab(tabId, localTitle, graphView);
this.parameter.userInterface.addEvent(event);
// update ui and add new component
if(SwingUtilities.isEventDispatchThread()) {
localAddTab(renamingTitle, renamingId, localTitle, graphView.getGraphRepresentation());
} else {
final String finalTitle = localTitle;
final int finalRenamingId = renamingId;
final String finalRenamingTitle = renamingTitle;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
synchronized (theMutexObject) {
localAddTab(finalRenamingTitle, finalRenamingId, finalTitle, graphView.getGraphRepresentation());
}
}
});
}
}
private void localAddTab(String renamingTitle, int renamingId, String title, JComponent graphRepresentation) {
if(renamingTitle != null) {
this.tabs.setTitleAt(renamingId, renamingTitle);
}
this.tabs.addTab(title, graphRepresentation);
// add close button
this.tabs.setTabComponentAt(DesktopPanel.this.tabs.getTabCount() - 1, new SimpleTabWithCloseButton(tabs, new ActionListener() {
public void actionPerformed(ActionEvent e) {
synchronized (theMutexObject) {
int index = tabs.indexOfTabComponent((SimpleTabWithCloseButton)e.getSource());
if(index >= 0) {
for(Integer buf : comTabIdAndTab.keySet()) {
Integer value = comTabIdAndTab.get(buf);
if(value != null && value.equals(index)) {
closeTab(buf);
break;
}
}
}
}
}
}));
this.tabs.setSelectedIndex(this.tabs.getTabCount() - 1);
}
/**
* This method closes tab.
* @param id - id of tab.
*/
private void closeTab(int id) {
final int index = comTabIdAndTab.get(id);
if(index < 0) return;
String shortFileName = this.comTabIdAndTabTitle.get(id);
String tabTitle = this.tabs.getTitleAt(index);
// clear tab statistic
ArrayList<Integer>array = this.tabTitleStatistic.get(shortFileName);
if(array != null) {
for(Integer bufInner : array) {
String title = buildTabTitle(shortFileName, bufInner);
if(title.equals(tabTitle)) {
array.remove(bufInner);
break;
}
}
if(array.size() == 0) {
this.tabTitleStatistic.remove(shortFileName);
}
}
// clear compositions
this.comTabIdAndTabTitle.remove(id);
this.comTabIdAndGraphView.remove(id);
this.comTabIdAndTab.remove(id);
// change next tab index
Map<Integer, Integer>bufMap = new HashMap<Integer, Integer>();
for(Integer bufInner : DesktopPanel.this.comTabIdAndTab.keySet()) {
Integer val = DesktopPanel.this.comTabIdAndTab.get(bufInner);
if((int)val > index) {
bufMap.put(bufInner, val - 1);
} else {
bufMap.put(bufInner, val);
}
}
this.comTabIdAndTab = bufMap;
// create and send event
if(this.parameter != null && this.parameter.userInterface != null) {
UIEventCloseTab event = new UIEventCloseTab(id);
this.parameter.userInterface.addEvent(event);
}
// update ui
if(SwingUtilities.isEventDispatchThread()) {
this.tabs.remove(index);
this.view.updateUI();
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
synchronized (theMutexObject) {
tabs.remove(index);
view.updateUI();
}
}
});
}
}
private void updateUITheme() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
synchronized (DesktopPanel.this.theMutexObject) {
SwingUtilities.updateComponentTreeUI(DesktopPanel.this.view);
}
}
});
}
private String buildTabTitle(final String title, final int number) {
if(number <= 0) return(title);
return(title + ":" + number);
}
/**
* This method returns next id for tab.
*/
private int getNextId() {
int a = counterId.incrementAndGet();
return(a);
}
///////////////////////////////////////////////////////////////////////////
// PRIVATE CLASSES
///////////////////////////////////////////////////////////////////////////
/**
* Bean, witch contains information about connection of graph and AIF.
*/
private static class Connection {
private final int connectId;
private final String connectName;
private final String anchor;
private final int graphId;
/**
* Constructor.
*/
public Connection(final int connectId, final String connectName, final int graphId, final String anchor) {
this.connectId = connectId;
this.connectName = connectName;
this.anchor = anchor;
this.graphId = graphId;
}
public String getAnchor() {
return(this.anchor);
}
public int getConnectId() {
return(this.connectId);
}
public String getConnectName() {
return(this.connectName);
}
public int getGraphId() {
return(this.graphId);
}
}
}