/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ro.nextreports.designer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.jdesktop.swingx.JXTree;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import ro.nextreports.designer.action.undo.LayoutEdit;
import ro.nextreports.designer.grid.DefaultGridModel;
import ro.nextreports.designer.grid.event.GridModelEvent;
import ro.nextreports.designer.grid.event.GridModelListener;
import ro.nextreports.designer.ui.BaseDialog;
import ro.nextreports.designer.util.I18NSupport;
import ro.nextreports.designer.util.ImageUtil;
import ro.nextreports.designer.util.Show;
import ro.nextreports.designer.util.TreeUtil;
import ro.nextreports.engine.util.ObjectCloner;
import ro.nextreports.engine.ReportLayout;
import ro.nextreports.engine.band.Band;
import ro.nextreports.engine.band.BandElement;
import com.jgoodies.looks.HeaderStyle;
import com.jgoodies.looks.Options;
/**
* @author Decebal Suiu
*/
public class StructurePanel extends JPanel implements GridModelListener, ActionListener {
private JToggleButton showEmptyButton;
private StructureTreeNode rootNode;
private JXTree structureTree;
private StructureTreeModel structureTreeModel;
private JPopupMenu popup;
// when layout is cleared entirely, we must create a ReportGridCell for the first row inserted and
// firstReportCell variable becomes true
// on columns inserted event we must test this variable and if it is true we must return
private boolean firstReportCell = false;
public StructurePanel() {
super();
initComponents();
}
public JXTree getStructureTree() {
return structureTree;
}
public StructureTreeModel getStructureTreeModel() {
return structureTreeModel;
}
public void refresh() {
rootNode = new StructureTreeNode();
for (Band band : LayoutHelper.getReportLayout().getBands()) {
rootNode.add(new StructureTreeNode(band));
}
structureTreeModel.setRoot(rootNode);
}
public StructureTreeNode getBandElementTreeNode(int row, int column) {
Enumeration en = rootNode.breadthFirstEnumeration();
while (en.hasMoreElements()) {
while (en.hasMoreElements()) {
StructureTreeNode node = (StructureTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof ReportGridCell) {
ReportGridCell reportGridCell = (ReportGridCell) userObject;
if ((row == reportGridCell.getRow()) && (column == reportGridCell.getColumn())) {
return node;
}
}
}
}
return null;
}
public StructureTreeNode getRowElementTreeNode(int row) {
Enumeration en = rootNode.breadthFirstEnumeration();
while (en.hasMoreElements()) {
while (en.hasMoreElements()) {
StructureTreeNode node = (StructureTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof Band) {
Band band = (Band) userObject;
for (int i=0, size=node.getChildCount(); i<size; i++) {
StructureTreeNode child = (StructureTreeNode)node.getChildAt(i);
int bandRow = (Integer) child.getUserObject();
int gridRow = LayoutHelper.getReportLayout().getGridRow(band.getName(), bandRow);
if (gridRow == row) {
return child;
}
}
}
}
}
return null;
}
public void gridChanged(GridModelEvent event) {
// System.out.println("StructurePanel.gridChanged()");
int eventType = event.getType();
if (eventType == GridModelEvent.ROWS_INSERTED) {
// System.out.println("Row inserted ..............");
int row = event.getFirstRow();
// System.out.println("row = " + row);
String bandName = Globals.getReportGrid().getBandName(row);
// System.out.println("bandName = " + bandName);
int columnCount = Globals.getReportGrid().getColumnCount();
BandLocation bandLocation = Globals.getReportGrid().getBandLocation(bandName);
// after a clear all
if (bandLocation == null) {
return;
}
int bandRow = bandLocation.getRow(row);
DefaultMutableTreeNode bandTreeNode = getBandRowInsertedTreeNode(bandName, bandRow, row);
if ((row == 0) && (columnCount == 0)) {
StructureTreeNode elementNode = new StructureTreeNode(new ReportGridCell(null, row, 0));
elementNode.setVisible(false);
bandTreeNode.add(elementNode);
firstReportCell = true;
}
for (int column = 0; column < columnCount; column++) {
//System.out.println("column = " + column);
StructureTreeNode elementNode = new StructureTreeNode(new ReportGridCell(null, row, column));
elementNode.setVisible(false);
bandTreeNode.add(elementNode);
}
structureTreeModel.reload();
//((DefaultTreeModel) structureTree.getModel()).nodeStructureChanged(bandTreeNode.getParent());
// System.out.println("...............................");
} else if (eventType == GridModelEvent.ROWS_DELETED) {
// System.out.println("Row deleted ..............");
int row = event.getFirstRow();
int lastRow = event.getLastRow();
for (int i = lastRow; i >= row; i--) {
// System.out.println("row = " + i);
String bandName = Globals.getReportGrid().getBandName(i);
// System.out.println("bandName = " + bandName);
int bandRow = Globals.getReportGrid().getBandLocation(bandName).getRow(i);
deleteBandRowTreeNode(bandName, bandRow, i);
}
// System.out.println("...............................");
} else if (eventType == GridModelEvent.CELLS_UPDATED) {
// System.out.println("Cells updated ............");
int row = event.getFirstRow();
// System.out.println("row = " + row);
int column = event.getFirstColumn();
// System.out.println("column = " + column);
String bandName = Globals.getReportGrid().getBandName(row);
// System.out.println("bandName = " + bandName);
// System.out.println(Globals.getReportGrid().getBandLocations());
DefaultGridModel gridModel = (DefaultGridModel) event.getSource();
BandElement element = (BandElement) gridModel.getValueAt(row, column);
int bandRow = Globals.getReportGrid().getBandLocation(bandName).getRow(row);
StructureTreeNode elementNode = getBandElementTreeNode(bandName, bandRow, column);
elementNode.setVisible(element != null);
elementNode.setUserObject(new ReportGridCell(element, row, column));
TreePath[] selectionPath = structureTree.getSelectionPaths();
// must use reload to take visble/invisible null cells into account
structureTreeModel.reload(elementNode.getParent());
// reselect the nodes
if ((selectionPath != null) && ((element != null) || !structureTreeModel.isActivatedFilter())) {
structureTree.setSelectionPaths(selectionPath);
}
if (element == null) {
Globals.getReportDesignerPanel().getPropertiesPanel().refresh();
}
} else if (eventType == GridModelEvent.COLUMNS_INSERTED) {
// System.out.println("Columns inserted ..............");
int column = event.getFirstColumn();
// System.out.println("column = " + column);
if (firstReportCell) {
firstReportCell = false;
} else {
insertColumnNodes(column);
structureTreeModel.nodeStructureChanged(rootNode);
}
// System.out.println("...............................");
} else if (eventType == GridModelEvent.COLUMNS_DELETED) {
// System.out.println("Columns deleted ..............");
int column = event.getFirstColumn();
int lastColumn = event.getLastColumn();
// System.out.println("column = " + column);
for (int i = lastColumn; i >= column; i--) {
deleteColumnNodes(i);
}
// System.out.println("...............................");
}
}
private void initComponents() {
setLayout(new BorderLayout());
rootNode = new StructureTreeNode();
rootNode.add(new StructureTreeNode(new Band(ReportLayout.HEADER_BAND_NAME)));
rootNode.add(new StructureTreeNode(new Band(ReportLayout.DETAIL_BAND_NAME)));
rootNode.add(new StructureTreeNode(new Band(ReportLayout.FOOTER_BAND_NAME)));
structureTreeModel = new StructureTreeModel(rootNode);
structureTree = new JXTree(structureTreeModel);
structureTree.setShowsRootHandles(true);
structureTree.setCellRenderer(new StructureTreeCellRenderer());
structureTree.setRolloverEnabled(true);
structureTree.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, null, Color.RED));
createPopup();
// by default do not show empty cells
Action showEmptyAction = new AbstractAction() {
public void actionPerformed(ActionEvent event) {
TreePath lastPath = structureTree.getSelectionPath();
structureTreeModel.activateFilter(!showEmptyButton.isSelected());
structureTreeModel.reload();
if (lastPath != null) {
structureTree.expandPath(lastPath.getParentPath());
structureTree.setSelectionPath(lastPath);
}
}
};
showEmptyAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("empty_cell"));
showEmptyAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("report.structure.show.empty"));
showEmptyButton = new JToggleButton(showEmptyAction);
structureTreeModel.activateFilter(true);
JToolBar toolBar = new JToolBar();
toolBar.putClientProperty("JToolBar.isRollover", Boolean.TRUE); // hide buttons borders
toolBar.putClientProperty(Options.HEADER_STYLE_KEY, HeaderStyle.BOTH);
toolBar.setBorderPainted(false);
// add expand action
toolBar.add(new AbstractAction() {
public Object getValue(String key) {
if (AbstractAction.SMALL_ICON.equals(key)) {
return ImageUtil.getImageIcon("expandall");
} else if (AbstractAction.SHORT_DESCRIPTION.equals(key)) {
return I18NSupport.getString("querybuilder.expand.all");
}
return super.getValue(key);
}
public void actionPerformed(ActionEvent e) {
TreeUtil.expandAll(structureTree);
}
});
// add collapse action
toolBar.add(new AbstractAction() {
public Object getValue(String key) {
if (AbstractAction.SMALL_ICON.equals(key)) {
return ImageUtil.getImageIcon("collapseall");
} else if (AbstractAction.SHORT_DESCRIPTION.equals(key)) {
return I18NSupport.getString("querybuilder.collapse.all");
}
return super.getValue(key);
}
public void actionPerformed(ActionEvent e) {
TreeUtil.collapseAll(structureTree);
}
});
toolBar.add(showEmptyButton);
add(toolBar, BorderLayout.NORTH);
add(new JScrollPane(structureTree), BorderLayout.CENTER);
}
public void addGroup(String groupName, int groupMask) {
int children = rootNode.getChildCount();
List<Band> headerBands = LayoutHelper.getReportLayout().getGroupHeaderBands();
int headers = 0;
if (headerBands != null) {
headers = headerBands.size();
}
List<Band> footerBands = LayoutHelper.getReportLayout().getGroupFooterBands();
int footers = 0;
if (footerBands != null) {
footers = footerBands.size();
}
rootNode.insert(new StructureTreeNode(new Band(ReportLayout.GROUP_FOOTER_BAND_NAME_PREFIX + groupName)), children - footers);
rootNode.insert(new StructureTreeNode(new Band(ReportLayout.GROUP_HEADER_BAND_NAME_PREFIX + groupName)), headers);
((DefaultTreeModel) structureTree.getModel()).nodeStructureChanged(rootNode);
}
public void deleteGroup(String groupName) {
DefaultMutableTreeNode footerBandNode = getBandTreeNode(ReportLayout.GROUP_FOOTER_BAND_NAME_PREFIX + groupName);
if (footerBandNode != null) {
structureTreeModel.removeNodeFromParent(footerBandNode);
}
DefaultMutableTreeNode headerBandNode = getBandTreeNode(ReportLayout.GROUP_HEADER_BAND_NAME_PREFIX + groupName);
if (headerBandNode != null) {
structureTreeModel.removeNodeFromParent(headerBandNode);
}
}
private void createPopup() {
popup = new JPopupMenu();
JMenuItem mi = new JMenuItem(I18NSupport.getString("insert.row"));
mi.addActionListener(this);
mi.setActionCommand("insert");
popup.add(mi);
popup.setOpaque(true);
popup.setLightWeightPopupEnabled(true);
structureTree.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
show(e);
}
public void mouseReleased(MouseEvent e) {
show(e);
}
private void show(MouseEvent e) {
if (e.isPopupTrigger()) {
TreePath path = structureTree.getSelectionPath();
if (path != null) {
DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) path.getLastPathComponent();
if (dmtn.getUserObject() instanceof Band) {
popup.show((JComponent) e.getSource(), e.getX(), e.getY());
}
}
}
}
});
}
@SuppressWarnings("unchecked")
private DefaultMutableTreeNode getBandTreeNode(String bandName) {
Enumeration en = rootNode.children();
while (en.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof Band) {
Band band = (Band) userObject;
if (bandName.equals(band.getName())) {
return node;
}
}
}
return null;
}
@SuppressWarnings("unchecked")
private DefaultMutableTreeNode getBandRowTreeNode(String bandName, int row) {
DefaultMutableTreeNode bandNode = getBandTreeNode(bandName);
Enumeration rows = bandNode.children();
while (rows.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) rows.nextElement();
Object userObject = node.getUserObject();
if ((Integer) userObject == row) {
return node;
}
}
StructureTreeNode rowNode = new StructureTreeNode(row);
bandNode.insert(rowNode, row);
return rowNode;
}
private StructureTreeNode getBandElementTreeNode(String bandName, int row, int column) {
DefaultMutableTreeNode bandNode = getBandRowTreeNode(bandName, row);
StructureTreeNode child;
if (bandNode.getChildCount() <= column) {
child = new StructureTreeNode(new ReportGridCell(null, row, column));
child.setVisible(false);
bandNode.insert(child, column);
} else {
child = (StructureTreeNode) bandNode.getChildAt(column);
}
return child;
}
@SuppressWarnings("unchecked")
private DefaultMutableTreeNode getBandRowInsertedTreeNode(String bandName, int bandRow, int row) {
DefaultMutableTreeNode bandNode = getBandTreeNode(bandName);
// tree must be traversed in preorder (band by band)
Enumeration en = rootNode.preorderEnumeration();
String currentBandName = ReportLayout.HEADER_BAND_NAME;
while (en.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof Band) {
currentBandName = ((Band) userObject).getName();
} else if (userObject instanceof ReportGridCell) {
// increment rows for all report grid cells which are in the rows
// following the inserted row
ReportGridCell reportGridCell = (ReportGridCell) userObject;
if (row <= reportGridCell.getRow()) {
reportGridCell.setRow(reportGridCell.getRow() + 1);
}
} else if (userObject instanceof Integer) {
// modify 'row' nodes in the tree (for the current band)
if (bandName.equals(currentBandName)) {
if ((Integer) userObject == bandRow) {
DefaultMutableTreeNode sibling = node.getNextSibling();
node.setUserObject(((Integer) node.getUserObject()).intValue() + 1);
while (sibling != null) {
sibling.setUserObject(((Integer) sibling.getUserObject()).intValue() + 1);
sibling = sibling.getNextSibling();
}
}
}
}
}
DefaultMutableTreeNode rowNode = new StructureTreeNode(bandRow);
bandNode.insert(rowNode, bandRow);
return rowNode;
}
private void deleteBandRowTreeNode(String bandName, int bandRow, int row) {
// tree must be traversed in preorder (band by band)
Enumeration en = rootNode.preorderEnumeration();
String currentBandName = ReportLayout.HEADER_BAND_NAME;
DefaultMutableTreeNode nodeToDelete = null;
while (en.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof Band) {
currentBandName = ((Band) userObject).getName();
} else if (userObject instanceof ReportGridCell) {
// decrement rows for all report grid cells which are in the rows
// following the inserted row
ReportGridCell reportGridCell = (ReportGridCell) userObject;
if (row <= reportGridCell.getRow()) {
reportGridCell.setRow(reportGridCell.getRow() - 1);
}
} else if (userObject instanceof Integer) {
// modify 'row' nodes in the tree (for the current band)
if (bandName.equals(currentBandName)) {
if ((Integer) userObject == bandRow) {
if (nodeToDelete == null) {
nodeToDelete = node;
DefaultMutableTreeNode sibling = node.getNextSibling();
while (sibling != null) {
sibling.setUserObject(((Integer) sibling.getUserObject()).intValue() - 1);
sibling = sibling.getNextSibling();
}
}
}
}
}
}
if (nodeToDelete != null) {
structureTreeModel.removeNodeFromParent(nodeToDelete);
}
}
private void insertColumnNodes(int column) {
Enumeration en = rootNode.preorderEnumeration();
List<DefaultMutableTreeNode> nodes = new ArrayList<DefaultMutableTreeNode>();
List<DefaultMutableTreeNode> nodesToAdd = new ArrayList<DefaultMutableTreeNode>();
int columnCount = Globals.getReportGrid().getColumnCount();
while (en.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof ReportGridCell) {
ReportGridCell reportGridCell = (ReportGridCell) userObject;
if ((column == reportGridCell.getColumn()) ||
((column == reportGridCell.getColumn() + 1) && (column + 1 == columnCount))) {
StructureTreeNode colNode = new StructureTreeNode(
new ReportGridCell(null, reportGridCell.getRow(), column));
colNode.setVisible(false);
nodes.add((DefaultMutableTreeNode) node.getParent());
nodesToAdd.add(colNode);
}
// increment column for all report grid cells which are in the columns
// following the inserted column
if (column <= reportGridCell.getColumn()) {
reportGridCell.setColumn(reportGridCell.getColumn() + 1);
}
}
}
for (int i = 0, size = nodes.size(); i < size; i++) {
nodes.get(i).insert(nodesToAdd.get(i), column);
}
}
private void deleteColumnNodes(int column) {
Enumeration en = rootNode.preorderEnumeration();
List<DefaultMutableTreeNode> nodesToDelete = new ArrayList<DefaultMutableTreeNode>();
while (en.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) en.nextElement();
Object userObject = node.getUserObject();
if (userObject instanceof ReportGridCell) {
ReportGridCell reportGridCell = (ReportGridCell) userObject;
if (column == reportGridCell.getColumn()) {
nodesToDelete.add(node);
}
// decrement column for all report grid cells which are in the columns
// following the deleted column
if (column <= reportGridCell.getColumn()) {
reportGridCell.setColumn(reportGridCell.getColumn() - 1);
}
}
}
for (DefaultMutableTreeNode node : nodesToDelete) {
structureTreeModel.removeNodeFromParent(node);
}
}
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeNode node;
TreePath path = structureTree.getSelectionPath();
node = (DefaultMutableTreeNode) path.getLastPathComponent();
if (e.getActionCommand().equals("insert")) {
final NumberSelectionPanel panel = new NumberSelectionPanel(I18NSupport.getString("insert.row.number"));
final BaseDialog dialog = new BaseDialog(panel, I18NSupport.getString("insert.row.after.action.name"), true) {
public boolean okPressed() {
if ((panel.getNumber() < 1) || (panel.getNumber() > BandUtil.MAX)) {
Show.info(this, I18NSupport.getString("rowCol.max", BandUtil.MAX));
return false;
}
return true;
}
};
dialog.pack();
dialog.setLocationRelativeTo(Globals.getMainFrame());
dialog.setVisible(true);
if (!dialog.okPressed()) {
return;
}
ReportLayout oldLayout = ObjectCloner.silenceDeepCopy(LayoutHelper.getReportLayout());
int rows = panel.getNumber();
String bandName = ((Band) node.getUserObject()).getName();
Band band = LayoutHelper.getReportLayout().getBand(bandName);
int row = Globals.getReportGrid().getBandLocation(bandName).getLastGridRow();
int cols = Globals.getReportGrid().getColumnCount();
if (cols == 0) {
// empty report : we will add one column
cols = 1;
}
for (int i = 0; i < rows; i++) {
Globals.getReportLayoutPanel().getReportGridPanel().insertRow(band);
for (int j=0; j<cols; j++) {
BandUtil.insertElement(new BandElement(""), row+i, j);
}
}
Globals.getReportGrid().getSelectionModel().clearSelection();
ReportLayout newLayout = ObjectCloner.silenceDeepCopy(LayoutHelper.getReportLayout());
Globals.getReportUndoManager().addEdit(new LayoutEdit(oldLayout, newLayout, I18NSupport.getString("edit.row.insert.before")));
}
}
}