/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2011 Dmitry Barashev
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.sourceforge.ganttproject;
import biz.ganttproject.core.model.task.TaskDefaultColumn;
import biz.ganttproject.core.option.ValidationException;
import biz.ganttproject.core.table.ColumnList;
import biz.ganttproject.core.table.ColumnList.Column;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import net.sourceforge.ganttproject.action.GPAction;
import net.sourceforge.ganttproject.chart.Chart;
import net.sourceforge.ganttproject.chart.TimelineChart;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.gui.UIUtil;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.language.GanttLanguage.Event;
import net.sourceforge.ganttproject.task.CustomColumn;
import net.sourceforge.ganttproject.task.CustomPropertyEvent;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.event.TaskDependencyEvent;
import net.sourceforge.ganttproject.task.event.TaskHierarchyEvent;
import net.sourceforge.ganttproject.task.event.TaskListener;
import net.sourceforge.ganttproject.task.event.TaskListenerAdapter;
import net.sourceforge.ganttproject.task.event.TaskScheduleEvent;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.table.NumberEditorExt;
import org.jdesktop.swingx.table.TableColumnExt;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public abstract class GPTreeTableBase extends JXTreeTable implements CustomPropertyListener {
private final IGanttProject myProject;
private final UIFacade myUiFacade;
private final TableHeaderUiFacadeImpl myTableHeaderFacade = new TableHeaderUiFacadeImpl();
private final CustomPropertyManager myCustomPropertyManager;
private final JScrollPane myScrollPane = new JScrollPane() {
@Override
public void applyComponentOrientation(ComponentOrientation o) {
super.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
}
};
private GPAction myEditCellAction = new GPAction("tree.edit") {
@Override
public void actionPerformed(ActionEvent e) {
JTable t = getTable();
if (t.getSelectedRow() < 0) {
return;
}
if (t.getSelectedColumn() < 0) {
t.getColumnModel().getSelectionModel().setSelectionInterval(0, 0);
}
editCellAt(t.getSelectedRow(), t.getSelectedColumn());
}
};
private final Runnable myUpdateUiCommand = new Runnable() {
@Override
public void run() {
updateUI();
}
};
@Override
public Component prepareEditor(TableCellEditor editor, int row, int column) {
Component result = super.prepareEditor(editor, row, column);
if (result instanceof JTextComponent) {
Object textFieldFont = UIManager.get("TextField.font");
if (textFieldFont instanceof Font) {
result.setFont((Font) textFieldFont);
}
if (Boolean.TRUE == getClientProperty("GPTreeTableBase.clearText")) {
((JTextComponent) result).setText("");
}
if (Boolean.TRUE == getClientProperty("GPTreeTableBase.selectAll")) {
SwingUtilities.invokeLater(TreeTableCellEditorImpl.createSelectAllCommand((JTextComponent) result));
}
if (Boolean.TRUE == getClientProperty("GPTreeTableBase.unselectAll")) {
SwingUtilities.invokeLater(TreeTableCellEditorImpl.createUnselectAllCommand((JTextComponent) result));
putClientProperty("GPTreeTableBase.unselectAll", false);
}
}
return result;
}
@Override
public void editingCanceled(ChangeEvent e) {
super.editingCanceled(e);
SwingUtilities.invokeLater(myUpdateUiCommand);
}
@Override
public void editingStopped(ChangeEvent arg0) {
super.editingStopped(arg0);
SwingUtilities.invokeLater(myUpdateUiCommand);
}
protected class TableHeaderUiFacadeImpl implements ColumnList {
private final List<Column> myDefaultColumnStubs = new ArrayList<>();
private final List<ColumnImpl> myColumns = new ArrayList<>();
TableHeaderUiFacadeImpl() {
GanttLanguage.getInstance().addListener(new GanttLanguage.Listener() {
@Override
public void languageChanged(Event event) {
for (ColumnImpl column : myColumns) {
column.setName(column.getName());
}
}
});
}
private List<ColumnImpl> getColumns() {
return Collections.unmodifiableList(myColumns);
}
@Override
public int getSize() {
return myColumns.size();
}
@Override
public Column getField(int index) {
return myColumns.get(index);
}
@Override
public void clear() {
clearUiColumns();
myColumns.clear();
for (int i = 0; i < myDefaultColumnStubs.size(); i++) {
Column stub = myDefaultColumnStubs.get(i);
ColumnImpl column = createColumn(i, stub);
if (stub.isVisible()) {
// keep some columns visible in the table when creating a new project
// otherwise table appears without any columns at all and as a side effect,
// chart timeline may change its height
insertColumnIntoUi(column);
}
}
}
private void clearUiColumns() {
List<TableColumn> columns = Collections.list(getTable().getColumnModel().getColumns());
for (int i = 0; i < columns.size(); i++) {
getTable().removeColumn(columns.get(i));
}
}
@Override
public void add(String id, int order, int width) {
ColumnImpl column = findColumnByID(id);
if (column == null) {
CustomPropertyDefinition def = myCustomPropertyManager.getCustomPropertyDefinition(id);
if (def == null) {
return;
}
if (order == -1) {
order = getTable().getColumnCount();
}
if (width == -1) {
width = 75;
}
ColumnStub columnStub = new ColumnList.ColumnStub(id, def.getName(), true, order, width);
column = createColumn(getSize(), columnStub);
}
if (column == null) {
return;
}
insertColumnIntoUi(column);
}
private boolean importColumnList(ColumnList columns) {
boolean anyVisible = false;
for (int i = 0; i < columns.getSize(); i++) {
Column foreign = columns.getField(i);
ColumnImpl mine = findColumnByID(foreign.getID());
if (mine == null) {
int modelIndex = getModelIndex(foreign);
if (modelIndex >= 0) {
mine = createColumn(modelIndex, foreign);
}
} else {
mine.getStub().setOrder(foreign.getOrder());
mine.getStub().setVisible(foreign.isVisible());
mine.getStub().setWidth(foreign.getWidth());
anyVisible = foreign.isVisible();
}
}
return anyVisible;
}
@Override
public void importData(ColumnList source) {
for (ColumnImpl column : myColumns) {
column.getStub().setVisible(false);
}
if (!importColumnList(source)) {
importColumnList(ColumnList.Immutable.fromList(myDefaultColumnStubs));
}
Collections.sort(myColumns, new Comparator<ColumnImpl>() {
@Override
public int compare(ColumnImpl left, ColumnImpl right) {
int test1 = (left.getStub().isVisible() ? -1 : 0) + (right.getStub().isVisible() ? 1 : 0);
if (test1 != 0) {
return test1;
}
if (!left.getStub().isVisible() && !right.getStub().isVisible()) {
return left.getName().compareTo(right.getName());
}
return left.getStub().getOrder() - right.getStub().getOrder();
}
});
clearUiColumns();
for (ColumnImpl column : myColumns) {
if (column.getStub().isVisible()) {
insertColumnIntoUi(column);
}
}
}
private int getModelIndex(Column c) {
for (int i = 0; i < myDefaultColumnStubs.size(); i++) {
if (c.getID().equals(myDefaultColumnStubs.get(i).getID())) {
return i;
}
}
List<CustomPropertyDefinition> definitions = myCustomPropertyManager.getDefinitions();
for (int i = 0; i < definitions.size(); i++) {
if (definitions.get(i).getID().equals(c.getID())) {
return myDefaultColumnStubs.size() + i;
}
}
return -1;
}
void createDefaultColumns(List<ColumnList.Column> stubs) {
myDefaultColumnStubs.clear();
for (Column stub : stubs) {
myDefaultColumnStubs.add(new ColumnList.ColumnStub(stub.getID(), stub.getName(), stub.isVisible(),
stub.getOrder(), stub.getWidth()));
}
}
ColumnImpl createColumn(int modelIndex, ColumnList.Column stub) {
TableColumnExt tableColumn = newTableColumnExt(modelIndex);
tableColumn.setPreferredWidth(stub.getWidth());
tableColumn.setIdentifier(stub.getID());
ColumnImpl result = new ColumnImpl(getTreeTable(), tableColumn, stub);
myColumns.add(result);
return result;
}
void insertColumnIntoUi(ColumnImpl column) {
getTable().addColumn(column.myTableColumn);
column.setWidth(column.getStub().getWidth());
}
void renameColumn(CustomPropertyDefinition definition) {
ColumnImpl c = findColumnByID(definition.getID());
if (c == null) {
return;
}
c.setName(definition.getName());
}
void updateType(CustomPropertyDefinition def) {
ColumnImpl c = findColumnByID(def.getID());
if (c == null) {
return;
}
c.getTableColumnExt().setCellRenderer(createCellRenderer(def.getType()));
c.getTableColumnExt().setCellEditor(createCellEditor(def.getType()));
}
void deleteColumn(CustomPropertyDefinition definition) {
ColumnImpl c = findColumnByID(definition.getID());
if (c == null) {
return;
}
getTable().removeColumn(c.myTableColumn);
myColumns.remove(c);
for (ColumnImpl column : myColumns) {
if (column.myTableColumn.getModelIndex() > c.myTableColumn.getModelIndex()) {
column.myTableColumn.setModelIndex(column.myTableColumn.getModelIndex() - 1);
}
}
}
ColumnImpl findColumnByID(String id) {
for (ColumnImpl c : myColumns) {
if (c.getID().equals(id)) {
return c;
}
}
return null;
}
ColumnImpl findColumnByViewIndex(int index) {
for (ColumnImpl c : myColumns) {
if (c.getOrder() == index) {
return c;
}
}
return null;
}
}
protected static class ColumnImpl implements ColumnList.Column {
private final JXTreeTable myTable;
private final TableColumnExt myTableColumn;
private final Column myStub;
private SortOrder mySort = SortOrder.UNSORTED;
ColumnImpl(JXTreeTable table, TableColumnExt tableColumn, ColumnList.Column stub) {
myTable = table;
myTableColumn = tableColumn;
myStub = stub;
}
public SortOrder getSort() {
return mySort;
}
public void setSort(SortOrder sort) {
mySort = sort;
}
private TreeTableModel getTableModel() {
return myTable.getTreeTableModel();
}
@Override
public String getID() {
return myStub.getID();
}
@Override
public String getName() {
return getTableModel().getColumnName(myTableColumn.getModelIndex());
}
private void setName(String name) {
myTableColumn.setTitle(name);
}
@Override
public int getOrder() {
return myTable.convertColumnIndexToView(myTableColumn.getModelIndex());
}
@Override
public int getWidth() {
return myTableColumn.getWidth();
}
@Override
public boolean isVisible() {
return getOrder() >= 0;
}
@Override
public void setVisible(boolean visible) {
if (visible && !isVisible()) {
myTable.addColumn(myTableColumn);
} else if (!visible && isVisible()) {
myTable.getColumnModel().removeColumn(myTableColumn);
}
}
@Override
public void setWidth(int width) {
myTableColumn.setWidth(width);
myTableColumn.setPreferredWidth(width);
}
Column getStub() {
return myStub;
}
TableColumnExt getTableColumnExt() {
return myTableColumn;
}
@Override
public void setOrder(int order) {
}
Dimension getHeaderFitDimension() {
return UIUtil.getHeaderDimension(myTable, myTableColumn);
}
}
protected IGanttProject getProject() {
return myProject;
}
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE && !isEditing()) {
return false;
}
if (e.getID() != KeyEvent.KEY_PRESSED) {
putClientProperty("GPTreeTableBase.clearText", false);
putClientProperty("GPTreeTableBase.selectAll", false);
return super.processKeyBinding(ks, e, condition, pressed);
}
if (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
if (e.getKeyCode() == KeyEvent.VK_META || e.getKeyCode() == 0) {
return false;
}
putClientProperty("GPTreeTableBase.selectAll", true);
putClientProperty("GPTreeTableBase.clearText", false);
return super.processKeyBinding(ks, e, condition, pressed);
}
if (e.isMetaDown() || e.isControlDown()) {
putClientProperty("GPTreeTableBase.selectAll", true);
putClientProperty("GPTreeTableBase.clearText", false);
return super.processKeyBinding(ks, e, condition, pressed);
}
putClientProperty("GPTreeTableBase.clearText", true);
putClientProperty("GPTreeTableBase.selectAll", false);
if (UIManager.getLookAndFeel().getName().toLowerCase().replace(" ", "").indexOf("macosx") >= 0) {
putClientProperty("GPTreeTableBase.unselectAll", true);
}
return super.processKeyBinding(ks, e, condition, pressed);
}
@Override
public void applyComponentOrientation(ComponentOrientation o) {
super.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
}
@Override
public String getToolTipText(MouseEvent e) {
try {
return super.getToolTipText(e);
} catch (NullPointerException ex) {
return null;
}
}
protected GPTreeTableBase(IGanttProject project, UIFacade uiFacade, CustomPropertyManager customPropertyManager,
DefaultTreeTableModel model) {
super(model);
setTableHeader(new JTableHeader(getColumnModel()) {
@Override
public void applyComponentOrientation(ComponentOrientation o) {
super.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
}
});
myCustomPropertyManager = customPropertyManager;
myUiFacade = uiFacade;
myProject = project;
myProject.addProjectEventListener(new ProjectEventListener.Stub() {
@Override
public void projectClosed() {
getTableHeaderUiFacade().clear();
}
@Override
public void projectOpened() {
onProjectOpened();
}
@Override
public void projectCreated() {
onProjectCreated();
}
});
setAutoStartEditOnKeyStroke(true);
setSurrendersFocusOnKeystroke(true);
}
protected void createDefaultEditors() {
super.createDefaultEditors();
defaultEditorsByColumnClass.put(Object.class, new GenericEditor(){
@Override
public boolean stopCellEditing() {
try {
return super.stopCellEditing();
} catch (ValidationException e) {
getComponent().setBackground(UIUtil.INVALID_VALUE_BACKGROUND);
return false;
}
}
});
defaultEditorsByColumnClass.put(Number.class, new NumberEditorExt(true) {
@Override
public boolean stopCellEditing() {
try {
return super.stopCellEditing();
} catch (ValidationException e) {
getComponent().setBackground(UIUtil.INVALID_VALUE_BACKGROUND);
return false;
}
}
});
}
protected void onProjectOpened() {
}
protected void onProjectCreated() {
getTableHeaderUiFacade().createDefaultColumns(getDefaultColumns());
getTableHeaderUiFacade().importData(ColumnList.Immutable.fromList(getDefaultColumns()));
}
void initTreeTable() {
doInit();
}
private static class SortTableHeaderRenderer implements TableCellRenderer {
private final Icon myAscIcon;
private final Icon myDescIcon;
private final Function<Integer, ColumnImpl> myColumnByViewIndex;
private final TableCellRenderer myDefaultRenderer;
SortTableHeaderRenderer(JTable table, Function<Integer, ColumnImpl> columnByViewIndex) {
myAscIcon = UIManager.getIcon("Table.ascendingSortIcon");
myDescIcon = UIManager.getIcon("Table.descendingSortIcon");
myColumnByViewIndex = Preconditions.checkNotNull(columnByViewIndex);
myDefaultRenderer = Preconditions.checkNotNull(table.getTableHeader().getDefaultRenderer());
}
@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
Component c = myDefaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
if (c instanceof JLabel == false) {
return c;
}
ColumnImpl column = myColumnByViewIndex.apply(col);
if (column == null) {
return c;
}
JLabel label = (JLabel) c;
if (column.getSort() == SortOrder.ASCENDING) {
label.setIcon(myAscIcon);
}
if (column.getSort() == SortOrder.DESCENDING) {
label.setIcon(myDescIcon);
}
return label;
}
}
private static <T>Comparator<T> reverseComparator(final Comparator<T> comparator) {
return new Comparator<T>() {
@Override
public int compare(T t1, T t2) {
return -comparator.compare(t1, t2);
}
};
}
private final TaskListener myRemoveOrderListener = new TaskListenerAdapter() {
private void clearOrdering() {
for (ColumnImpl c : myTableHeaderFacade.getColumns()) {
c.setSort(SortOrder.UNSORTED);
}
}
@Override
public void dependencyAdded(TaskDependencyEvent e) {
clearOrdering();
}
@Override
public void dependencyRemoved(TaskDependencyEvent e) {
clearOrdering();
}
@Override
public void dependencyChanged(TaskDependencyEvent e) {
clearOrdering();
}
@Override
public void taskScheduleChanged(TaskScheduleEvent e) {
clearOrdering();
}
@Override
public void taskAdded(TaskHierarchyEvent e) {
clearOrdering();
}
@Override
public void taskRemoved(TaskHierarchyEvent e) {
clearOrdering();
}
@Override
public void taskMoved(TaskHierarchyEvent e) {
clearOrdering();
}
@Override
public void taskModelReset() {
clearOrdering();
}
};
protected void doInit() {
setRootVisible(false);
myCustomPropertyManager.addListener(this);
getTableHeader().setDefaultRenderer(new SortTableHeaderRenderer(this, new Function<Integer, ColumnImpl>() {
@Override
public ColumnImpl apply(@Nullable Integer idxColumn) {
return myTableHeaderFacade.findColumnByViewIndex(idxColumn);
}
}));
getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
int index = getTable().columnAtPoint(mouseEvent.getPoint());
if (index == -1) return;
ColumnImpl column = myTableHeaderFacade.findColumnByViewIndex(index);
TaskDefaultColumn taskColumn = TaskDefaultColumn.find(column.getID());
if (taskColumn == TaskDefaultColumn.BEGIN_DATE || taskColumn == TaskDefaultColumn.END_DATE) {
for (ColumnImpl c : myTableHeaderFacade.getColumns()) {
if (c != column) {
c.setSort(SortOrder.UNSORTED);
}
}
if (column.getSort() == SortOrder.ASCENDING) {
column.setSort(SortOrder.DESCENDING);
myProject.getTaskManager().getTaskHierarchy().sort(
reverseComparator((Comparator<Task>) taskColumn.getSortComparator())
);
} else {
column.setSort(SortOrder.ASCENDING);
myProject.getTaskManager().getTaskHierarchy().sort((Comparator<Task>) taskColumn.getSortComparator());
}
}
}
});
myProject.getTaskManager().addTaskListener(myRemoveOrderListener);
getTable().getTableHeader().addMouseListener(new HeaderMouseListener(myCustomPropertyManager));
getTable().getColumnModel().addColumnModelListener(new TableColumnModelListener() {
@Override
public void columnMoved(TableColumnModelEvent e) {
if (e.getFromIndex() != e.getToIndex()) {
myProject.setModified();
}
}
@Override
public void columnAdded(TableColumnModelEvent e) {
myProject.setModified();
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
myProject.setModified();
}
@Override
public void columnMarginChanged(ChangeEvent e) {
myProject.setModified();
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
getTable().setAutoCreateColumnsFromModel(false);
getTable().setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
ImageIcon icon = new ImageIcon(getClass().getResource("/icons/simple_task.gif"));
setOpenIcon(icon);
setClosedIcon(icon);
setCollapsedIcon(new ImageIcon(getClass().getResource("/icons/plus.gif")));
setExpandedIcon(new ImageIcon(getClass().getResource("/icons/minus.gif")));
setLeafIcon(icon);
addActionWithAccelleratorKey(myEditCellAction);
setHighlighters(UIUtil.ZEBRA_HIGHLIGHTER);
getTable().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
onCellSelectionChanged();
}
});
getTable().getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
onCellSelectionChanged();
}
});
getTree().addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent arg0) {
getChart().reset();
}
@Override
public void treeCollapsed(TreeExpansionEvent arg0) {
getChart().reset();
}
});
getTableHeaderUiFacade().importData(ColumnList.Immutable.fromList(getDefaultColumns()));
// getScrollPane().setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
getTable().setFillsViewportHeight(true);
}
private void onCellSelectionChanged() {
if (!getTable().isEditing()) {
int row = getTable().getSelectedRow();
int col = getTable().getSelectedColumn();
Rectangle rect = getTable().getCellRect(row, col, true);
getScrollPane().scrollRectToVisible(rect);
}
}
private void addNewCustomColumn(CustomColumn customColumn) {
ColumnList.Column stub = new ColumnList.ColumnStub(customColumn.getId(), customColumn.getName(),
false, getTable().getColumnCount(), 100);
getTableHeaderUiFacade().createColumn(getTable().getModel().getColumnCount() - 1, stub);
}
private void deleteCustomColumn(CustomColumn column) {
getTableHeaderUiFacade().deleteColumn(column);
}
@Override
public void customPropertyChange(CustomPropertyEvent event) {
switch (event.getType()) {
case CustomPropertyEvent.EVENT_ADD:
addNewCustomColumn((CustomColumn) event.getDefinition());
break;
case CustomPropertyEvent.EVENT_REMOVE:
deleteCustomColumn((CustomColumn) event.getDefinition());
break;
case CustomPropertyEvent.EVENT_NAME_CHANGE:
getTableHeaderUiFacade().renameColumn(event.getDefinition());
getTable().getTableHeader().repaint();
break;
case CustomPropertyEvent.EVENT_TYPE_CHANGE:
getTableHeaderUiFacade().updateType(event.getDefinition());
getTable().repaint();
}
}
public ColumnList getVisibleFields() {
return getTableHeaderUiFacade();
}
TableHeaderUiFacadeImpl getTableHeaderUiFacade() {
return myTableHeaderFacade;
}
protected List<ColumnList.Column> getDefaultColumns() {
return Collections.emptyList();
}
protected abstract Chart getChart();
protected TableColumnExt newTableColumnExt(int modelIndex) {
TableColumnExt result = new TableColumnExt(modelIndex);
Class<?> columnClass = getTreeTableModel().getColumnClass(modelIndex);
TableCellRenderer renderer = createCellRenderer(columnClass);
if (renderer != null) {
result.setCellRenderer(renderer);
}
TableCellEditor editor = createCellEditor(columnClass);
if (editor != null) {
result.setCellEditor(editor);
} else {
System.err.println("no editor for column=" + modelIndex + " class=" + columnClass);
}
return result;
}
private TableCellRenderer createCellRenderer(Class<?> columnClass) {
// TODO(dbarashev): make sure that icon and boolean values render fine
// if (Icon.class.equals(columnClass) || Boolean.class.equals(columnClass))
// {
// renderer = TableCellRenderers.getNewDefaultRenderer(columnClass);
//
// }
return getTreeTable().getDefaultRenderer(columnClass);
}
private TableCellEditor createCellEditor(Class<?> columnClass) {
TableCellEditor editor = columnClass.equals(GregorianCalendar.class) ? UIUtil.newDateCellEditor(myProject, false)
: getTreeTable().getDefaultEditor(columnClass);
return editor == null ? null : wrapEditor(editor);
}
private TableCellEditor wrapEditor(TableCellEditor editor) {
return new TreeTableCellEditorImpl((DefaultCellEditor) editor, getTable());
}
public JXTreeTable getTree() {
return this;
}
public JXTreeTable getTreeTable() {
return this;
}
public JTable getTable() {
return this;
}
JScrollBar getVerticalScrollBar() {
return getScrollPane().getVerticalScrollBar();
}
JScrollBar getHorizontalScrollBar() {
return getScrollPane().createHorizontalScrollBar();
}
JScrollPane getScrollPane() {
return myScrollPane;
}
@Override
public void addMouseListener(MouseListener mouseListener) {
super.addMouseListener(mouseListener);
// this.getTreeTable().getParent().addMouseListener(mouseListener);
}
@Override
public void addKeyListener(KeyListener keyListener) {
super.addKeyListener(keyListener);
// getTable().addKeyListener(keyListener);
// getTree().addKeyListener(keyListener);
}
protected class VscrollAdjustmentListener implements AdjustmentListener, TimelineChart.VScrollController {
private final boolean isMod;
private final TimelineChart myChart;
VscrollAdjustmentListener(TimelineChart chart, boolean calculateMod) {
isMod = calculateMod;
myChart = chart;
}
protected TimelineChart getChart() {
return myChart;
}
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if (getChart() == null) {
return;
}
if (isMod) {
getChart().getModel().setVerticalOffset(e.getValue() % getTreeTable().getRowHeight());
} else {
getChart().getModel().setVerticalOffset(e.getValue());
}
getChart().reset();
}
@Override
public boolean isScrollable() {
return getVerticalScrollBar().getMaximum() - getVerticalScrollBar().getMinimum() > 0;
}
@Override
public void scrollBy(int pixels) {
getVerticalScrollBar().setValue(getVerticalScrollBar().getValue() + pixels);
}
}
void insertWithLeftyScrollBar(JComponent container) {
getScrollPane().getViewport().add(getTable());
container.add(getScrollPane(), BorderLayout.CENTER);
}
/** Adds keyStroke to the given action (if action is null nothing happens) */
private void addAction(Action action, KeyStroke keyStroke) {
if (action != null) {
InputMap inputMap = getInputMap();
inputMap.put(keyStroke, action.getValue(Action.NAME));
getActionMap().put(action.getValue(Action.NAME), action);
}
}
/** Adds an action to the object and makes it active */
void addActionWithAccelleratorKey(GPAction action) {
if (action != null) {
for (KeyStroke ks : GPAction.getAllKeyStrokes(action.getID())) {
addAction(action, ks);
}
}
}
void setupActionMaps(GPAction... actions) {
for (GPAction action : actions) {
addActionWithAccelleratorKey(action);
}
}
private class HeaderMouseListener extends MouseAdapter {
private final CustomPropertyManager myCustomPropertyManager;
private final LinkedList<Column> myRecentlyHiddenColumns = new LinkedList<>();
HeaderMouseListener(CustomPropertyManager customPropertyManager) {
super();
myCustomPropertyManager = customPropertyManager;
}
/**
* @inheritDoc Shows the popupMenu to hide/show columns and to add custom
* columns.
*/
@Override
public void mousePressed(MouseEvent e) {
handlePopupTrigger(e);
}
@Override
public void mouseReleased(MouseEvent e) {
handlePopupTrigger(e);
}
private void handlePopupTrigger(MouseEvent e) {
if (e.isPopupTrigger()) {
Collection<Action> actions = createPopupActions(e);
myUiFacade.showPopupMenu(e.getComponent(), actions, e.getX(), e.getY());
}
}
private Collection<Action> createPopupActions(final MouseEvent mouseEvent) {
List<Action> result = new ArrayList<>();
final int columnAtPoint = getTable().columnAtPoint(mouseEvent.getPoint());
final ColumnImpl column = getTableHeaderUiFacade().findColumnByViewIndex(columnAtPoint);
{
result.add(new GPAction("columns.manage.label") {
@Override
public void actionPerformed(ActionEvent e) {
ShowHideColumnsDialog dialog = new ShowHideColumnsDialog(myUiFacade, myTableHeaderFacade,
myCustomPropertyManager);
dialog.show();
}
});
}
{
GPAction fitAction = new GPAction("columns.fit.label") {
@Override
public void actionPerformed(ActionEvent e) {
autoFitColumnWidth(column);
}
};
result.add(fitAction);
fitAction.putValue(Action.NAME, GanttLanguage.getInstance().formatText("columns.fit.label", column.getName()));
}
result.add(null);
{
GPAction hideAction = new GPAction("columns.hide.label") {
@Override
public void actionPerformed(ActionEvent arg0) {
assert column.isVisible() : "how come it is at mouse click point?";
column.setVisible(false);
myRecentlyHiddenColumns.add(column);
}
};
if (columnAtPoint == -1) {
hideAction.setEnabled(false);
} else {
hideAction.putValue(Action.NAME,
GanttLanguage.getInstance().formatText("columns.hide.label", column.getName()));
}
result.add(hideAction);
}
if (!myRecentlyHiddenColumns.isEmpty()) {
List<GPAction> showActions = new ArrayList<>();
for (ListIterator<Column> it = myRecentlyHiddenColumns.listIterator(myRecentlyHiddenColumns.size()); it.hasPrevious();) {
final Column hidden = it.previous();
GPAction action = new GPAction("columns.show.label") {
@Override
public void actionPerformed(ActionEvent arg0) {
hidden.setVisible(true);
myRecentlyHiddenColumns.remove(hidden);
}
};
action.putValue(Action.NAME, GanttLanguage.getInstance().formatText("columns.show.label", hidden.getName()));
showActions.add(action);
if (showActions.size() == 5) {
break;
}
}
result.addAll(showActions);
}
return result;
}
}
void autoFitColumns() {
int visibleWidth = 0;
int headerHeight = 0;
for (ColumnImpl column : getTableHeaderUiFacade().getColumns()) {
if (column.isVisible()) {
Dimension columnDimension = autoFitColumnWidth(column);
visibleWidth += columnDimension.width;
headerHeight = Math.max(headerHeight, column.getHeaderFitDimension().height);
}
}
// {
// Rectangle bounds = getBounds();
// setBounds(bounds.x, bounds.y, visibleWidth, bounds.height);
// }
if (headerHeight > 0) {
Rectangle bounds = getTable().getTableHeader().getBounds();
getTable().getTableHeader().setBounds(bounds.x, bounds.y, visibleWidth, headerHeight);
}
{
Rectangle bounds = getTable().getBounds();
getTable().setBounds(bounds.x, bounds.y, visibleWidth, getTable().getRowCount() * getTable().getRowHeight());
}
}
private Dimension autoFitColumnWidth(ColumnImpl column) {
Dimension dimension = UIUtil.autoFitColumnWidth(getTable(), column.getTableColumnExt());
column.getTableColumnExt().setWidth(dimension.width);
column.getTableColumnExt().setPreferredWidth(dimension.width);
return dimension;
}
}