001: package jimm.datavision.gui;
002:
003: import jimm.datavision.*;
004: import jimm.datavision.field.Field;
005: import jimm.datavision.gui.cmd.Command;
006: import jimm.datavision.gui.cmd.SectionResizeCommand;
007: import jimm.datavision.gui.cmd.SectionPageBreakCommand;
008: import java.util.*;
009: import java.awt.*;
010: import java.awt.event.*;
011: import javax.swing.*;
012:
013: /**
014: * A section widget is the visual representation of a report section.
015: *
016: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
017: */
018: public class SectionWidget extends JPanel implements ActionListener {
019:
020: public static final int LHS_WIDTH = 125;
021:
022: protected static final Font DEFAULT_POPUP_FONT = new Font("Serif",
023: Font.PLAIN, 10);
024:
025: static final Color NORMAL_COLOR = Color.white;
026: static final Color SUPPRESSED_COLOR = Color.lightGray;
027:
028: protected String name;
029: protected String popupName;
030: protected Designer designer;
031: protected Section section;
032: protected SectionNameLabel label;
033: protected SectionFieldPanel fieldPanel;
034: protected JPopupMenu popup;
035: protected JMenuItem nameItem, editSuppress;
036: protected JCheckBoxMenuItem togglePageBreak;
037: protected JMenuItem deleteGroup, addGroup, deleteSection,
038: insertSection;
039:
040: /**
041: * An inner class that handles display of the popup menu.
042: */
043: class PopupListener extends MouseAdapter {
044: public void mousePressed(MouseEvent e) {
045: maybeShowPopup(e);
046: }
047:
048: public void mouseReleased(MouseEvent e) {
049: maybeShowPopup(e);
050: }
051:
052: private void maybeShowPopup(MouseEvent e) {
053: if (e.isPopupTrigger())
054: showPopup(e);
055: }
056: }
057:
058: /**
059: * Constructor.
060: *
061: * @param win parent design window
062: * @param sect the report section
063: * @param name a name such as "Report Header (a)"
064: */
065: public SectionWidget(Designer win, Section sect, String name) {
066: super ();
067:
068: designer = win;
069: section = sect;
070: this .name = name;
071: this .popupName = "";
072:
073: setLayout(new SectionLayout());
074: setPreferredSize(new Dimension(getTotalWidth(), getHeight()));
075:
076: buildPopupMenu();
077: addMouseListener(new PopupListener());
078:
079: // LHS name of section
080: buildDisplayName();
081:
082: // Fields
083: fieldPanel = new SectionFieldPanel(this );
084: fieldPanel.setLayout(null);
085: add(fieldPanel);
086: for (Iterator iter = section.fields(); iter.hasNext();) {
087: Field f = (Field) iter.next();
088: FieldWidget fw = f.makeWidget(this );
089: fieldPanel.add(fw.getComponent(), 0); // Add to top of visual stack.
090: }
091:
092: // Let field panel set background color of itself and fields based
093: // on "always hide" suppression.
094: fieldPanel.setHidden(section.isHidden());
095:
096: // If design window has initiated the drop of a new text field, create
097: // one. Else deselect all fields.
098: fieldPanel.addMouseListener(new PopupListener() {
099: public void mouseClicked(MouseEvent e) {
100: deselectAll();
101: if (designer.isPlacingNewTextField())
102: createNewTextField(e); // Calls acceptNewTextField()
103: }
104: });
105:
106: // Resizer bar
107: add(new SectionResizer(this , designer.sectionContainer));
108: }
109:
110: /**
111: * Builds the popup menu.
112: */
113: protected void buildPopupMenu() {
114: popup = new JPopupMenu(popupName);
115:
116: nameItem = MenuUtils.addToMenu(this , popup,
117: "SectionWidget.popup_dummy_title", DEFAULT_POPUP_FONT);
118: popup.addSeparator();
119: editSuppress = MenuUtils.addToMenu(this , popup,
120: "SectionWidget.popup_suppress", DEFAULT_POPUP_FONT);
121: togglePageBreak = MenuUtils.addCheckboxToMenu(this , popup,
122: "SectionWidget.popup_page_break", DEFAULT_POPUP_FONT);
123: popup.addSeparator();
124: MenuUtils.addToMenu(this , popup, "SectionWidget.popup_shrink",
125: DEFAULT_POPUP_FONT);
126: popup.addSeparator();
127: deleteGroup = MenuUtils.addToMenu(this , popup,
128: "SectionWidget.popup_delete_group", DEFAULT_POPUP_FONT);
129: addGroup = MenuUtils.addToMenu(this , popup,
130: "SectionWidget.popup_add_group", DEFAULT_POPUP_FONT);
131: popup.addSeparator();
132: deleteSection = MenuUtils.addToMenu(this , popup,
133: "SectionWidget.popup_delete_section",
134: DEFAULT_POPUP_FONT);
135: insertSection = MenuUtils.addToMenu(this , popup,
136: "SectionWidget.popup_insert_section",
137: DEFAULT_POPUP_FONT);
138: }
139:
140: /**
141: * Performs some action based on the action command string (the menu
142: * item text).
143: */
144: public void actionPerformed(ActionEvent e) {
145: String command = e.getActionCommand();
146: if (command == null)
147: return;
148: if ("suppress".equals(command))
149: editSuppression();
150: else if ("page_break".equals(command))
151: togglePageBreak();
152: else if ("delete_group".equals(command))
153: designer.deleteGroupContaining(section);
154: else if ("add_group".equals(command))
155: designer.openNewGroupWin();
156: else if ("delete_section".equals(command))
157: designer.deleteSection(section);
158: else if ("insert_section_below".equals(command))
159: designer.insertSectionBelow(section);
160: else if ("shrink_to_fit".equals(command))
161: shrinkToFit();
162: }
163:
164: /**
165: * Modifies menu items based on the state of the section.
166: */
167: protected void enablePopupMenuItems() {
168: nameItem.setText(popupName);
169: togglePageBreak.setSelected(section.hasPageBreak());
170: deleteSection.setEnabled(!section.getReport().isOneOfAKind(
171: section));
172: deleteGroup.setEnabled(section.getReport().isInsideGroup(
173: section));
174: }
175:
176: /**
177: * Constructs the section name widget that is displayed to the left of
178: * the section.
179: */
180: protected void buildDisplayName() {
181: label = new SectionNameLabel(name, this );
182: add(label);
183: }
184:
185: /**
186: * Set the section display name.
187: *
188: * @param name the new name
189: */
190: public void setDisplayName(String name) {
191: this .name = name;
192: label.setText(name);
193: }
194:
195: /**
196: * Set the popup menu name, also displayed as first, disabled menu item.
197: *
198: * @param popupName the new name
199: */
200: public void setPopupName(String popupName) {
201: this .popupName = popupName;
202: nameItem.setText(popupName);
203: }
204:
205: /**
206: * Returns the report we are representing.
207: *
208: * @return a report
209: */
210: public Report getReport() {
211: return section.getReport();
212: }
213:
214: /**
215: * Returns the section we are representing.
216: *
217: * @return a report section
218: */
219: public Section getSection() {
220: return section;
221: }
222:
223: /**
224: * Returns the {@link SectionArea} of the {@link Section} (report header, page
225: * footer, etc.)
226: *
227: * @return the section's <code>SectionArea</code>
228: */
229: public SectionArea getSectionArea() {
230: return section.getArea();
231: }
232:
233: /**
234: * Returns the design window containing this section widget
235: *
236: * @return a design window
237: */
238: public Designer getDesigner() {
239: return designer;
240: }
241:
242: /**
243: * Returns the width of the report paper (the white part upon which fields
244: * are placed).
245: *
246: * @return the paper width
247: */
248: public int getPaperWidth() {
249: return (int) section.getWidth();
250: }
251:
252: /**
253: * Returns the width of the section, including the left-hand side name.
254: *
255: * @return the total width
256: */
257: public int getTotalWidth() {
258: return LHS_WIDTH + getPaperWidth();
259: }
260:
261: /**
262: * Returns the height of the section, including the resizer bar.
263: *
264: * @return the total height
265: */
266: public int getHeight() {
267: return (int) section.getMinHeight() + SectionResizer.HEIGHT;
268: }
269:
270: /**
271: * Returns the height of the report section.
272: *
273: * @return report section height
274: */
275: public int getSectionHeight() {
276: return (int) section.getMinHeight();
277: }
278:
279: /**
280: * Returns the minimum height the section needs to fit all of its fields.
281: *
282: * @return minimum height
283: */
284: public int getMinSectionHeight() {
285: int minY = 0;
286: Component[] fieldWidgets = fieldPanel.getComponents();
287: for (int i = 0; i < fieldWidgets.length; ++i) {
288: int y = fieldWidgets[i].getBounds().y
289: + fieldWidgets[i].getBounds().height;
290: if (y > minY)
291: minY = y;
292: }
293: return minY;
294: }
295:
296: /**
297: * Resizes this widget. Called by the design window whenever the user
298: * selects a new paper size.
299: */
300: void paperSizeChanged() {
301: setPreferredSize(new Dimension(getTotalWidth(), getHeight()));
302: invalidate();
303: }
304:
305: /**
306: * Toggles the suppressed flag of the section.
307: */
308: void editSuppression() {
309: new SuppressionProcWin(designer, this );
310: }
311:
312: /**
313: * Toggles the page break flag of the section.
314: */
315: void togglePageBreak() {
316: performCommand(new SectionPageBreakCommand(section));
317: }
318:
319: /**
320: * Shrinks this section widget to the minimum height required. This method
321: * is only called from the popup menu. It should not be called as part
322: * of a larger operation because it creates a command that allows undo/redo.
323: */
324: public void shrinkToFit() {
325: SectionResizeCommand cmd = new SectionResizeCommand(this );
326: growBy(getMinSectionHeight() - getSectionHeight());
327: performCommand(cmd);
328: }
329:
330: /**
331: * Grows this section widget to the minimum height required. This method,
332: * unlike <code>shrinkToFit</code>, is always called as part of some other
333: * operation.
334: */
335: public void growToFit() {
336: int dy = getMinSectionHeight() - getSectionHeight();
337:
338: if (dy > 0)
339: growBy(dy);
340: }
341:
342: /**
343: * Resizes the section. Called by resizer bar and by commands that
344: * grow sections as a side effect.
345: *
346: * @param dy delta y
347: */
348: public void growBy(int dy) {
349: if (dy == 0)
350: return;
351:
352: // Make sure section fits all fields.
353: int origHeight = (int) section.getMinHeight();
354: int newHeight = origHeight + dy;
355: int minHeight = getMinSectionHeight();
356: if (newHeight < minHeight) {
357: newHeight = minHeight;
358: if (newHeight == origHeight)
359: return;
360: }
361:
362: section.setMinHeight(newHeight); // Hight not including resizer
363: setPreferredSize(new Dimension(getTotalWidth(), getHeight())); // Incl. resizer
364: revalidate();
365: }
366:
367: /**
368: * Grows or shrinks the section widget and executes a command that allows
369: * this action to be undone/redone. Calls {@link #growBy} to grow
370: * or shrink, then lets our window execute the command that remembers
371: * the size change for later undo/redo.
372: * <p>
373: * The command does not change our height. It remembers the old and new
374: * heights for later undo/redo.
375: *
376: * @param dy delta height
377: * @param cmd a section resize command
378: * @see #performCommand
379: */
380: public void resizeBy(int dy, SectionResizeCommand cmd) {
381: growBy(dy);
382: performCommand(cmd);
383: }
384:
385: /**
386: * Passes a command up to the design window for execution.
387: *
388: * @param cmd a command
389: * @see Designer#performCommand
390: */
391: public void performCommand(Command cmd) {
392: designer.performCommand(cmd);
393: }
394:
395: /**
396: * Passes responsiblity up to the design window.
397: * @see Designer#setIgnoreKeys
398: */
399: public void setIgnoreKeys(boolean ignoreKeys) {
400: designer.setIgnoreKeys(ignoreKeys);
401: }
402:
403: /**
404: * Passes this request up to the design window.
405: *
406: * @param x where to place the title
407: * @param width how wide it should be
408: * @param title the string to display
409: * @return the newly-created widget
410: * @see Designer#addTitleField
411: */
412: public FieldWidget addTitleField(int x, int width, String title) {
413: return designer.addTitleField(x, width, title);
414: }
415:
416: /**
417: * Passes on to the design window the request to pick up all selected fields
418: * for dragging (not just the specified field). Called from {@link
419: * FieldWidget#mousePressed}.
420: *
421: * @param mouseScreenPos the location of the mouse in screen coordinates
422: * @see Designer#pickUp
423: */
424: void pickUp(java.awt.Point mouseScreenPos) {
425: designer.pickUp(mouseScreenPos);
426: }
427:
428: /**
429: * Passes on to the design window the request to put down all fields being
430: * dragged (not just the specified field). Called from {@link
431: * FieldWidget#mouseReleased}.
432: *
433: * @param f the field in which the mouse has been clicked
434: * @param origScreenPos the original location of the field in screen
435: * coordinates
436: * @param mouseScreenPos the location of the mouse in screen coordinates
437: * @see Designer#putDown
438: */
439: void putDown(FieldWidget f, java.awt.Point origScreenPos,
440: java.awt.Point mouseScreenPos) {
441: designer.putDown(f, origScreenPos, mouseScreenPos);
442: }
443:
444: /**
445: * Passes on to the design window the request to start stretching all selected
446: * fields (not just the specified field). Called from {@link
447: * FieldWidget#mousePressed}.
448: *
449: * @param mouseScreenPos the location of the mouse in screen coordinates
450: * @see Designer#startStretching
451: */
452: void startStretching(java.awt.Point mouseScreenPos) {
453: designer.startStretching(mouseScreenPos);
454: }
455:
456: /**
457: * Passes on to the design window the request to stop stretching all fields
458: * being stretched (not just the specified field). Called from {@link
459: * FieldWidget#mouseReleased}.
460: *
461: * @param f the field in which the mouse has been clicked
462: * @param origBounds the field's original bounds
463: * @see Designer#putDown
464: */
465: void stopStretching(FieldWidget f,
466: jimm.datavision.field.Rectangle origBounds) {
467: designer.stopStretching(f, origBounds);
468: }
469:
470: /**
471: * Tells the window to drag (move, resize) all selected fields. Called
472: * from one field widget when it's being manipulated with the mouse.
473: *
474: * @param action a <code>FieldWidget.ACTION_*</code> constant
475: * @param mouseScreenPos the location of the mouse in screen coordinates
476: * @see FieldWidget#mouseDragged
477: * @see Designer#dragSelectedWidgets
478: */
479: void dragSelectedWidgets(int action, java.awt.Point mouseScreenPos) {
480: designer.dragSelectedWidgets(action, mouseScreenPos);
481: }
482:
483: /**
484: * Selects or deselcts a field widget, possibly deselecting all others
485: * everywhere. Called from field widget itself, this passes the request
486: * on to the design window.
487: *
488: * @param fieldWidget a field widget
489: * @param makeSelected if <code>true</code>, select this field; else
490: * deselect it
491: * @param deselectOthers if <code>true</code>, all other fields in all
492: * sections are deselected first
493: * @see Designer#select
494: */
495: void select(FieldWidget fieldWidget, boolean makeSelected,
496: boolean deselectOthers) {
497: designer.select(fieldWidget, makeSelected, deselectOthers);
498: }
499:
500: /**
501: * Deselects all fields in all sections. Tells the design window to do
502: * so.
503: *
504: * @see Designer#deselectAll
505: */
506: void deselectAll() {
507: designer.deselectAll();
508: }
509:
510: /**
511: * Adds field widget to panel. Does not affect models. Field retains its
512: * selection state.
513: *
514: * @param fw field widget to add
515: */
516: public void addField(FieldWidget fw) {
517: fieldPanel.add(fw.getComponent(), 0); // Add to top of visual stack.
518: fw.sectionWidget = this ;
519: fw.getComponent().setBackground(
520: section.isHidden() ? SUPPRESSED_COLOR : NORMAL_COLOR);
521: growToFit();
522: }
523:
524: /**
525: * Removes field widget from panel, but do not change field model's relation
526: * with section model. Field retains its selection state.
527: * <p>
528: * To delete a field widget completely, see {@link
529: * Designer#deleteSelectedFields}.
530: *
531: * @param fw field widget to remove
532: * @see #addField
533: */
534: public void removeField(FieldWidget fw) {
535: fieldPanel.remove(fw.getComponent());
536: }
537:
538: public SectionFieldPanel getFieldPanel() {
539: return fieldPanel;
540: }
541:
542: /**
543: * Asks the design window to snap the rectangle to it's grid.
544: *
545: * @param r a rectangle
546: */
547: void snapToGrid(jimm.datavision.field.Rectangle r) {
548: designer.snapToGrid(r);
549: }
550:
551: /**
552: * Sets the visibility of all selected fields plus the one passed in.
553: * Passes the buck to the design window.
554: *
555: * @see FieldWidget#toggleVisibility
556: * @see Designer#setFieldVisibility
557: */
558: void setFieldVisibility(boolean newVisiblity, FieldWidget fw) {
559: designer.setFieldVisibility(newVisiblity, fw);
560: }
561:
562: /**
563: * Asks design window to create and accepts a new text field.
564: *
565: * @see Designer#createNewTextField
566: */
567: void createNewTextField(MouseEvent e) {
568: designer.createNewTextField(this , e);
569: }
570:
571: /**
572: * Displays popup menu, after enabling and disabling menu items.
573: *
574: * @param e mouse event that caused popup to do its thing
575: */
576: void showPopup(MouseEvent e) {
577: enablePopupMenuItems();
578: popup.show(e.getComponent(), e.getX(), e.getY());
579: }
580:
581: }
|