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 javax.swing.*; 116import javax.swing.event.MouseInputAdapter; 117import java.util.HashMap; 118import java.util.Iterator; 119 120// added as part of migration to JMRI 121import jmri.util.JmriJFrame; 122 123/** 124 * JTabbedPane implementation which allows tabbs to be 'torn off' as their own 125 * window. When the DetachableTabbedPane is set not visible using the 126 * 'setVisible' method, any detached tabs are also hidden. When set visible by 127 * the same means, previously detached, yet hidden tabs, are re-shown. 128 * 129 * @author <a href="mailto:red@mitre.org">Chadwick A. McHenry</a> 130 * @version 1.0 131 */ 132public class DetachableTabbedPane extends JTabbedPane { 133 134 /* multiple use Icons */ 135 private static Icon plainIcon = new DetachPanelIcon (false); 136 private static Icon pressedIcon = new DetachPanelIcon (true); 137 /* map panels to their Detachable objects 138 * @see #Detachable */ 139 protected HashMap<Component, Detachable> panelToDetMap = new HashMap<Component, Detachable>(); 140 141 /** 142 * Indicates whether the tabs in this TabbedPane are actually detachable, 143 * or just behave normally 144 */ 145 protected boolean detachable = true; 146 147 /** Prettify the detached tabbs */ 148 protected Image detachedIconImage = null; 149 150 String titleSuffix = ": Foon"; 151 152 /** 153 * Creates an empty <code>DetachableTabbedPane</code> with a default tab 154 * placement of <code>JTabbedPane.TOP</code> and detachability on. 155 */ 156 public DetachableTabbedPane () { 157 super (); 158 init (); 159 } 160 161 public DetachableTabbedPane (String titleSuffix) { 162 super (); 163 init (); 164 this.titleSuffix = titleSuffix; 165 } 166 167 /** 168 * Creates an empty <code>DetachableTabbedPane</code> with the specified tab 169 * placement of either: <code>JTabbedPane.TOP</code>, 170 * <code>JTabbedPane.BOTTOM</code>, <code>JTabbedPane.LEFT</code>, or 171 * <code>JTabbedPane.RIGHT</code>, and specified detachability. 172 */ 173 public DetachableTabbedPane (int tabPlacement, boolean detachable) { 174 super (tabPlacement); 175 this.detachable = detachable; 176 init (); 177 } 178 179 /** 180 * Creates an empty <code>DetachableTabbedPane</code> with the specified tab 181 * placement and tab layout policy. 182 */ 183 public DetachableTabbedPane (int tabPlacement, int tabLayoutPolicy, 184 boolean detachable) { 185 super (tabPlacement, tabLayoutPolicy); 186 this.detachable = detachable; 187 init (); 188 } 189 190 /** Code common to all constructors. */ 191 private void init () { 192 // retrieve the current mouse listeners (put in by L&F) and remove them 193 // from the standard dispatcher so we can filter some events 194 final MouseListener[] mListeners = getMouseListeners (); 195 for (int i=0; i<mListeners.length; i++) 196 removeMouseListener (mListeners[i]); 197 198 // this will forward mouse events to the little detach buttons and 199 // all the look and feel listeners (since we want to filter some) 200 MouseInputAdapter ma = new MouseInputAdapter () { 201 Detachable last = null; 202 // Returns a Detachable only if the mouse event is within the 203 // detachable's icon 204 private Detachable getDetachable (MouseEvent e) { 205 if (last != null && last.contains (e.getX(), e.getY())) 206 return last; 207 208 last = null; 209 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 210 while (iter.hasNext ()) { 211 Detachable d = iter.next(); 212 if (d.contains (e.getX(), e.getY())) { 213 last = d; 214 break; 215 } 216 } 217 return last; 218 } 219 @Override 220 public void mouseMoved (MouseEvent e) { 221 Detachable old = last; 222 Detachable d = getDetachable (e); 223 if (old != d) { 224 if (old != null) { 225 old.setPressed (false); 226 old.repaint (); 227 } 228 if (d != null) { 229 d.setPressed (true); 230 d.repaint (); 231 } 232 } 233 } 234 @Override 235 public void mouseClicked (MouseEvent e) { 236 Detachable d = getDetachable (e); 237 last = null; 238 if (d != null) { 239 detach (d); 240 d.setPressed (false); 241 // filter the event from the other handlers 242 return; 243 } 244 // not 'contained' within a detachable? pass it on 245 for (int i=0; i<mListeners.length; i++) 246 mListeners[i].mouseClicked (e); 247 } 248 @Override 249 public void mouseExited (MouseEvent e) { 250 if (last != null) { 251 last.setPressed (false); 252 last.repaint (); 253 } 254 last = null; 255 // no filtering 256 for (int i=0; i<mListeners.length; i++) 257 mListeners[i].mouseExited (e); 258 } 259 @Override 260 public void mouseEntered (MouseEvent e) { 261 // no filtering 262 for (int i=0; i<mListeners.length; i++) 263 mListeners[i].mouseEntered (e); 264 } 265 @Override 266 public void mousePressed (MouseEvent e) { 267 // filter from the other handlers so it doesn't 'change tabs' 268 if (getDetachable (e) != null) 269 return; 270 // not 'contained' within a detachable? pass it on 271 for (int i=0; i<mListeners.length; i++) 272 mListeners[i].mousePressed (e); 273 } 274 @Override 275 public void mouseReleased (MouseEvent e) { 276 // no filtering 277 for (int i=0; i<mListeners.length; i++) 278 mListeners[i].mouseReleased (e); 279 } 280 }; 281 addMouseListener (ma); 282 addMouseMotionListener (ma); 283 } 284 285 public void setDetachedIconImage (Image image) { 286 detachedIconImage = image; 287 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 288 while (iter.hasNext ()) { 289 Detachable d = iter.next(); 290 d.getFrame ().setIconImage (detachedIconImage); 291 } 292 } 293 294 public Image getDetachedIconImage () { 295 return detachedIconImage; 296 } 297 298 /** 299 * Returns the default Detachable. 300 */ 301 protected Detachable createDetachable (String title, Icon icon, 302 Component comp, 303 String tip, int index, String titleSuffix) { 304 return new Detachable (title, icon, comp, tip, index, titleSuffix); 305 } 306 307 /** 308 * Lookup the Detachable for the specified component, which must have been 309 * added as a tab. Returns null if not already added. 310 */ 311 protected Detachable getDetachable(Component comp) { 312 return panelToDetMap.get(comp); 313 } 314 315 /** 316 * Return Detachables which have been added as Tabs or Detached Frames. TODO: 317 * Currently, order is not accurate. 318 */ 319 protected Detachable[] getDetachables() { 320 return panelToDetMap.values().toArray(new Detachable[0]); 321 } 322 323 /** 324 * Overridden to add our 'detach' icon. All the <code>add</code> and 325 * <code>addTab</code> methods are cover methods for <code>insertTab</code>. 326 */ 327 @Override 328 public void insertTab (String title, Icon icon, Component comp, 329 String tip, int index) { 330 // the index we get is based on the number of tabs show, not the number of 331 // components, so to remain consistent create the Detachable with an index 332 // based on the number of detachables we have 333 Detachable d = createDetachable (title, icon, comp, tip, 334 panelToDetMap.size(), titleSuffix); 335 d.getFrame ().setIconImage (detachedIconImage); 336 337 shiftDetachables (true, d); 338 panelToDetMap.put (comp, d); 339 340 if (detachable && d.isDetached ()) 341 detach (d); 342 else 343 attach (d); 344 } 345 346 /** 347 * Overridden to remove the comopnent from the possible list of components 348 * this pane displays 349 */ 350 @Override 351 public void remove (int index) { 352 remove(panelToDetMap.get (getComponentAt (index))); 353 } 354 @Override 355 public void remove (Component comp) { 356 Detachable detachable = panelToDetMap.get (comp); 357 if (detachable != null) 358 remove (detachable); 359 else 360 super.remove(comp); 361 } 362 private void remove (Detachable d) { 363 if (d != null) { 364 panelToDetMap.remove (d.component); 365 shiftDetachables (false, d); 366 super.remove (d.component); // ok even if not 'attached' 367 d.dispose (); 368 } 369 } 370 /** 371 * This keeps the order of the detachables correct when adding or replacing 372 * in the tabbed. 373 */ 374 private void shiftDetachables (boolean insert, Detachable cause) { 375 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 376 while (iter.hasNext ()) { 377 Detachable d = iter.next(); 378 if (d.index >= cause.index) 379 d.index += (insert ? 1 : -1); 380 } 381 } 382 383 /** 384 * Bypass remove and add internal panel to the tabbedpane 385 */ 386 private void detach (Detachable d) { 387 if (detachable) { 388 super.remove (d.component); // ok, even if not 'attached' yet 389 validate (); 390 d.setDetached (true); 391 } 392 } 393 394 /** 395 * Bypass insertTab and add internal panel to the tabbedpane 396 */ 397 private void attach (Detachable d) { 398 int ti; 399 for (ti=0; ti<getTabCount(); ti++) { 400 Detachable tabD = panelToDetMap.get (getComponentAt(ti)); 401 if (tabD.index > d.index) 402 break; 403 } 404 d.setDetached (false); 405 super.insertTab (d.title, d.icon, d.component, d.tip, ti); 406 validate (); 407 } 408 409 /** 410 * Overridden to hide or show the detached tabs as well. State is retained, 411 * so that if you hide this TabbedPane, the detached panels will be hidden, 412 * but when you re-show this TabbedPane, the detached panels will be 413 * re-shown in their last positions. 414 */ 415 @Override 416 public void setVisible (boolean show) { 417 Iterator<Detachable> iter = panelToDetMap.values ().iterator (); 418 while (iter.hasNext ()) { 419 Detachable d = iter.next(); 420 if (d.isDetached ()) 421 d.getFrame().setVisible (show); 422 } 423 } 424 425 public void setDetachable (boolean detachable) { 426 if (detachable == this.detachable) 427 return; 428 429 this.detachable = detachable; 430 //TODO: finish! 431 // if (detachable) { 432 // /* 433 // for each tab, set icon d.icon; 434 // */ 435 // } else { // !detachable 436 // /* 437 // for each detachable, close if open: 438 // for each tab, set icon d.userIcon; 439 // */ 440 // } 441 } 442 443 /** 444 * Icon which remembers where it was drawn last so that it can be queried 445 * with 'contains' requests. Needed because JTabbedPane won't let us put a 446 * component (like a button) in the tab itself. This ability will be 447 * included in a future Java release, but for now, I've got to do it by hand 448 * with this. 449 */ 450 private static class LocatedIcon implements Icon { 451 Icon icon; 452 int x, y; 453 LocatedIcon (Icon icon) { 454 this.icon = icon; 455 } 456 boolean contains (int cx, int cy) { 457 return (x <= cx) && (cx <= x+icon.getIconWidth()) && 458 (y <= cy) && (cy <= y+icon.getIconHeight()); 459 } 460 public int getIconHeight () { 461 return icon.getIconHeight(); 462 } 463 public int getIconWidth () { 464 return icon.getIconWidth(); 465 } 466 public void paintIcon (Component c, Graphics g, int px, int py) { 467 x = px; 468 y = py; 469 icon.paintIcon (c, g, x, y); 470 } 471 } 472 473 /** 474 * Class to maintain info for panels as they are added and removed, detached 475 * and attached from the <code>DetachableTabPane</code>. 476 */ 477 public class Detachable { 478 479 protected String title = null; // remember to reattach to tabbed pane 480 protected Icon icon = null; // possibly composite icon 481 protected Icon userIcon = null; // user supplied icon 482 protected Component component = null; // component to display 483 protected String tip = null; 484 protected int index = 0; 485 486 //transient Icon cachedIcon = null; 487 488 //DetachButton button = null; // displayed in tab for detaching 489 protected LocatedIcon button = null; // displayed in tab for detaching 490 protected JmriJFrame frame = null; // detached container 491 protected boolean detached = false; // keeps detached state if not visible 492 493 public Detachable (String title, Icon icon, 494 Component comp, String tip, int index, String titleSuffix) { 495 496 this.title = title; 497 this.userIcon = icon; 498 this.component = comp; 499 this.tip = tip; 500 this.index = index; 501 502 /* frame to display component when detached. */ 503 this.frame = new JmriJFrame ( (title!=null?title:comp.getName())+titleSuffix); 504 frame.addWindowListener (new WindowAdapter () { 505 @Override 506 public void windowClosing (WindowEvent e) { 507 DetachableTabbedPane.this.attach (Detachable.this); 508 } 509 }); 510 511 // put it in the frame to set frames initial sizing 512 frame.getContentPane ().add (component); 513 frame.validate (); 514 frame.pack (); 515 frame.getContentPane ().remove (component); 516 // initially attached (added to tabbedPane at creation, so hide) 517 frame.setVisible (false); 518 519 button = new LocatedIcon (plainIcon); 520 // create composite if neccissary 521 if (userIcon != null) 522 this.icon = new CompositeIcon (button, userIcon); 523 else 524 this.icon = button; 525 } 526 527 public JFrame getFrame () { 528 return frame; 529 } 530 531 public Component getComponent () { 532 return component; 533 } 534 535 public String getTitle () { 536 return title; 537 } 538 539 public void setPressed (boolean pressed) { 540 if (pressed) 541 button.icon = pressedIcon; 542 else 543 button.icon = plainIcon; 544 repaint (); 545 } 546 547 public void repaint () { 548 DetachableTabbedPane.this. 549 repaint (0, button.x, button.y, 550 button.getIconWidth(),button.getIconHeight()); 551 } 552 553 public boolean isDetached () { 554 return detached; 555 } 556 557 public void setDetached (boolean detached) { 558 this.detached = detached; 559 560 if (detached && /*tabbedPane*/ isVisible () && ! frame.isVisible ()) { 561 // removeTabAt doesn't even set it visible again in java 1.3, so do it 562 // by hand 563 component.setVisible (true); 564 frame.getContentPane().add (component, BorderLayout.CENTER); 565 566 // some window managers like to reposition windows. Don't let 'em 567 Rectangle bounds = frame.getBounds(); 568 569 // don't pack again, so it remains the size the user chose before 570 frame.setVisible (true); 571 frame.setBounds (bounds); 572 frame.validate (); 573 574 } else if (! detached && frame.isVisible()) { 575 frame.setVisible (false); 576 frame.getContentPane().removeAll (); 577 } 578 } 579 580 public void dispose () { 581 frame.dispose (); 582 } 583 584 public boolean contains (int x, int y) { 585 return (! detached && button.contains (x, y)); 586 } 587 } 588 589 /** Testing */ 590// public static void main(String s[]) { 591// JFrame frame = new JFrame("Annotation Editor Panel Demo"); 592// 593// frame.addWindowListener(new WindowAdapter() { 594// @Override 595// public void windowClosing(WindowEvent e) {System.exit(0);} 596// }); 597// 598// DetachableTabbedPane aep = new DetachableTabbedPane (); 599// 600// // add some tabs 601// aep.add ("One", new JLabel ("One")); 602// aep.add ("Two", new JLabel ("Two")); 603// 604// frame.getContentPane().add(aep); 605// frame.pack(); 606// frame.setVisible(true); 607// } 608 609}