package com.vitco.layout.content.layer;
import com.jidesoft.action.CommandMenuBar;
import com.vitco.core.data.Data;
import com.vitco.core.data.notification.DataChangeAdapter;
import com.vitco.layout.content.JCustomScrollPane;
import com.vitco.layout.content.JCustomTable;
import com.vitco.layout.content.ViewPrototype;
import com.vitco.manager.action.types.StateActionPrototype;
import com.vitco.manager.async.AsyncAction;
import com.vitco.manager.async.AsyncActionManager;
import com.vitco.manager.pref.PrefChangeListener;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.misc.SwingAsyncHelper;
import org.springframework.beans.factory.annotation.Autowired;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
/**
* Build the layer menu with options.
*/
public class LayerView extends ViewPrototype implements LayerViewInterface {
// the currently selected layer
private int selectedLayer = -1;
// layer buffer
private Integer[] layers = new Integer[]{};
private String[] layerNames = new String[]{};
private int layerCount = 0;
private Integer[] layerVoxelCounts = new Integer[]{};
private Boolean[] layerVisibilities = new Boolean[]{};
// true if editing was canceled
private boolean cancelEdit = false;
// this instance
private final LayerView thisInstance = this;
protected AsyncActionManager asyncActionManager;
// set the action handler
@Autowired
public final void setAsyncActionManager(AsyncActionManager asyncActionManager) {
this.asyncActionManager = asyncActionManager;
}
// layout of cells
private class TableRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column
) {
boolean isSelectedLayer;
boolean isVisible;
synchronized (thisInstance) {
isSelectedLayer = layers[row] == selectedLayer;
isVisible = layerVisibilities[row];
}
// set the correct background (selected/unselected/visible/invisible)
if (isSelectedLayer) {
setFont(VitcoSettings.TABLE_FONT_BOLD); // set font
if (isVisible) {
setBackground(VitcoSettings.VISIBLE_SELECTED_LAYER_BG);
} else {
setBackground(VitcoSettings.HIDDEN_SELECTED_LAYER_BG);
}
} else {
setFont(VitcoSettings.TABLE_FONT); // set font
if (isVisible) {
setBackground(VitcoSettings.VISIBLE_LAYER_BG);
} else {
setBackground(VitcoSettings.HIDDEN_LAYER_BG);
}
}
setForeground(VitcoSettings.DEFAULT_TEXT_COLOR); // set text color
setBorder(VitcoSettings.DEFAULT_CELL_BORDER); // padding
setValue(value.toString()); // set the value
return this;
}
}
// construct the table data input
private class LayerTableModel extends AbstractTableModel {
public int getColumnCount() {
return 2;
}
public int getRowCount() {
synchronized (thisInstance) {
return layerCount;
}
}
// column names
public String getColumnName(int col) {
switch (col) {
case 0:
return langSelector.getString("layer-window_layer-name");
default:
return langSelector.getString("layer-window_layer-visible");
}
}
// display value
public Object getValueAt(int row, int col) {
synchronized (thisInstance) {
switch (col) {
case 0:
return layerNames[row] + " (" + layerVoxelCounts[row] + ")";
default:
return layerVisibilities[row];
}
}
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
// only the name is editable
public boolean isCellEditable(int row, int col) {
return col == 0;
}
public final void setValueAt(final Object value, final int row, final int col) {
if (!cancelEdit) {
switch (col) {
case 0:
// this needs to be done asynchronously,
// b/c it could trigger a UI refresh
final int layer;
synchronized (thisInstance) {
layer = layers[row];
}
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
data.renameLayer(layer, (String) value);
}
});
break;
default: break;
}
fireTableCellUpdated(row, col);
} else {
cancelEdit = false;
}
}
}
private final CellEditor cellEditor = new CellEditor();
private class CellEditor extends AbstractCellEditor implements TableCellEditor {
// handles the editing of the cell value
SaveTextArea component;
@Override
public boolean isCellEditable( EventObject e ) {
// only edit with double-click
return !(e instanceof MouseEvent) || ((MouseEvent) e).getClickCount() >= 2;
}
// start editing
public Component getTableCellEditorComponent(final JTable table, Object value,
boolean isSelected, final int rowIndex, final int vColIndex) {
synchronized (thisInstance) {
component = new SaveTextArea(layerNames[rowIndex]);
}
component.setBorder(VitcoSettings.DEFAULT_CELL_BORDER_EDIT); // border
component.setFont(VitcoSettings.TABLE_FONT_BOLD); // font
component.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent e) {
if (e.getKeyCode() == 10) { // apply changes (return key)
// needs to be done asynchronously
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
finishCellEditing(table);
}
});
}
}
@Override
public void keyReleased(final KeyEvent e) {
e.consume(); // prevent further use of this keystroke
}
});
component.setBackground(VitcoSettings.EDIT_BG_COLOR); // bg color when edit
component.setForeground(VitcoSettings.EDIT_TEXT_COLOR); // set text color when edit
return component;
}
// edit complete
// Important: can not rely on this to fire (resize bug!)
public Object getCellEditorValue() {
return component.getText(); // return new value
}
}
// var & setter
protected Data data;
@Autowired
public final void setData(Data data) {
this.data = data;
}
protected void finishCellEditing(final JTable table) {
if (table.isEditing()) {
// save the value
final Component editField = table.getEditorComponent();
if (editField != null) {
// needs to be done async, b/c it could trigger a ui refresh
final int layer;
synchronized (thisInstance) {
layer = layers[table.getEditingRow()];
}
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
data.renameLayer(layer, ((JTextArea)editField).getText());
}
});
}
// cancel all further saving of edits
cancelEdit = true;
TableCellEditor tce = table.getCellEditor();
if (tce != null) {
tce.stopCellEditing();
}
cancelEdit = false;
}
}
// true if we are in animation mode, false if in voxel mode
private boolean isAnimationMode = false;
@Override
public final JPanel build() {
JPanel result = new JPanel();
result.setLayout(new BorderLayout());
// create the table
final JCustomTable table = new JCustomTable(new LayerTableModel());
// custom layout for cells
TableRenderer tableRenderer = new TableRenderer();
table.getColumnModel().getColumn(0).setCellRenderer(tableRenderer);
table.getColumnModel().getColumn(1).setCellRenderer(tableRenderer);
// update now
synchronized (thisInstance) {
synchronized (VitcoSettings.SYNC) {
selectedLayer = data.getSelectedLayer();
layers = data.getLayers();
layerCount = layers.length;
layerNames = data.getLayerNames();
layerVoxelCounts = new Integer[layerNames.length];
layerVisibilities = new Boolean[layerNames.length];
for (int i = 0; i < layers.length; i++) {
layerVoxelCounts[i] = data.getVoxelCount(layers[i]);
layerVisibilities[i] = data.getLayerVisible(layers[i]);
}
}
}
// update table when data changes
data.addDataChangeListener(new DataChangeAdapter() {
private void refresh(final int msDelay) {
synchronized (thisInstance) {
synchronized (VitcoSettings.SYNC) {
selectedLayer = data.getSelectedLayer();
layers = data.getLayers();
layerCount = layers.length;
layerNames = data.getLayerNames();
layerVoxelCounts = new Integer[layerNames.length];
layerVisibilities = new Boolean[layerNames.length];
for (int i = 0; i < layers.length; i++) {
layerVoxelCounts[i] = data.getVoxelCount(layers[i]);
layerVisibilities[i] = data.getLayerVisible(layers[i]);
}
}
}
// refresh this group
actionGroupManager.refreshGroup("voxel_layer_interaction");
// refresh table
asyncActionManager.addAsyncAction(new AsyncAction("RenderLayerViewTable") {
private final long time = System.currentTimeMillis();
@Override
public void performAction() {
SwingAsyncHelper.handle(new Runnable() {
@Override
public void run() {
table.revalidate();
table.repaint();
}
}, errorHandler);
}
@Override
public boolean ready() {
return System.currentTimeMillis() - msDelay > time;
}
});
}
@Override
public void onVoxelDataChanged() {
refresh(200);
}
@Override
public void onLayerStateChanged() {
refresh(10);
}
});
// change visibility/selection of layer
table.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
JTable aTable = (JTable)e.getSource();
final int row = aTable.rowAtPoint(e.getPoint());
if (row > -1) { // verify that a row was clicked
final int col = aTable.columnAtPoint(e.getPoint());
final int layer;
synchronized (thisInstance) {
layer = layers[row];
}
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
// if (e.getClickCount() == 1) { // select layer
// data.selectLayerSoft(layer);
// } else if (col == 1 && e.getClickCount() > 1 && e.getClickCount()%2 == 0) { // toggle visibility
// data.setVisible(layer, !data.getLayerVisible(layer));
// }
if (col == 0) {
data.selectLayerSoft(layer); // select layer
} else {
data.setVisible(layer, !data.getLayerVisible(layer)); // toggle visibility
}
// cancel editing if we are editing
if (e.getClickCount() == 1 && table.isEditing()) {
finishCellEditing(table);
}
}
});
}
}
});
// register editing
table.getColumnModel().getColumn(0).setCellEditor(cellEditor);
// container for table
JCustomScrollPane pane = new JCustomScrollPane(table);
pane.setBorder(BorderFactory.createMatteBorder(1,1,0,1,VitcoSettings.DEFAULT_BORDER_COLOR));
result.add(pane, BorderLayout.CENTER);
// create the menu bar
CommandMenuBar menuPanel = new CommandMenuBar();
menuGenerator.buildMenuFromXML(menuPanel, "com/vitco/layout/content/layer/toolbar.xml");
menuPanel.setBorder(BorderFactory.createMatteBorder(0,1,1,1,VitcoSettings.DEFAULT_BORDER_COLOR));
result.add(menuPanel, BorderLayout.SOUTH);
// register change of mode (disable buttons to make history consistent)
preferences.addPrefChangeListener("is_animation_mode_active", new PrefChangeListener() {
@Override
public void onPrefChange(Object o) {
isAnimationMode = (Boolean)o;
actionGroupManager.refreshGroup("voxel_layer_interaction");
}
});
// register the menu actions
actionGroupManager.addAction("voxel_layer_interaction", "layer-frame_add-layer", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
if (getStatus()) {
finishCellEditing(table);
data.selectLayer(data.createLayer("Layer"));
}
}
@Override
public boolean getStatus() {
return data.getLayers().length < VitcoSettings.MAX_LAYER_COUNT && !isAnimationMode;
}
});
actionGroupManager.addAction("voxel_layer_interaction", "layer-frame_remove-layer", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
finishCellEditing(table);
data.deleteLayer(data.getSelectedLayer());
}
@Override
public boolean getStatus() {
return data.getSelectedLayer() != -1 && !isAnimationMode;
}
});
actionGroupManager.addAction("voxel_layer_interaction", "layer-frame_move-layer-up", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
finishCellEditing(table);
data.moveLayerUp(data.getSelectedLayer());
}
@Override
public boolean getStatus() {
return data.canMoveLayerUp(data.getSelectedLayer()) && !isAnimationMode;
}
});
actionGroupManager.addAction("voxel_layer_interaction", "layer-frame_move-layer-down", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
finishCellEditing(table);
data.moveLayerDown(data.getSelectedLayer());
}
@Override
public boolean getStatus() {
return data.canMoveLayerDown(data.getSelectedLayer()) && !isAnimationMode;
}
});
actionGroupManager.addAction("voxel_layer_interaction", "layer-frame_layer-merge", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
finishCellEditing(table);
data.mergeVisibleLayers();
}
@Override
public boolean getStatus() {
return data.canMergeVisibleLayers() && !isAnimationMode;
}
});
actionGroupManager.registerGroup("voxel_layer_interaction");
return result;
}
}