001: /*=============================================================================
002: * Copyright Texas Instruments, Inc., 2001. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.chimera;
020:
021: import javax.swing.Action;
022: import javax.swing.JOptionPane;
023: import javax.swing.JPopupMenu;
024: import javax.swing.JMenu;
025: import javax.swing.ImageIcon;
026: import javax.swing.Icon;
027: import javax.swing.AbstractAction;
028: import java.awt.Component;
029: import java.awt.event.ActionEvent;
030: import java.util.LinkedList;
031: import java.util.Iterator;
032:
033: /**
034: * A <code>View</code> is a "window" created by a plug-in. It can contain
035: * any swing component, or any lightweight (ie. does not have a native peer)
036: * AWT component.
037: *
038: * @author Rob Clark
039: * @version 0.1
040: */
041: public abstract class View {
042: // protected void finalize() { System.out.println("finalize view: " + getName()); }
043:
044: /**
045: * The main application.
046: */
047: protected Main main;
048:
049: // hack needed for Dock.showView():
050: Main getMain() {
051: return main;
052: }
053:
054: /**
055: * The plugin that created this view.
056: */
057: private Plugin plugin;
058:
059: /**
060: * The name of the view.
061: */
062: private String name;
063:
064: /**
065: * The optional icon.
066: */
067: private Icon icon;
068:
069: /**
070: * The <code>Dock</code> this view is currently displayed in.
071: */
072: private Dock dock;
073:
074: /**
075: * The list of view/plug-in specific actions that are displayed in the
076: * pop-up menu.
077: */
078: private LinkedList actionList = new LinkedList();
079:
080: /**
081: * List of runnables to run when view gets closed.
082: */
083: private LinkedList closeRunnableList = new LinkedList();
084:
085: /*=======================================================================*/
086: /**
087: * Class Constructor.
088: *
089: * @param main the main application
090: * @param name the name of the view
091: * @param icon the optional icon, or <code>null</code>
092: */
093: public View(Main main, String name, Icon icon) {
094: this .main = main;
095: this .plugin = plugin;
096:
097: main.log("creating view " + name);
098:
099: // XXX view name must not contain '/':
100: int idx;
101: while ((idx = name.indexOf('/')) != -1)
102: name = name.substring(0, idx) + ':'
103: + name.substring(idx + 1);
104:
105: if (name.equals(""))
106: name = "view";
107:
108: setName(name);
109: setIcon(icon);
110: }
111:
112: /*=======================================================================*/
113: /**
114: * Return the GUI component. This is what is displayed in the dock when
115: * this view's tab is in front. This method must return the same object
116: * each time it is called.
117: * <p>
118: * Care should be taken if the component is not a swing component, as
119: * heavyweight AWT components cause problems, especially when the docks
120: * are displayed in DESKTOP_MODE.
121: *
122: * @return a GUI component.
123: */
124: public abstract Component getComponent();
125:
126: /*=======================================================================*/
127: /**
128: * Get the popup menu. This is used to display a pop-up menu when the
129: * user right-clicks on this view's tab in the dock. It contains a set
130: * of standard actions, plus all the actions specific to this view.
131: *
132: * @return a popup menu
133: * @see #addAction
134: */
135: public final JPopupMenu getPopupMenu() {
136: // NOTE: always construct a new popup instance, because the dock
137: // may be adding it's own entries to the menu
138: JPopupMenu popup = new JPopupMenu();
139:
140: popup.add(getWarpMenu());
141:
142: if (actionList.size() > 0) {
143: popup.addSeparator();
144:
145: for (Iterator itr = actionList.iterator(); itr.hasNext();)
146: popup.add((Action) (itr.next()));
147: }
148:
149: popup.addSeparator();
150:
151: popup.add(new AbstractAction("Close") {
152: public void actionPerformed(ActionEvent evt) {
153: close();
154: }
155: });
156:
157: return popup;
158: }
159:
160: // XXX perhaps the code to handling the warp menu should be moved to the dock?
161: private JMenu getWarpMenu() {
162: JMenu menu = new JMenu("Warp To");
163:
164: class WarpAction extends AbstractAction {
165: private String name;
166:
167: WarpAction(String name) {
168: super (name);
169:
170: this .name = name;
171: }
172:
173: public void actionPerformed(ActionEvent evt) {
174: warpTo(name);
175: }
176: }
177:
178: for (Iterator itr = Dock.getDocks(); itr.hasNext();) {
179: Dock dock = ((Dock) (itr.next()));
180:
181: if (dock != getDock()) {
182: menu.add(new WarpAction(dock.getName()));
183: }
184: }
185:
186: menu.addSeparator();
187:
188: menu.add(new AbstractAction("New Dock...") {
189: public void actionPerformed(ActionEvent evt) {
190: OptionPane optionPane = new OptionPane(main
191: .getWindowManager().getDialog("Input"),
192: "Enter Dock Name", OptionPane.QUESTION_MESSAGE,
193: OptionPane.OK_CANCEL_OPTION);
194:
195: optionPane.setInitialSelectionValue(getName());
196: optionPane.setWantsInput(true);
197:
198: String dockName = (String) (optionPane.showModal());
199:
200: if (dockName != null) {
201: warpTo(dockName);
202: }
203: }
204: });
205:
206: return menu;
207: }
208:
209: private void warpTo(String dockName) {
210: getDock().removeView(this );
211: Dock.showView(this , dockName);
212: }
213:
214: /*=======================================================================*/
215: /**
216: * Called by {@link #close} in order to determine if this view can be
217: * closed. If this method returns <code>false</code> then the close is
218: * aborted. By default it always returns <code>true</code>, but can
219: * be overriden as needed.
220: *
221: * @return <code>true</code> to permit the view to be closed, and
222: * <code>false</code> to prevent it
223: */
224: public boolean isClosable() {
225: return true;
226: }
227:
228: /*=======================================================================*/
229: /**
230: * Called to cause this view to close. If the plugin needs to perform
231: * cleanup, it should override the {@link #closeHook} method which is
232: * called by this method.
233: *
234: * @returns <code>true</code> if the view was closed, else
235: * <code>false</code>
236: * @see #closeHook
237: */
238: public final boolean close() {
239: if (isClosable()) {
240: closeHook();
241:
242: // must copy to array, to avoid concurrent modification probs:
243: Runnable[] rs = (Runnable[]) closeRunnableList
244: .toArray(new Runnable[closeRunnableList.size()]);
245: for (int i = 0; i < rs.length; i++) {
246: rs[i].run();
247: closeRunnableList.remove(rs[i]);
248: }
249:
250: return true;
251: }
252:
253: return false;
254: }
255:
256: /*=======================================================================*/
257: /**
258: * This method can be overriden by the view implementation if needed. The
259: * default behaviour is a no-op.
260: *
261: * @see #close
262: * @deprecated see {@link #addCloseRunnable}
263: */
264: public void closeHook() {
265: // XXX probably should deprecate this and use the close-runnables
266: }
267:
268: /*=======================================================================*/
269: /**
270: * Make this view the one that is currently in focus.
271: */
272: public void toForeground() {
273: if (dock != null) {
274: Dialog dialog = dock.getDialog();
275: if (dialog != null)
276: dialog.toFront();
277: dock.setForegroundView(this );
278: }
279: }
280:
281: /*=======================================================================*/
282: /**
283: * Get the name of this view.
284: *
285: * @return the name
286: */
287: public String getName() {
288: return name;
289: }
290:
291: /*=======================================================================*/
292: /**
293: * The "descriptor" is a string that has both the name of the view and
294: * the name of the plugin owning the view encoded in it. The purpose is
295: * to uniquely identify a view amongst all views.
296: * <p>
297: * The method for the encoding is currently unspecified, and possibly
298: * subject to change.
299: *
300: * @return a string
301: */
302: public String getDescriptor() {
303: return ((getPlugin() == null) ? "anon" : getPlugin().getName())
304: + "::" + getName();
305: }
306:
307: /*=======================================================================*/
308: /**
309: * Set the name for this view.
310: *
311: * @param name the name of the view
312: */
313: public void setName(String name) {
314: if (name.equals(this .name))
315: return;
316:
317: if (name.indexOf('/') != -1)
318: throw new ti.exceptions.ProgrammingErrorException(
319: "Illegal view name: " + name);
320:
321: String oldName = this .name;
322:
323: this .name = name;
324:
325: if (dock != null)
326: dock.notifySetName(this , oldName, name);
327: }
328:
329: /*=======================================================================*/
330: /**
331: * Get the icon for this view.
332: *
333: * @return the icon, or <code>null</code> if no icon
334: */
335: public Icon getIcon() {
336: return icon;
337: }
338:
339: /*=======================================================================*/
340: /**
341: * Set the icon for this view.
342: *
343: * @param icon the optional icon, or <code>null</code>
344: */
345: public void setIcon(Icon icon) {
346: if (this .icon == icon)
347: return;
348:
349: Icon oldIcon = this .icon;
350:
351: this .icon = icon;
352:
353: if (dock != null)
354: dock.notifySetIcon(this , oldIcon, icon);
355: }
356:
357: /*=======================================================================*/
358: /**
359: * This can be called by the class implementing the view in order to
360: * add view and/or plug-in specific actions to this view's popup menu.
361: * <p>
362: * Note: this is only <code>public</code> in order for it to be accessible
363: * to scripts that subclass this class. You should use it as if it were
364: * <code>protected</code>.
365: *
366: * @param a an action, or <code>null</code> to add seperator
367: */
368: public void addAction(Action a) {
369: actionList.add(a);
370: }
371:
372: /*=======================================================================*/
373: /**
374: * This can be called by the class implementing the view in order to
375: * remove view and/or plug-in specific actions to this view's popup
376: * menu.
377: * <p>
378: * Note: this is only <code>public</code> in order for it to be accessible
379: * to scripts that subclass this class. You should use it as if it were
380: * <code>protected</code>.
381: *
382: * @param a an action, or <code>null</code> to add seperator
383: */
384: public void removeAction(Action a) {
385: actionList.remove(a);
386: }
387:
388: /*=======================================================================*/
389: /**
390: * Get the dock that this view is in.
391: *
392: * @return a dock, or <code>null</code> if this view isn't in a dock
393: */
394: public Dock getDock() {
395: return dock;
396: }
397:
398: /*=======================================================================*/
399: /**
400: * Set the dock this view is in. This is called by the <code>Dock</code>
401: * when the view is moved into the dock.
402: *
403: * @param dock the dock to move this view to
404: */
405: void setDock(Dock dock) {
406: this .dock = dock;
407: }
408:
409: /*=======================================================================*/
410: /**
411: * Set the plugin this view is from. This is normally called by the
412: * <code>Plugin</code> when the plugin is started.
413: *
414: * @param plugin the plugin to move this view to
415: */
416: void setPlugin(Plugin plugin) {
417: this .plugin = plugin;
418: }
419:
420: /*=======================================================================*/
421: /**
422: * Get the plugin this view owned by.
423: * @return the plugin
424: */
425: Plugin getPlugin() {
426: return plugin;
427: }
428:
429: /*=======================================================================*/
430: /**
431: * What to do when closing... this is sort of non-standard, but will
432: * do for now. I wouldn't suggest anyone use this method who isn't
433: * willing to go back and change their code when I change this.
434: */
435: public void addCloseRunnable(Runnable closeRunnable) {
436: closeRunnableList.add(closeRunnable);
437: }
438:
439: public void removeCloseRunnable(Runnable closeRunnable) {
440: closeRunnableList.remove(closeRunnable);
441: }
442:
443: public String toString() {
444: return "[view: " + getName() + "]";
445: }
446: }
447:
448: /*
449: * Local Variables:
450: * tab-width: 2
451: * indent-tabs-mode: nil
452: * mode: java
453: * c-indentation-style: java
454: * c-basic-offset: 2
455: * eval: (c-set-offset 'substatement-open '0)
456: * eval: (c-set-offset 'case-label '+)
457: * eval: (c-set-offset 'inclass '+)
458: * eval: (c-set-offset 'inline-open '0)
459: * End:
460: */
|