001 002/* ---------------------------------------------------------------------- 003 * 004 * Copyright (c) 2002-2009 The MITRE Corporation 005 * 006 * Except as permitted below 007 * ALL RIGHTS RESERVED 008 * 009 * The MITRE Corporation (MITRE) provides this software to you without 010 * charge to use for your internal purposes only. Any copy you make for 011 * such purposes is authorized provided you reproduce MITRE's copyright 012 * designation and this License in any such copy. You may not give or 013 * sell this software to any other party without the prior written 014 * permission of the MITRE Corporation. 015 * 016 * The government of the United States of America may make unrestricted 017 * use of this software. 018 * 019 * This software is the copyright work of MITRE. No ownership or other 020 * proprietary interest in this software is granted you other than what 021 * is granted in this license. 022 * 023 * Any modification or enhancement of this software must inherit this 024 * license, including its warranty disclaimers. You hereby agree to 025 * provide to MITRE, at no charge, a copy of any such modification or 026 * enhancement without limitation. 027 * 028 * MITRE IS PROVIDING THE PRODUCT "AS IS" AND MAKES NO WARRANTY, EXPRESS 029 * OR IMPLIED, AS TO THE ACCURACY, CAPABILITY, EFFICIENCY, 030 * MERCHANTABILITY, OR FUNCTIONING OF THIS SOFTWARE AND DOCUMENTATION. IN 031 * NO EVENT WILL MITRE BE LIABLE FOR ANY GENERAL, CONSEQUENTIAL, 032 * INDIRECT, INCIDENTAL, EXEMPLARY OR SPECIAL DAMAGES, EVEN IF MITRE HAS 033 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 034 * 035 * You accept this software on the condition that you indemnify and hold 036 * harmless MITRE, its Board of Trustees, officers, agents, and 037 * employees, from any and all liability or damages to third parties, 038 * including attorneys' fees, court costs, and other related costs and 039 * expenses, arising out of your use of this software irrespective of the 040 * cause of said liability. 041 * 042 * The export from the United States or the subsequent reexport of this 043 * software is subject to compliance with United States export control 044 * and munitions control restrictions. You agree that in the event you 045 * seek to export this software you assume full responsibility for 046 * obtaining all necessary export licenses and approvals and for assuring 047 * compliance with applicable reexport restrictions. 048 * 049 * ---------------------------------------------------------------------- 050 * 051 * NOTICE 052 * 053 * This software was produced for the U. S. Government 054 * under Contract No. W15P7T-09-C-F600, and is 055 * subject to the Rights in Noncommercial Computer Software 056 * and Noncommercial Computer Software Documentation 057 * Clause 252.227-7014 (JUN 1995). 058 * 059 * (c) 2009 The MITRE Corporation. All Rights Reserved. 060 * 061 * ---------------------------------------------------------------------- 062 * 063 */ 064/* 065 * Copyright (c) 2002-2006 The MITRE Corporation 066 * 067 * Except as permitted below 068 * ALL RIGHTS RESERVED 069 * 070 * The MITRE Corporation (MITRE) provides this software to you without 071 * charge to use for your internal purposes only. Any copy you make for 072 * such purposes is authorized provided you reproduce MITRE's copyright 073 * designation and this License in any such copy. You may not give or 074 * sell this software to any other party without the prior written 075 * permission of the MITRE Corporation. 076 * 077 * The government of the United States of America may make unrestricted 078 * use of this software. 079 * 080 * This software is the copyright work of MITRE. No ownership or other 081 * proprietary interest in this software is granted you other than what 082 * is granted in this license. 083 * 084 * Any modification or enhancement of this software must inherit this 085 * license, including its warranty disclaimers. You hereby agree to 086 * provide to MITRE, at no charge, a copy of any such modification or 087 * enhancement without limitation. 088 * 089 * MITRE IS PROVIDING THE PRODUCT "AS IS" AND MAKES NO WARRANTY, EXPRESS 090 * OR IMPLIED, AS TO THE ACCURACY, CAPABILITY, EFFICIENCY, 091 * MERCHANTABILITY, OR FUNCTIONING OF THIS SOFTWARE AND DOCUMENTATION. IN 092 * NO EVENT WILL MITRE BE LIABLE FOR ANY GENERAL, CONSEQUENTIAL, 093 * INDIRECT, INCIDENTAL, EXEMPLARY OR SPECIAL DAMAGES, EVEN IF MITRE HAS 094 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 095 * 096 * You accept this software on the condition that you indemnify and hold 097 * harmless MITRE, its Board of Trustees, officers, agents, and 098 * employees, from any and all liability or damages to third parties, 099 * including attorneys' fees, court costs, and other related costs and 100 * expenses, arising out of your use of this software irrespective of the 101 * cause of said liability. 102 * 103 * The export from the United States or the subsequent reexport of this 104 * software is subject to compliance with United States export control 105 * and munitions control restrictions. You agree that in the event you 106 * seek to export this software you assume full responsibility for 107 * obtaining all necessary export licenses and approvals and for assuring 108 * compliance with applicable reexport restrictions. 109 */ 110 111package jmri.util.org.mitre.jawb.swing; 112 113import java.awt.*; 114import java.awt.event.*; 115import java.util.HashMap; 116import java.util.Iterator; 117 118import javax.swing.*; 119import javax.swing.event.MouseInputAdapter; 120 121// added as part of migration to JMRI 122import jmri.util.JmriJFrame; 123 124/** 125 * JTabbedPane implementation which allows tabbs to be 'torn off' as their own 126 * window. When the DetachableTabbedPane is set not visible using the 127 * 'setVisible' method, any detached tabs are also hidden. When set visible by 128 * the same means, previously detached, yet hidden tabs, are re-shown. 129 * 130 * @author <a href="mailto:red@mitre.org">Chadwick A. McHenry</a> 131 * @version 1.0 132 */ 133public class DetachableTabbedPane extends JTabbedPane { 134 135 /* multiple use Icons */ 136 private static Icon plainIcon = new DetachPanelIcon (false); 137 private static Icon pressedIcon = new DetachPanelIcon (true); 138 /* map panels to their Detachable objects 139 * @see #Detachable */ 140 protected HashMap<Component, Detachable> panelToDetMap = new HashMap<Component, Detachable>(); 141 142 /** 143 * Indicates whether the tabs in this TabbedPane are actually detachable, 144 * or just behave normally 145 */ 146 protected boolean detachable = true; 147 148 /** Prettify the detached tabbs */ 149 protected Image detachedIconImage = null; 150 151 String titleSuffix = ": Foon"; 152 153 /** 154 * Creates an empty <code>DetachableTabbedPane</code> with a default tab 155 * placement of <code>JTabbedPane.TOP</code> and detachability on. 156 */ 157 public DetachableTabbedPane () { 158 super (); 159 init (); 160 } 161 162 public DetachableTabbedPane (String titleSuffix) { 163 super (); 164 init (); 165 this.titleSuffix = titleSuffix; 166 } 167 168 /** 169 * Creates an empty <code>DetachableTabbedPane</code> with the specified tab 170 * placement of either: <code>JTabbedPane.TOP</code>, 171 * <code>JTabbedPane.BOTTOM</code>, <code>JTabbedPane.LEFT</code>, or 172 * <code>JTabbedPane.RIGHT</code>, and specified detachability. 173 * @param tabPlacement tab placement 174 * @param detachable true if detachable 175 */ 176 public DetachableTabbedPane (int tabPlacement, boolean detachable) { 177 super (tabPlacement); 178 this.detachable = detachable; 179 init (); 180 } 181 182 /** 183 * Creates an empty <code>DetachableTabbedPane</code> with the specified tab 184 * placement and tab layout policy. 185 * @param tabPlacement tab placement 186 * @param tabLayoutPolicy tab layout policy 187 * @param detachable true if detachable 188 */ 189 public DetachableTabbedPane (int tabPlacement, int tabLayoutPolicy, 190 boolean detachable) { 191 super (tabPlacement, tabLayoutPolicy); 192 this.detachable = detachable; 193 init (); 194 } 195 196 /** Code common to all constructors. */ 197 private void init () { 198 // retrieve the current mouse listeners (put in by L&F) and remove them 199 // from the standard dispatcher so we can filter some events 200 final MouseListener[] mListeners = getMouseListeners (); 201 for (int i=0; i<mListeners.length; i++) 202 removeMouseListener (mListeners[i]); 203 204 // this will forward mouse events to the little detach buttons and 205 // all the look and feel listeners (since we want to filter some) 206 MouseInputAdapter ma = new MouseInputAdapter () { 207 Detachable last = null; 208 // Returns a Detachable only if the mouse event is within the 209 // detachable's icon 210 private Detachable getDetachable (MouseEvent e) { 211 if (last != null && last.contains (e.getX(), e.getY())) 212 return last; 213 214 last = null; 215 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 216 while (iter.hasNext ()) { 217 Detachable d = iter.next(); 218 if (d.contains (e.getX(), e.getY())) { 219 last = d; 220 break; 221 } 222 } 223 return last; 224 } 225 @Override 226 public void mouseMoved (MouseEvent e) { 227 Detachable old = last; 228 Detachable d = getDetachable (e); 229 if (old != d) { 230 if (old != null) { 231 old.setPressed (false); 232 old.repaint (); 233 } 234 if (d != null) { 235 d.setPressed (true); 236 d.repaint (); 237 } 238 } 239 } 240 @Override 241 public void mouseClicked (MouseEvent e) { 242 Detachable d = getDetachable (e); 243 last = null; 244 if (d != null) { 245 detach (d); 246 d.setPressed (false); 247 // filter the event from the other handlers 248 return; 249 } 250 // not 'contained' within a detachable? pass it on 251 for (int i=0; i<mListeners.length; i++) 252 mListeners[i].mouseClicked (e); 253 } 254 @Override 255 public void mouseExited (MouseEvent e) { 256 if (last != null) { 257 last.setPressed (false); 258 last.repaint (); 259 } 260 last = null; 261 // no filtering 262 for (int i=0; i<mListeners.length; i++) 263 mListeners[i].mouseExited (e); 264 } 265 @Override 266 public void mouseEntered (MouseEvent e) { 267 // no filtering 268 for (int i=0; i<mListeners.length; i++) 269 mListeners[i].mouseEntered (e); 270 } 271 @Override 272 public void mousePressed (MouseEvent e) { 273 // filter from the other handlers so it doesn't 'change tabs' 274 if (getDetachable (e) != null) 275 return; 276 // not 'contained' within a detachable? pass it on 277 for (int i=0; i<mListeners.length; i++) 278 mListeners[i].mousePressed (e); 279 } 280 @Override 281 public void mouseReleased (MouseEvent e) { 282 // no filtering 283 for (int i=0; i<mListeners.length; i++) 284 mListeners[i].mouseReleased (e); 285 } 286 }; 287 addMouseListener (ma); 288 addMouseMotionListener (ma); 289 } 290 291 public void setDetachedIconImage (Image image) { 292 detachedIconImage = image; 293 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 294 while (iter.hasNext ()) { 295 Detachable d = iter.next(); 296 d.getFrame ().setIconImage (detachedIconImage); 297 } 298 } 299 300 public Image getDetachedIconImage () { 301 return detachedIconImage; 302 } 303 304 /** 305 * Returns the default Detachable. 306 * @param title title 307 * @param icon icon 308 * @param comp component 309 * @param tip tool tip 310 * @param index index 311 * @param titleSuffix title suffix 312 * @return default Detachable 313 */ 314 protected Detachable createDetachable (String title, Icon icon, 315 Component comp, 316 String tip, int index, String titleSuffix) { 317 return new Detachable (title, icon, comp, tip, index, titleSuffix); 318 } 319 320 /** 321 * Lookup the Detachable for the specified component, which must have been 322 * added as a tab. Returns null if not already added. 323 * @param comp component 324 * @return Returns null if not already added 325 */ 326 protected Detachable getDetachable(Component comp) { 327 return panelToDetMap.get(comp); 328 } 329 330 /** 331 * Return Detachables which have been added as Tabs or Detached Frames. TODO: 332 * Currently, order is not accurate. 333 * @return Detachables 334 */ 335 protected Detachable[] getDetachables() { 336 return panelToDetMap.values().toArray(new Detachable[0]); 337 } 338 339 /** 340 * Overridden to add our 'detach' icon. All the <code>add</code> and 341 * <code>addTab</code> methods are cover methods for <code>insertTab</code>. 342 */ 343 @Override 344 public void insertTab (String title, Icon icon, Component comp, 345 String tip, int index) { 346 // the index we get is based on the number of tabs show, not the number of 347 // components, so to remain consistent create the Detachable with an index 348 // based on the number of detachables we have 349 Detachable d = createDetachable (title, icon, comp, tip, 350 panelToDetMap.size(), titleSuffix); 351 d.getFrame ().setIconImage (detachedIconImage); 352 353 shiftDetachables (true, d); 354 panelToDetMap.put (comp, d); 355 356 if (detachable && d.isDetached ()) 357 detach (d); 358 else 359 attach (d); 360 } 361 362 /** 363 * Overridden to remove the comopnent from the possible list of components 364 * this pane displays 365 */ 366 @Override 367 public void remove (int index) { 368 remove(panelToDetMap.get (getComponentAt (index))); 369 } 370 @Override 371 public void remove (Component comp) { 372 Detachable detachable = panelToDetMap.get (comp); 373 if (detachable != null) 374 remove (detachable); 375 else 376 super.remove(comp); 377 } 378 private void remove (Detachable d) { 379 if (d != null) { 380 panelToDetMap.remove (d.component); 381 shiftDetachables (false, d); 382 super.remove (d.component); // ok even if not 'attached' 383 d.dispose (); 384 } 385 } 386 /** 387 * This keeps the order of the detachables correct when adding or replacing 388 * in the tabbed. 389 */ 390 private void shiftDetachables (boolean insert, Detachable cause) { 391 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 392 while (iter.hasNext ()) { 393 Detachable d = iter.next(); 394 if (d.index >= cause.index) 395 d.index += (insert ? 1 : -1); 396 } 397 } 398 399 /** 400 * Bypass remove and add internal panel to the tabbedpane 401 */ 402 private void detach (Detachable d) { 403 if (detachable) { 404 super.remove (d.component); // ok, even if not 'attached' yet 405 validate (); 406 d.setDetached (true); 407 } 408 } 409 410 /** 411 * Bypass insertTab and add internal panel to the tabbedpane 412 */ 413 private void attach (Detachable d) { 414 int ti; 415 for (ti=0; ti<getTabCount(); ti++) { 416 Detachable tabD = panelToDetMap.get (getComponentAt(ti)); 417 if (tabD.index > d.index) 418 break; 419 } 420 d.setDetached (false); 421 super.insertTab (d.title, d.icon, d.component, d.tip, ti); 422 validate (); 423 } 424 425 /** 426 * Overridden to hide or show the detached tabs as well. State is retained, 427 * so that if you hide this TabbedPane, the detached panels will be hidden, 428 * but when you re-show this TabbedPane, the detached panels will be 429 * re-shown in their last positions. 430 */ 431 @Override 432 public void setVisible (boolean show) { 433 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 434 while (iter.hasNext ()) { 435 Detachable d = iter.next(); 436 if (d.isDetached ()) 437 d.getFrame().setVisible (show); 438 } 439 } 440 441 public void setDetachable (boolean detachable) { 442 if (detachable == this.detachable) 443 return; 444 445 this.detachable = detachable; 446 //TODO: finish! 447 // if (detachable) { 448 // /* 449 // for each tab, set icon d.icon; 450 // */ 451 // } else { // !detachable 452 // /* 453 // for each detachable, close if open: 454 // for each tab, set icon d.userIcon; 455 // */ 456 // } 457 } 458 459 /** 460 * Icon which remembers where it was drawn last so that it can be queried 461 * with 'contains' requests. Needed because JTabbedPane won't let us put a 462 * component (like a button) in the tab itself. This ability will be 463 * included in a future Java release, but for now, I've got to do it by hand 464 * with this. 465 */ 466 private static class LocatedIcon implements Icon { 467 Icon icon; 468 int x, y; 469 LocatedIcon (Icon icon) { 470 this.icon = icon; 471 } 472 boolean contains (int cx, int cy) { 473 return (x <= cx) && (cx <= x+icon.getIconWidth()) && 474 (y <= cy) && (cy <= y+icon.getIconHeight()); 475 } 476 @Override 477 public int getIconHeight () { 478 return icon.getIconHeight(); 479 } 480 @Override 481 public int getIconWidth () { 482 return icon.getIconWidth(); 483 } 484 @Override 485 public void paintIcon (Component c, Graphics g, int px, int py) { 486 x = px; 487 y = py; 488 icon.paintIcon (c, g, x, y); 489 } 490 } 491 492 /** 493 * Class to maintain info for panels as they are added and removed, detached 494 * and attached from the <code>DetachableTabPane</code>. 495 */ 496 public class Detachable { 497 498 protected String title = null; // remember to reattach to tabbed pane 499 protected Icon icon = null; // possibly composite icon 500 protected Icon userIcon = null; // user supplied icon 501 protected Component component = null; // component to display 502 protected String tip = null; 503 protected int index = 0; 504 505 //transient Icon cachedIcon = null; 506 507 //DetachButton button = null; // displayed in tab for detaching 508 protected LocatedIcon button = null; // displayed in tab for detaching 509 protected JmriJFrame frame = null; // detached container 510 protected boolean detached = false; // keeps detached state if not visible 511 512 public Detachable (String title, Icon icon, 513 Component comp, String tip, int index, String titleSuffix) { 514 515 this.title = title; 516 this.userIcon = icon; 517 this.component = comp; 518 this.tip = tip; 519 this.index = index; 520 521 /* frame to display component when detached. */ 522 frame = new JmriJFrame ( (title!=null?title:comp.getName())+titleSuffix); 523 frame.makePrivateWindow(); 524 frame.addHelpMenu(null,true); 525 frame.addWindowListener (new WindowAdapter () { 526 @Override 527 public void windowClosing (WindowEvent e) { 528 DetachableTabbedPane.this.attach (Detachable.this); 529 } 530 }); 531 532 // put it in the frame to set frames initial sizing 533 frame.getContentPane ().add (component); 534 frame.validate (); 535 frame.pack (); 536 frame.getContentPane ().remove (component); 537 // initially attached (added to tabbedPane at creation, so hide) 538 frame.setVisible (false); 539 540 button = new LocatedIcon (plainIcon); 541 // create composite if neccissary 542 if (userIcon != null) 543 this.icon = new CompositeIcon (button, userIcon); 544 else 545 this.icon = button; 546 } 547 548 public JFrame getFrame () { 549 return frame; 550 } 551 552 public Component getComponent () { 553 return component; 554 } 555 556 public String getTitle () { 557 return title; 558 } 559 560 public void setPressed (boolean pressed) { 561 if (pressed) 562 button.icon = pressedIcon; 563 else 564 button.icon = plainIcon; 565 repaint (); 566 } 567 568 public void repaint () { 569 DetachableTabbedPane.this. 570 repaint (0, button.x, button.y, 571 button.getIconWidth(),button.getIconHeight()); 572 } 573 574 public boolean isDetached () { 575 return detached; 576 } 577 578 public void setDetached (boolean detached) { 579 this.detached = detached; 580 581 if (detached && /*tabbedPane*/ isVisible () && ! frame.isVisible ()) { 582 // removeTabAt doesn't even set it visible again in java 1.3, so do it 583 // by hand 584 component.setVisible (true); 585 frame.getContentPane().add (component, BorderLayout.CENTER); 586 587 frame.makePublicWindow(); 588 589 // some window managers like to reposition windows. Don't let 'em 590 Rectangle bounds = frame.getBounds(); 591 592 // don't pack again, so it remains the size the user chose before 593 frame.setVisible (true); 594 frame.setBounds (bounds); 595 frame.validate (); 596 597 } else if (! detached && frame.isVisible()) { 598 frame.setVisible (false); 599 frame.getContentPane().removeAll (); 600 frame.makePrivateWindow(); 601 } 602 } 603 604 public void dispose () { 605 frame.dispose (); 606 } 607 608 public boolean contains (int x, int y) { 609 return (! detached && button.contains (x, y)); 610 } 611 } 612 613 /** Testing */ 614// public static void main(String s[]) { 615// JFrame frame = new JFrame("Annotation Editor Panel Demo"); 616// 617// frame.addWindowListener(new WindowAdapter() { 618// @Override 619// public void windowClosing(WindowEvent e) {System.exit(0);} 620// }); 621// 622// DetachableTabbedPane aep = new DetachableTabbedPane (); 623// 624// // add some tabs 625// aep.add ("One", new JLabel ("One")); 626// aep.add ("Two", new JLabel ("Two")); 627// 628// frame.getContentPane().add(aep); 629// frame.pack(); 630// frame.setVisible(true); 631// } 632 633}