001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.pm.graphic.chart;
051:
052: import java.awt.Color;
053: import java.awt.Component;
054: import java.awt.Point;
055: import java.awt.event.ActionEvent;
056: import java.awt.event.ActionListener;
057: import java.awt.event.ItemEvent;
058: import java.awt.event.ItemListener;
059: import java.io.Serializable;
060: import java.util.ArrayList;
061: import java.util.Iterator;
062: import java.util.List;
063:
064: import javax.swing.BorderFactory;
065: import javax.swing.ButtonGroup;
066: import javax.swing.DefaultListSelectionModel;
067: import javax.swing.Icon;
068: import javax.swing.JCheckBox;
069: import javax.swing.JComponent;
070: import javax.swing.JLabel;
071: import javax.swing.JList;
072: import javax.swing.JPanel;
073: import javax.swing.JRadioButton;
074: import javax.swing.JScrollPane;
075: import javax.swing.JTree;
076: import javax.swing.ListCellRenderer;
077: import javax.swing.event.ListSelectionEvent;
078: import javax.swing.event.ListSelectionListener;
079: import javax.swing.event.TreeSelectionEvent;
080: import javax.swing.event.TreeSelectionListener;
081: import javax.swing.plaf.basic.BasicListUI;
082: import javax.swing.tree.DefaultTreeCellRenderer;
083: import javax.swing.tree.DefaultTreeSelectionModel;
084: import javax.swing.tree.TreePath;
085:
086: import org.apache.commons.collections.Closure;
087:
088: import com.jgoodies.forms.builder.DefaultFormBuilder;
089: import com.jgoodies.forms.layout.CellConstraints;
090: import com.jgoodies.forms.layout.FormLayout;
091: import com.projity.grouping.core.NodeList;
092: import com.projity.grouping.core.model.NodeModel;
093: import com.projity.grouping.core.transform.CommonTransformFactory;
094: import com.projity.grouping.core.transform.ViewConfiguration;
095: import com.projity.menu.MenuActionConstants;
096: import com.projity.pm.assignment.HasAssignmentsImpl;
097: import com.projity.pm.assignment.HasTimeDistributedData;
098: import com.projity.pm.graphic.IconManager;
099: import com.projity.pm.graphic.model.cache.GraphicNode;
100: import com.projity.pm.graphic.spreadsheet.selection.event.SelectionNodeEvent;
101: import com.projity.pm.graphic.spreadsheet.selection.event.SelectionNodeListener;
102: import com.projity.pm.graphic.swing.Util;
103: import com.projity.pm.resource.Resource;
104: import com.projity.pm.resource.ResourceImpl;
105: import com.projity.pm.task.Project;
106: import com.projity.strings.Messages;
107: import com.projity.toolbar.TransformComboBox;
108: import com.projity.toolbar.TransformComboBoxModel;
109: import com.projity.util.Environment;
110: import com.projity.workspace.SavableToWorkspace;
111: import com.projity.workspace.WorkspaceSetting;
112:
113: /**
114: *
115: */
116: public class ChartLegend implements SelectionNodeListener,
117: Serializable, SavableToWorkspace {
118: private static final long serialVersionUID = 5098599798868391983L;
119:
120: JTree tree;
121: // JList traces;
122:
123: JCheckBox selectedOnTop;
124: JCheckBox cumulative;
125: JCheckBox histogram;
126: JRadioButton cost;
127: JRadioButton work;
128: JScrollPane treeScrollPane = null;
129: JScrollPane tracesScrollPane = null;
130: boolean selecting = false;
131: JList workTraces;
132: JList costTraces;
133:
134: boolean simple;
135: ChartInfo chartInfo;
136: List selectedObjects = new ArrayList();
137: List selectedResourcesFromTasks = new ArrayList();
138: List selectedResourcesOnTree = new ArrayList();
139: TransformComboBox filterComboBox = null;
140: JList tracesList;
141:
142: public ChartLegend(ChartInfo chartInfo) {
143: super ();
144: this .chartInfo = chartInfo;
145: this .simple = chartInfo.isSimple();
146: }
147:
148: void rebuildTree() {
149: // System.out.println("rebuilding tree");
150: initTree();
151: // ((AbstractMutableNodeHierarchy)chartInfo.getCache().getReference().getModel().getHierarchy()).dump();
152: }
153:
154: private void initTree() {
155: // tree = new JTree(chartInfo.getNodeModel());
156: tree = new JTree(chartInfo.getCache());
157: tree.setExpandsSelectedPaths(true);
158: final JTree finalTree = tree;
159: tree.addTreeSelectionListener(new TreeSelectionListener() {
160: public void valueChanged(TreeSelectionEvent evt) {
161: if (selecting)
162: return;
163: TreePath[] paths = ((JTree) evt.getSource())
164: .getSelectionPaths(); //evt.getPaths();
165: chartInfo.updateChart(selectedObjects,
166: pathsToList(paths));
167: }
168:
169: });
170: tree.setCellRenderer(new TreeRenderer());
171: if (treeScrollPane == null)
172: treeScrollPane = new JScrollPane(tree);
173: else
174: treeScrollPane.getViewport().add(tree);
175: }
176:
177: JList getListInstance(boolean cost) {
178: final JList list = new JList() { // do not want to update the UI. see below also
179: private static final long serialVersionUID = 1L;
180:
181: public void updateUI() {
182: if (!Environment.isNewLook())
183: super .updateUI();
184: }
185: };
186: if (Environment.isNewLook()) // The PLAF can override the custom renderer. This avoids that
187: list.setUI(new BasicListUI());
188: list
189: .setSelectionMode(DefaultListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
190: list.setCellRenderer(new ListRenderer());
191: setListFields(list, cost);
192: if (!simple) {
193: list.setSelectedIndex(0); // start off with first choice selected
194: list.addListSelectionListener(new ListSelectionListener() {
195: public void valueChanged(ListSelectionEvent e) {
196: if (chartInfo.isRestoring()) // don't want to listen if updating from workspace
197: return;
198: if (e.getValueIsAdjusting() == false) {
199: chartInfo.setTraces(list.getSelectedValues());
200: }
201: }
202: });
203: }
204:
205: return list;
206: }
207:
208: public void setControlValues() {
209: if (simple) {
210: selectedOnTop.setSelected(chartInfo.isSelectedOnTop()); // start off as histogram
211: } else {
212: cumulative.setSelected(chartInfo.isCumulative()); // start off as histogram
213: histogram.setSelected(chartInfo.isHistogram()); // start off as histogram
214: work.setSelected(chartInfo.isWork());
215: cost.setSelected(!chartInfo.isWork());
216: Util.setSelectedValues(tracesList, chartInfo.traces);
217: }
218:
219: }
220:
221: void initControls() {
222: chartInfo.setAxisPanel(new AxisPanel(chartInfo));
223: filterComboBox = new TransformComboBox(null,
224: MenuActionConstants.ACTION_CHOOSE_FILTER,
225: TransformComboBoxModel.FILTER);
226: filterComboBox.setView(ViewConfiguration
227: .getView(MenuActionConstants.ACTION_CHARTS));
228: filterComboBox.addActionListener(new ActionListener() {
229:
230: public void actionPerformed(ActionEvent e) {
231: if (chartInfo.isRestoring())
232: return;
233: TransformComboBox combo = (TransformComboBox) e
234: .getSource();
235: CommonTransformFactory factory = (CommonTransformFactory) combo
236: .getSelectedItem();
237: ((TransformComboBoxModel) combo.getModel())
238: .changeTransform(factory);
239: }
240: });
241:
242: initTree();
243: Object[] fields = getFields(false);
244:
245: workTraces = getListInstance(false);
246: tracesList = workTraces; // start off work
247: tracesScrollPane = new JScrollPane(workTraces);
248: workTraces
249: .setVisibleRowCount(Environment.getStandAlone() ? HasTimeDistributedData.tracesCount
250: : HasTimeDistributedData.serverTracesCount);
251:
252: // final ViewTransformer transformer=ViewConfiguration.getView(MenuActionConstants.ACTION_CHARTS).getTransform();
253: // final ResourceInTeamFilter hiddenFilter=(ResourceInTeamFilter)transformer.getHiddenFilter();
254: // teamResources= new JCheckBox(Messages.getString("Text.ShowTeamResourcesOnly"));
255: // teamResources.addItemListener(new ItemListener() {
256: // public void itemStateChanged(ItemEvent e) {
257: // hiddenFilter.setFilterTeam(e.getStateChange() == ItemEvent.SELECTED);
258: // transformer.update();
259: // }
260: // });
261: // teamResources.setSelected(hiddenFilter.isFilterTeam());
262:
263: if (simple) {
264: chartInfo.setTraces(fields);
265: tree.getSelectionModel().setSelectionMode(
266: DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); // allow only 1 for histogram
267:
268: selectedOnTop = new JCheckBox(Messages
269: .getString("Text.ShowSelectedOnTop")); //$NON-NLS-1$
270: selectedOnTop.addItemListener(new ItemListener() {
271: public void itemStateChanged(ItemEvent e) {
272:
273: chartInfo
274: .setSelectedOnTop(e.getStateChange() == ItemEvent.SELECTED);
275: Object[] traces = getFields(false);
276: chartInfo.setTraces(traces);
277: workTraces = getListInstance(false);
278: tracesScrollPane.getViewport().add(workTraces);
279: workTraces
280: .setVisibleRowCount(Environment
281: .getStandAlone() ? HasTimeDistributedData.tracesCount
282: : HasTimeDistributedData.serverTracesCount);
283: }
284: });
285: selectedOnTop.setSelected(chartInfo.isSelectedOnTop()); // start off as histogram
286:
287: return;
288: }
289:
290: costTraces = getListInstance(true);
291: cumulative = new JCheckBox(Messages
292: .getString("Text.Cumulative")); //$NON-NLS-1$
293: cumulative.setSelected(chartInfo.isCumulative()); // start off as histogram
294: cumulative.addItemListener(new ItemListener() {
295: public void itemStateChanged(ItemEvent e) {
296: chartInfo
297: .setCumulative(e.getStateChange() == ItemEvent.SELECTED);
298: }
299: });
300: histogram = new JCheckBox(Messages.getString("Text.Histogram")); //$NON-NLS-1$
301: histogram.setSelected(chartInfo.isHistogram()); // start off as histogram
302: histogram.addItemListener(new ItemListener() {
303: public void itemStateChanged(ItemEvent e) {
304: boolean histogramSelected = e.getStateChange() == ItemEvent.SELECTED;
305: chartInfo.setHistogram(histogramSelected);
306: if (histogramSelected) {
307: workTraces
308: .setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION); // allow only 1 for histogram
309: costTraces
310: .setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION); // allow only 1 for histogram
311: } else {
312: workTraces
313: .setSelectionMode(DefaultListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // allow many
314: costTraces
315: .setSelectionMode(DefaultListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // allow only 1 for histogram
316: }
317: }
318: });
319:
320: work = new JRadioButton(Messages.getString("Text.work")); //$NON-NLS-1$
321: work.setSelected(chartInfo.isWork());
322: cost = new JRadioButton(Messages.getString("Text.cost")); //$NON-NLS-1$
323:
324: ItemListener costWork = new ItemListener() {
325: public void itemStateChanged(ItemEvent e) {
326: boolean isCost = e.getSource() == cost;
327: chartInfo.setWork(!isCost);
328: tracesList = isCost ? costTraces : workTraces;
329: tracesScrollPane.getViewport().add(tracesList);
330: if (!chartInfo.isRestoring())
331: chartInfo.setTraces(tracesList.getSelectedValues());
332: }
333: };
334: cost.addItemListener(costWork);
335: work.addItemListener(costWork);
336: ButtonGroup group = new ButtonGroup();
337: group.add(cost);
338: group.add(work);
339:
340: // by default, always select first item
341: chartInfo.setTraces(new Object[] { fields[0] });
342: }
343:
344: // This class is a custom renderer based
345: // on DefaultTreeCellRenderer
346: class TreeRenderer extends DefaultTreeCellRenderer {
347: private static final long serialVersionUID = 1L;
348: private Icon activeIcon = IconManager.getIcon("greenCircle"); //$NON-NLS-1$
349:
350: public Component getTreeCellRendererComponent(JTree tree,
351: Object value, boolean selected, boolean expanded,
352: boolean leaf, int row, boolean hasFocus) {
353: // Allow the original renderer to set up the label
354: Component c = super .getTreeCellRendererComponent(tree,
355: value, selected, expanded, leaf, row, hasFocus);
356:
357: if (selectedResourcesFromTasks
358: .contains(((GraphicNode) value).getNode().getImpl())) {
359: setIcon(activeIcon);
360: }
361:
362: return c;
363: }
364: }
365:
366: class ListRenderer extends JLabel implements ListCellRenderer {
367: private static final long serialVersionUID = 1L;
368:
369: public ListRenderer() {
370: // Don't paint behind the component
371: setOpaque(true);
372: }
373:
374: public Component getListCellRendererComponent(JList list,
375: Object value, // value to display
376: int index, // cell index
377: boolean iss, // is selected
378: boolean chf) { // cell has focus?
379: setText(value.toString());
380: if (iss) {
381: Color color = ChartHelper.getColorForField(value);
382: setBackground(color);
383: if (color.getRed() + color.getGreen() + color.getBlue() < 450) // draw dark with white foreground
384: setForeground(Color.white);
385: else
386: setForeground(list.getForeground());
387:
388: } else {
389: setBackground(list.getBackground());
390: setForeground(list.getForeground());
391: }
392: // Set a border if the
393: //list item is selected
394: if (iss) {
395: setBorder(BorderFactory
396: .createLineBorder(Color.black, 1));
397: } else {
398: setBorder(BorderFactory.createLineBorder(list
399: .getBackground(), 1));
400: }
401:
402: return this ;
403: }
404: }
405:
406: private List pathsToList(TreePath[] paths) {
407: List list = new ArrayList();
408: if (paths != null) {
409: for (int i = 0; i < paths.length; i++) {
410: list
411: .add(((GraphicNode) paths[i]
412: .getLastPathComponent()).getNode()
413: .getImpl());
414: }
415: }
416: return list;
417: }
418:
419: public JComponent createContentPanel() {
420: // Separating the component initialization and configuration
421: // from the layout code makes both parts easier to read.
422: initControls();
423: FormLayout layout = new FormLayout(
424: "p:grow, 3dlu,100dlu:grow,5dlu, default, 5dlu", // cols //$NON-NLS-1$
425: "p, 3dlu, p, 3dlu, p, 3dlu, " + (simple ? "" : "fill:") + "p:grow, 5dlu"); // rows //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
426:
427: // Create a builder that assists in adding components to the container.
428: // Wrap the panel with a standardized border.
429: DefaultFormBuilder builder = new DefaultFormBuilder(layout);
430: builder.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
431: CellConstraints cc = new CellConstraints();
432:
433: builder.addLabel(Messages
434: .getString("ChartLegend.ResourceFilter"), cc.xy(1, 1)); //$NON-NLS-1$
435: builder.add(filterComboBox, cc.xy(3, 1));
436: builder.add(treeScrollPane, cc.xywh(1, 3, 3, 5));
437: builder.add(tracesScrollPane, cc.xy(5, 7));
438: if (simple) {
439: builder.add(selectedOnTop, cc.xy(5, 1));
440: } else {
441: builder.add(cumulative, cc.xy(5, 1));
442: builder.add(histogram, cc.xy(5, 3));
443: builder.add(workCostRadioPanel(), cc.xy(5, 5));
444: }
445: return builder.getPanel();
446: }
447:
448: public Component workCostRadioPanel() {
449: JPanel panel = new JPanel();
450: panel.add(work);
451: panel.add(cost);
452: return panel;
453: }
454:
455: private Object[] getFields(boolean cost) {
456: if (simple) {
457: return chartInfo.isSelectedOnTop() ? (Environment
458: .getStandAlone() ? HasTimeDistributedData.histogramTypes
459: : HasTimeDistributedData.serverHistogramTypes)
460: : (Environment.getStandAlone() ? HasTimeDistributedData.reverseHistogramTypes
461: : HasTimeDistributedData.serverReverseHistogramTypes);
462: }
463: return cost ? HasTimeDistributedData.costTypes
464: : HasTimeDistributedData.workTypes;
465: }
466:
467: private void setListFields(JList list, boolean cost) {
468: Object[] items = getFields(cost);
469: list.setListData(items);
470: list.setVisibleRowCount(items.length);
471: if (simple) {
472: list.setSelectionInterval(0, items.length);
473: list.setEnabled(false); // in simple mode, no selection allowed
474: }
475: }
476:
477: private List getListFromNodeList(List nodes) {
478: List implList = NodeList.nodeListToImplList(nodes);
479: // normally it is tasks or resources, but if project, make sure its works too
480: if (implList.isEmpty() || !(implList.get(0) instanceof Project))
481: return implList;
482:
483: final List resultList = new ArrayList();
484: Iterator i = implList.iterator();
485: while (i.hasNext()) {
486: ((Project) i.next()).forTasks(new Closure() {
487: public void execute(Object arg0) {
488: resultList.add(arg0);
489: }
490: });
491: //resultList.addAll( ((Project)i.next()).getTasks());
492: }
493: return resultList;
494: }
495:
496: public void selectionChanged(SelectionNodeEvent e) {
497:
498: if (!chartInfo.isVisible())
499: return;
500:
501: List nodes = e.getNodes();
502: selectedObjects = getListFromNodeList(nodes);
503: List resList = extractResources(selectedObjects);
504: if (resList.isEmpty())
505: selectedResourcesFromTasks = HasAssignmentsImpl
506: .extractOppositeList(selectedObjects, false);
507: else {
508: selectedResourcesFromTasks = resList; // top view is resource list
509: selectedObjects = null;
510: }
511: setTreeSelection(selectedResourcesFromTasks);
512: chartInfo.updateChart(selectedObjects, selectedResourcesOnTree);
513: }
514:
515: private List extractResources(List list) {
516: ArrayList resList = new ArrayList();
517: Iterator i = list.iterator();
518: Object obj;
519: while (i.hasNext()) {
520: obj = i.next();
521: if (obj instanceof Resource)
522: resList.add(obj);
523: }
524: return resList;
525: }
526:
527: private void setTreeSelection(List resources) {
528: selecting = true;
529: selectedResourcesOnTree.clear();
530: selectedResourcesOnTree.addAll(resources);
531: selectedResourcesOnTree.remove(ResourceImpl
532: .getUnassignedInstance());
533: int[] sel = new int[selectedResourcesOnTree.size()]; // if simple can only select 1
534: Object resource;
535: NodeModel nodeModel = chartInfo.getNodeModel();
536:
537: int topRow = Integer.MAX_VALUE;
538: Object topResource = null;
539: for (int i = 0; i < selectedResourcesOnTree.size(); i++) {
540: resource = selectedResourcesOnTree.get(i);
541: int row = nodeModel.getHierarchy().getIndexOfNode(
542: nodeModel.search(resource), false);
543: sel[i] = row;
544: if (row < topRow) {
545: topRow = row;
546: topResource = resource;
547: }
548: }
549: tree.clearSelection();
550: tree.setExpandsSelectedPaths(true);
551: if (simple && topResource != null) {
552: tree.setSelectionRow(topRow);
553: selectedResourcesOnTree.clear();
554: selectedResourcesOnTree.add(topResource);
555: } else {
556: tree.setSelectionRows(sel);
557: }
558: tree.setExpandsSelectedPaths(true);
559: tree.repaint();
560: selecting = false;
561: }
562:
563: public WorkspaceSetting createWorkspace(int context) {
564: Workspace ws = new Workspace();
565: ws.treeViewPosition = treeScrollPane.getViewport()
566: .getViewPosition();
567: ws.tracesViewPosition = tracesScrollPane.getViewport()
568: .getViewPosition();
569: return ws;
570: }
571:
572: public void restoreWorkspace(WorkspaceSetting w, int context) {
573: Workspace ws = (Workspace) w;
574: treeScrollPane.getViewport().setViewPosition(
575: ws.treeViewPosition);
576: tracesScrollPane.getViewport().setViewPosition(
577: ws.tracesViewPosition);
578: }
579:
580: public static class Workspace implements WorkspaceSetting {
581: private static final long serialVersionUID = -8581691622116505516L;
582: Point treeViewPosition;
583: Point tracesViewPosition;
584:
585: public Point getTracesViewPosition() {
586: return tracesViewPosition;
587: }
588:
589: public void setTracesViewPosition(Point tracesViewPosition) {
590: this .tracesViewPosition = tracesViewPosition;
591: }
592:
593: public Point getTreeViewPosition() {
594: return treeViewPosition;
595: }
596:
597: public void setTreeViewPosition(Point treeViewPosition) {
598: this.treeViewPosition = treeViewPosition;
599: }
600: }
601: }
|