/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.studio;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLDatastoreUndoable;
import com.opendoorlogistics.api.tables.ODLListener;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.codefromweb.DropDownMenuButton;
import com.opendoorlogistics.core.api.impl.scripts.ScriptTemplatesImpl;
import com.opendoorlogistics.core.components.ODLGlobalComponents;
import com.opendoorlogistics.core.scripts.execution.ExecutionReportImpl;
import com.opendoorlogistics.core.scripts.execution.adapters.vls.Layer;
import com.opendoorlogistics.core.scripts.execution.adapters.vls.Style;
import com.opendoorlogistics.core.scripts.execution.adapters.vls.View;
import com.opendoorlogistics.core.tables.decorators.tables.SimpleTableReadOnlyDecorator;
import com.opendoorlogistics.core.tables.utils.DatastoreCopier;
import com.opendoorlogistics.core.tables.utils.TableUtils;
import com.opendoorlogistics.core.utils.iterators.IteratorUtils;
import com.opendoorlogistics.core.utils.strings.Strings;
import com.opendoorlogistics.core.utils.strings.Strings.DoesStringExist;
import com.opendoorlogistics.core.utils.ui.ExecutionReportDialog;
import com.opendoorlogistics.core.utils.ui.PopupMenuMouseAdapter;
import com.opendoorlogistics.studio.appframe.AbstractAppFrame;
import com.opendoorlogistics.studio.appframe.AppBackground;
import com.opendoorlogistics.utils.ui.Icons;
import com.opendoorlogistics.utils.ui.ODLAction;
import com.opendoorlogistics.utils.ui.SimpleAction;
final public class DatastoreTablesPanel extends JPanel implements ODLListener {
private ODLDatastoreUndoable<? extends ODLTableAlterable> ds;
private final JList<ODLTableReadOnly> list;
private final AbstractAppFrame appFrame;
private final List<MyAction> actions;
private final List<ODLAction> wizardActions;
private final JLabel tablesLabel;
private final boolean useBackgroundImage;
// public DatastoreTablesPanel() {
// this(null);
// }
private abstract class MyAction extends SimpleAction {
public MyAction(String name, String tooltip, String smallIconPng) {
super(name, tooltip, smallIconPng);
}
@Override
public void updateEnabledState() {
setEnabled(ds != null);
}
public boolean addToToolbar(){
return true;
}
}
private abstract class MyNeedsSelectedAction extends MyAction {
public MyNeedsSelectedAction(String name, String tooltip, String smallIconPng) {
super(name, tooltip, smallIconPng);
}
@Override
public void updateEnabledState() {
setEnabled(ds != null && list.getSelectedValue() != null && list.getSelectedValuesList().size() == 1);
}
}
// private class LaunchScriptWizardAction extends MyNeedsSelectedAction{
// private final ScriptType type;
// public LaunchScriptWizardAction(String name, String tooltip,ScriptType
// type) {
// super(name, tooltip, IconsByScriptType.getFilename(type));
// this.type = type;
// }
//
// @Override
// public void actionPerformed(ActionEvent e) {
// if(list.getSelectedValue()!=null){
// appFrame.launchSingleTableScriptWizard(list.getSelectedValue().getImmutableId(),
// type);
// }
// }
// }
private class LaunchScriptWizardActionV2 extends ODLAction {
private final ODLComponent component;
public LaunchScriptWizardActionV2(ODLComponent component) {
super(component.getName(), component.getName(), component.getIcon(appFrame.getApi(), ODLComponent.MODE_DEFAULT));
this.component = component;
}
@Override
public void actionPerformed(ActionEvent e) {
List<ODLTableReadOnly> selected = list.getSelectedValuesList();
int[] ids = new int[selected.size()];
for (int i = 0; i < ids.length; i++) {
ids[i] = selected.get(i).getImmutableId();
}
appFrame.launchScriptWizard(ids, component);
}
@Override
public void updateEnabledState() {
// setEnabled(minNbTables == 0 || (ds!=null && hasSingleTableConfig
// && list.getSelectedValue()!=null));
setEnabled(true);
}
}
/**
* Create the panel.
*/
public DatastoreTablesPanel(AbstractAppFrame launcher) {
this.appFrame = launcher;
setLayout(new BorderLayout(0, 0));
list = new JList<ODLTableReadOnly>();
list.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
list.setModel(new DefaultListModel<ODLTableReadOnly>());
list.setCellRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(
JList<?> list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
Component ret = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
// ListModel<? extends ODLTableReadOnly> model = DatastoreTablesPanel.this.list.getModel();
// if(model!=null && index < model.getSize()){
// ODLTableReadOnly table = model.getElementAt(index);
// if(table!=null && table.getRowCount()>0){
// long flags = table.getRowFlags(table.getRowId(0));
// if((flags & TableFlags.FLAG_LINKED_EXCEL_READ_ONLY_DATA) == TableFlags.FLAG_LINKED_EXCEL_READ_ONLY_DATA){
// // as the linked data always appears at the top of the table, this table has read only data
// ret.setFont(ret.getFont().deriveFont(Font.ITALIC));
// }
// }
// }
return ret;
}
});
DefaultListSelectionModel selectionModel = new DefaultListSelectionModel() {
@Override
public void setSelectionInterval(int index0, int index1) {
super.setSelectionInterval(index0, index1);
}
};
selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
list.setSelectionModel(selectionModel);
// put list in a scrollpane
JScrollPane listScrollPane = new JScrollPane();
listScrollPane.setViewportView(list);
setLayout(new BorderLayout());
// get properties to see if we should use a background image
Boolean tmpBool = appFrame.getApi().properties().getBool("app.tablespanel.usebackgroundimage");
if(tmpBool!=null && tmpBool){
useBackgroundImage=true;
}else{
useBackgroundImage = false;
}
if(useBackgroundImage){
// put in a further panel so we can draw a custom background
JPanel listPanel = new JPanel(new BorderLayout()){
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
AppBackground.paintBackground(this, g, appFrame.getBackgroundImage());
}
};
listScrollPane.setOpaque(false);
listScrollPane.getViewport().setOpaque(false);
listPanel.add(listScrollPane, BorderLayout.CENTER);
add(listPanel, BorderLayout.CENTER);
}else{
add(listScrollPane, BorderLayout.CENTER);
}
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
add(toolBar, BorderLayout.SOUTH);
// create right-click popup menu on the list
final JPopupMenu popup = new JPopupMenu();
list.addMouseListener(new PopupMenuMouseAdapter() {
@Override
protected void launchMenu(MouseEvent me) {
//select the item under the mouse
list.setSelectedIndex(list.locationToIndex(me.getPoint()));
// launch the popup menu
popup.show(me.getComponent(), me.getX(), me.getY());
}
});
list.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
updateAppearance();
}
});
// create double click event on list
list.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() >= 2) {
// ensure the item is selected (may have been deselected by the toggle)
list.setSelectedIndex(list.locationToIndex(evt.getPoint()));
launchSelectedTable();
}
}
});
// create all actions and add as buttons and menu items
actions = createActions();
for (MyAction action : actions) {
if(action.addToToolbar()){
toolBar.add(action);
}
popup.add(action);
}
// add wizard for single table script types
if(appFrame.getAppPermissions().isScriptEditingAllowed()){
wizardActions = createLaunchScriptWizardActions();
final JPopupMenu wizardsPopupMenu = new JPopupMenu();
JMenu wizardsMenu = new JMenu("Component wizard...");
for (ODLAction action : wizardActions) {
wizardsPopupMenu.add(action);
wizardsMenu.add(action);
}
DropDownMenuButton wizardsMenuButton = new DropDownMenuButton(Icons.loadFromStandardPath("tools-wizard-2.png")) {
@Override
protected JPopupMenu getPopupMenu() {
return wizardsPopupMenu;
}
};
wizardsMenuButton.setToolTipText("Run the component wizard");
toolBar.add(wizardsMenuButton);
popup.add(wizardsMenu);
}else{
wizardActions = null;
}
tablesLabel = new JLabel("Tables");
tablesLabel.setHorizontalAlignment(SwingConstants.CENTER);
add(tablesLabel, BorderLayout.NORTH);
updateAppearance();
}
private List<ODLAction> createLaunchScriptWizardActions() {
ArrayList<ODLAction> ret = new ArrayList<>();
for (ODLComponent component : ODLGlobalComponents.getProvider()) {
if (component.getIcon(appFrame.getApi(), ODLComponent.MODE_DEFAULT) != null && IteratorUtils.size(ScriptTemplatesImpl.getTemplates(appFrame.getApi(), component)) > 0) {
ret.add(new LaunchScriptWizardActionV2(component));
}
}
return ret;
}
private List<MyAction> createActions() {
ArrayList<MyAction> ret = new ArrayList<>();
ret.add(new MyAction("Add table", "Add table", "table-add.png") {
@Override
public void actionPerformed(ActionEvent e) {
String newName = "New table";
if(ds!=null){
newName = Strings.makeUnique(newName, new DoesStringExist() {
@Override
public boolean isExisting(String s) {
return TableUtils.findTable(ds, s)!=null;
}
});
};
String name = getTableNameFromDialog(newName);
if (name != null) {
class Result{
ODLTableDefinition table;
}
Result result = new Result();
if(modifyDs(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
result.table = ds.createTable(name, -1);
return true;
}
})){
appFrame.launchTableGrid(result.table.getImmutableId());
};
}
}
});
ret.add(new MyNeedsSelectedAction("Delete table", "Delete table", "table-delete-2.png") {
@Override
public void actionPerformed(ActionEvent e) {
ODLTableDefinition table = getSelectedShowErrorIfNone();
if (table != null) {
modifyDs(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
ds.deleteTableById(table.getImmutableId());
return true;
}
});
}
}
});
ret.add(new MyNeedsSelectedAction("Rename table", "Rename table", "table-rename.png") {
@Override
public void actionPerformed(ActionEvent e) {
ODLTableDefinition table = getSelectedShowErrorIfNone();
if (table != null) {
String name = getTableNameFromDialog(table.getName());
if (name != null) {
modifyDs(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return ds.setTableName(table.getImmutableId(), name);
}
});
}
}
}
});
ret.add(new MyNeedsSelectedAction("Edit table schema", "Edit table schema", "table-edit.png") {
@Override
public void actionPerformed(ActionEvent e) {
ODLTableDefinition table = getSelectedShowErrorIfNone();
if (table != null) {
appFrame.launchTableSchemaEditor(table.getImmutableId());
}
}
});
ret.add(new MyNeedsSelectedAction("Copy table", "Copy table", "table-copy.png") {
@Override
public void actionPerformed(ActionEvent e) {
ODLTableDefinition table = getSelectedShowErrorIfNone();
if (table != null) {
String name = getTableNameFromDialog("Copy of " + table.getName());
if (name != null) {
class Result{
ODLTableReadOnly copy ;
}
Result result = new Result();
if(modifyDs(new Callable<Boolean>(){
@Override
public Boolean call() throws Exception {
result.copy = DatastoreCopier.copyTableIntoSameDatastore(ds, table.getImmutableId(), name);
return true;
}
})){
appFrame.launchTableGrid(result.copy.getImmutableId());
}
}
}
}
});
ret.add(new MyNeedsSelectedAction("Go to table", "Go to table", "table-go.png") {
@Override
public void actionPerformed(ActionEvent e) {
launchSelectedTable();
}
});
ret.add(new MyAction("Select all tables", "Select all tables", "select-all-tables.png") {
@Override
public void actionPerformed(ActionEvent e) {
list.getSelectionModel().setSelectionInterval(0, list.getModel().getSize()-1);
}
@Override
public boolean addToToolbar(){
return false;
}
});
ret.add(new MyNeedsSelectedAction("Unselect all tables", "Unselect all tables", "unselect-all-tables.png") {
@Override
public void actionPerformed(ActionEvent e) {
list.clearSelection();
}
@Override
public boolean addToToolbar(){
return false;
}
});
// ret.add(new LaunchScriptWizardAction("Build table view",
// "Build a view of the table", ScriptType.ADAPTED_TABLE));
// ret.add(new LaunchScriptWizardAction("Show map",
// "Launch the show map wizard",ScriptType.SHOW_MAP));
return ret;
}
@Override
public void tableChanged(int tableId, int firstRow, int lastRow) {
throw new UnsupportedOperationException();
}
@Override
public void datastoreStructureChanged() {
ODLDatastoreUndoable<? extends ODLTableAlterable> currentDs = ds;
onDatastoreClosed();
setDatastore(currentDs);
}
@Override
public ODLListenerType getType() {
return ODLListenerType.DATASTORE_STRUCTURE_CHANGED;
}
public void setDatastore(ODLDatastoreUndoable<? extends ODLTableAlterable> ds) {
if (this.ds != null) {
throw new RuntimeException();
}
this.ds = ds;
this.ds.addListener(this);
List<ODLTableReadOnly> tables = getSortedTables(ds);
// update list
list.setModel(new ListModel<ODLTableReadOnly>() {
@Override
public int getSize() {
return tables.size();
}
@Override
public ODLTableReadOnly getElementAt(int index) {
return new SimpleTableReadOnlyDecorator(tables.get(index)) {
@Override
public String toString() {
return getName();
}
};
}
@Override
public void addListDataListener(ListDataListener l) {
// TODO Auto-generated method stub
}
@Override
public void removeListDataListener(ListDataListener l) {
// TODO Auto-generated method stub
}
});
updateAppearance();
}
/**
* Sort tables, view-layer-style first and then any other table
* @param ds
* @return
*/
private List<ODLTableReadOnly> getSortedTables(ODLDatastoreAlterable<? extends ODLTableAlterable> ds) {
// Get sorted table names
List<ODLTableReadOnly> tables = new ArrayList<>(ds.getTableCount());
for(int i =0 ; i < ds.getTableCount() ; i++){
tables.add(ds.getTableAt(i));
}
Collections.sort(tables,new Comparator<ODLTableReadOnly>(){
final String [] specials = new String[]{View.TABLE_NAME,Layer.TABLE_NAME,Style.TABLE_NAME};
@Override
public int compare(ODLTableReadOnly o1, ODLTableReadOnly o2) {
int i1 = getNameScore(o1);
int i2 = getNameScore(o2);
if(i1!=i2){
return Integer.compare(i1, i2);
}
return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
}
private int getNameScore(ODLTableDefinition o1) {
ODLTableDefinition t = o1;
int i =0 ;
while(i < specials.length){
if(Strings.equals(specials[i], t.getName())){
break;
}
i++;
}
return i;
}
});
return tables;
}
public void onDatastoreClosed() {
if (ds != null) {
ds.removeListener(this);
ds = null;
}
list.setModel(new DefaultListModel<ODLTableReadOnly>());
updateAppearance();
}
private void launchSelectedTable() {
if (list.getSelectedValue() == null) {
return;
}
DatastoreTablesPanel.this.appFrame.launchTableGrid(list.getSelectedValue().getImmutableId());
}
// public interface UILauncher {
// JComponent launchTableGrid(int tableId);
// void launchSingleTableScriptWizard(int tableId, final ScriptType type);
//
// }
private void updateAppearance() {
for (MyAction action : actions) {
action.updateEnabledState();
}
if(wizardActions!=null){
for (ODLAction action : wizardActions) {
action.updateEnabledState();
}
}
list.setEnabled(ds != null);
// wizardsMenuButton.setEnabled(ds!=null);
if (ds == null) {
// set list to grey
list.setBackground(new Color(220, 220, 220));
if(useBackgroundImage){
list.setOpaque(false);
}
} else {
list.setBackground(Color.WHITE);
if(useBackgroundImage){
list.setOpaque(true);
}
}
tablesLabel.setEnabled(ds != null);
}
private ODLTableDefinition getSelectedShowErrorIfNone() {
ODLTableDefinition ret = list.getSelectedValue();
if (ret == null) {
JOptionPane.showMessageDialog(this.getParent(), "No table is selected");
}
return ret;
}
private String getTableNameFromDialog(String current) {
String s = JOptionPane.showInputDialog(this.getParent(), "Enter new table name", current);
return s;
}
private void showTableNameError() {
JOptionPane.showMessageDialog(DatastoreTablesPanel.this, "Could not perform action. Is name already used?");
}
private boolean modifyDs(Callable<Boolean> callable){
ExecutionReportImpl report = new ExecutionReportImpl();
if(!TableUtils.runTransaction(ds, callable,report)){
report.setFailed("An error occurred when attempting to modify the datastore.");
new ExecutionReportDialog((JFrame)SwingUtilities.getWindowAncestor(this), "Error", report, false).setVisible(true);
return false;
}
return true;
}
}