001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas/ 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see https://www.gnu.org/licenses/. 027 */ 028 029package edu.wisc.ssec.mcidasv.util; 030 031import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 032import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newHashSet; 033 034import static javax.swing.GroupLayout.DEFAULT_SIZE; 035import static javax.swing.GroupLayout.PREFERRED_SIZE; 036import static javax.swing.GroupLayout.Alignment.BASELINE; 037import static javax.swing.GroupLayout.Alignment.LEADING; 038import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 039 040import java.awt.Color; 041import java.awt.Component; 042import java.awt.Container; 043import java.awt.Dimension; 044import java.awt.Font; 045import java.awt.Graphics; 046import java.awt.Image; 047import java.awt.Rectangle; 048import java.awt.event.HierarchyEvent; 049import java.lang.reflect.InvocationTargetException; 050import java.lang.reflect.Method; 051import java.util.ArrayList; 052import java.util.Collection; 053import java.util.Collections; 054import java.util.EventObject; 055import java.util.HashMap; 056import java.util.HashSet; 057import java.util.List; 058import java.util.Map; 059import java.util.Map.Entry; 060import java.util.Set; 061import java.util.concurrent.Callable; 062import java.util.concurrent.ExecutionException; 063import java.util.concurrent.FutureTask; 064import java.util.concurrent.RunnableFuture; 065import java.util.regex.Pattern; 066 067import javax.swing.GroupLayout; 068import javax.swing.GroupLayout.ParallelGroup; 069import javax.swing.GroupLayout.SequentialGroup; 070import javax.swing.ImageIcon; 071import javax.swing.JButton; 072import javax.swing.JComboBox; 073import javax.swing.JComponent; 074import javax.swing.JLabel; 075import javax.swing.JMenuItem; 076import javax.swing.JPanel; 077import javax.swing.JTextField; 078import javax.swing.SwingConstants; 079import javax.swing.SwingUtilities; 080import javax.swing.UIDefaults; 081import javax.swing.text.JTextComponent; 082 083import org.slf4j.Logger; 084import org.slf4j.LoggerFactory; 085 086import ucar.unidata.idv.MapViewManager; 087import ucar.unidata.idv.ViewManager; 088import ucar.unidata.idv.ui.IdvComponentGroup; 089import ucar.unidata.idv.ui.IdvComponentHolder; 090import ucar.unidata.idv.ui.IdvWindow; 091import ucar.unidata.idv.ui.WindowInfo; 092import ucar.unidata.ui.ComponentHolder; 093import ucar.unidata.ui.MultiFrame; 094import ucar.unidata.util.GuiUtils; 095 096import edu.wisc.ssec.mcidasv.Constants; 097import edu.wisc.ssec.mcidasv.McIDASV; 098import edu.wisc.ssec.mcidasv.ViewManagerManager; 099import edu.wisc.ssec.mcidasv.ui.McvComponentGroup; 100import edu.wisc.ssec.mcidasv.ui.McvComponentHolder; 101import edu.wisc.ssec.mcidasv.ui.UIManager; 102 103/** 104 * McIDAS-V's collection of GUI utility methods 105 */ 106public class McVGuiUtils implements Constants { 107 108 /** Logging object. */ 109 // TODO(jon): consider removing when testing is done 110 private static final Logger logger = 111 LoggerFactory.getLogger(McVGuiUtils.class); 112 113 /** 114 * Estimated number of {@link ucar.unidata.idv.ViewManager ViewManagers}. 115 * This value is only used as a last resort ({@link McIDASV#getStaticMcv()} failing). 116 */ 117 private static final int ESTIMATED_VM_COUNT = 32; 118 119 public enum Width { HALF, SINGLE, ONEHALF, DOUBLE, TRIPLE, QUADRUPLE, DOUBLEDOUBLE } 120 121 public enum Position { LEFT, RIGHT, CENTER } 122 123 public enum Prefer { TOP, BOTTOM, NEITHER } 124 125 public enum TextColor { NORMAL, STATUS } 126 127 private McVGuiUtils() {} 128 129 /** 130 * Use this class to create a panel with a background image 131 * @author davep 132 * 133 */ 134 public static class IconPanel extends JPanel { 135 private Image img; 136 137 public IconPanel(String img) { 138 this(GuiUtils.getImageIcon(img).getImage()); 139 } 140 141 public IconPanel(Image img) { 142 this.img = img; 143 Dimension size = new Dimension(img.getWidth(null), img.getHeight(null)); 144 setPreferredSize(size); 145 setMinimumSize(size); 146 setMaximumSize(size); 147 setSize(size); 148 setLayout(null); 149 } 150 151 public void paintComponent(Graphics g) { 152 super.paintComponent(g); 153 g.drawImage(img, 0, 0, null); 154 } 155 156 } 157 158 /** 159 * Create a standard sized, right-justified label 160 * 161 * @param title Label text. Should not be {@code null}. 162 * 163 * @return A new label. 164 */ 165 public static JLabel makeLabelRight(String title) { 166 return makeLabelRight(title, null); 167 } 168 169 public static JLabel makeLabelRight(String title, Width width) { 170 if (width == null) { 171 width = Width.SINGLE; 172 } 173 JLabel newLabel = new JLabel(title); 174 setComponentWidth(newLabel, width); 175 setLabelPosition(newLabel, Position.RIGHT); 176 return newLabel; 177 } 178 179 /** 180 * Create a standard sized, left-justified label. 181 * 182 * @param title Label text. Should not be {@code null}. 183 * 184 * @return A new label. 185 */ 186 public static JLabel makeLabelLeft(String title) { 187 return makeLabelLeft(title, null); 188 } 189 190 public static JLabel makeLabelLeft(String title, Width width) { 191 if (width == null) { 192 width = Width.SINGLE; 193 } 194 JLabel newLabel = new JLabel(title); 195 setComponentWidth(newLabel, width); 196 setLabelPosition(newLabel, Position.LEFT); 197 return newLabel; 198 } 199 200 /** 201 * Create a sized, labeled component. 202 * 203 * @param label Label for {@code thing}. Should not be {@code null}. 204 * @param thing Component to label. Should not be {@code null}. 205 * 206 * @return A component with its label to the right. 207 */ 208 public static JPanel makeLabeledComponent(String label, JComponent thing) { 209 return makeLabeledComponent(makeLabelRight(label), thing); 210 } 211 212 public static JPanel makeLabeledComponent(JLabel label, JComponent thing) { 213 return makeLabeledComponent(label, thing, Position.RIGHT); 214 } 215 216 public static JPanel makeLabeledComponent(String label, JComponent thing, Position position) { 217 return makeLabeledComponent(new JLabel(label), thing, position); 218 } 219 220 public static JPanel makeLabeledComponent(JLabel label, JComponent thing, Position position) { 221 JPanel newPanel = new JPanel(); 222 223 if (position == Position.RIGHT) { 224 setComponentWidth(label); 225 setLabelPosition(label, Position.RIGHT); 226 } 227 228 GroupLayout layout = new GroupLayout(newPanel); 229 newPanel.setLayout(layout); 230 layout.setHorizontalGroup( 231 layout.createParallelGroup(LEADING) 232 .addGroup(layout.createSequentialGroup() 233 .addComponent(label) 234 .addGap(GAP_RELATED) 235 .addComponent(thing)) 236 ); 237 layout.setVerticalGroup( 238 layout.createParallelGroup(LEADING) 239 .addGroup(layout.createParallelGroup(BASELINE) 240 .addComponent(label) 241 .addComponent(thing)) 242 ); 243 return newPanel; 244 } 245 246 /** 247 * Create a sized, labeled component. 248 * 249 * @param thing Component to label. Should not be {@code null}. 250 * @param label Label for {@code thing}. Should not be {@code null}. 251 * 252 * @return A labeled component. 253 */ 254 public static JPanel makeComponentLabeled(JComponent thing, String label) { 255 return makeComponentLabeled(thing, new JLabel(label)); 256 } 257 258 public static JPanel makeComponentLabeled(JComponent thing, String label, Position position) { 259 return makeComponentLabeled(thing, new JLabel(label), position); 260 } 261 262 public static JPanel makeComponentLabeled(JComponent thing, JLabel label) { 263 return makeComponentLabeled(thing, label, Position.LEFT); 264 } 265 266 public static JPanel makeComponentLabeled(JComponent thing, JLabel label, Position position) { 267 JPanel newPanel = new JPanel(); 268 269 if (position == Position.RIGHT) { 270 setComponentWidth(label); 271 setLabelPosition(label, Position.RIGHT); 272 } 273 274 GroupLayout layout = new GroupLayout(newPanel); 275 newPanel.setLayout(layout); 276 layout.setHorizontalGroup( 277 layout.createParallelGroup(LEADING) 278 .addGroup(layout.createSequentialGroup() 279 .addComponent(thing) 280 .addGap(GAP_RELATED) 281 .addComponent(label)) 282 ); 283 layout.setVerticalGroup( 284 layout.createParallelGroup(LEADING) 285 .addGroup(layout.createParallelGroup(BASELINE) 286 .addComponent(thing) 287 .addComponent(label)) 288 ); 289 return newPanel; 290 } 291 292 /** 293 * Set the width of an existing component. 294 * 295 * @param existingComponent Component that will have its width set. 296 */ 297 public static void setComponentWidth(JComponent existingComponent) { 298 setComponentWidth(existingComponent, Width.SINGLE); 299 } 300 301 /** 302 * Set the width of an existing component using standard McIDAS-V component 303 * widths. 304 * 305 * @param existingComponent Component that will have its width set. 306 * @param width Width to use for {@code existingComponent}. 307 */ 308 public static void setComponentWidth(JComponent existingComponent, Width width) { 309 if (width == null) { 310 width = Width.SINGLE; 311 } 312 313 int componentWidth; 314 switch (width) { 315 case HALF: 316 componentWidth = ELEMENT_HALF_WIDTH; 317 break; 318 case SINGLE: 319 componentWidth = ELEMENT_WIDTH; 320 break; 321 case ONEHALF: 322 componentWidth = ELEMENT_ONEHALF_WIDTH; 323 break; 324 case DOUBLE: 325 componentWidth = ELEMENT_DOUBLE_WIDTH; 326 break; 327 case TRIPLE: 328 componentWidth = ELEMENT_DOUBLE_WIDTH + ELEMENT_WIDTH; 329 break; 330 case QUADRUPLE: 331 componentWidth = ELEMENT_DOUBLE_WIDTH + ELEMENT_DOUBLE_WIDTH; 332 break; 333 case DOUBLEDOUBLE: 334 componentWidth = ELEMENT_DOUBLEDOUBLE_WIDTH; 335 break; 336 default: 337 componentWidth = ELEMENT_WIDTH; 338 break; 339 } 340 setComponentWidth(existingComponent, componentWidth); 341 } 342 343 /** 344 * Set the width of an existing component to a given integer width. 345 * 346 * @param existingComponent Component that will have its width set. 347 * @param width Width to use for {@code existingComponent}. 348 */ 349 public static void setComponentWidth(JComponent existingComponent, int width) { 350 existingComponent.setMinimumSize(new Dimension(width, 24)); 351 existingComponent.setMaximumSize(new Dimension(width, 24)); 352 existingComponent.setPreferredSize(new Dimension(width, 24)); 353 } 354 355 /** 356 * Set the component width to that of another component. 357 */ 358 public static void setComponentWidth(JComponent setme, JComponent getme) { 359 setComponentWidth(setme, getme, 0); 360 } 361 362 public static void setComponentWidth(JComponent setme, JComponent getme, int padding) { 363 setme.setPreferredSize(new Dimension(getme.getPreferredSize().width + padding, getme.getPreferredSize().height)); 364 } 365 366 /** 367 * Set the component height to that of another component. 368 */ 369 public static void setComponentHeight(JComponent setme, JComponent getme) { 370 setComponentHeight(setme, getme, 0); 371 } 372 373 public static void setComponentHeight(JComponent setme, JComponent getme, int padding) { 374 setme.setPreferredSize(new Dimension(getme.getPreferredSize().width, getme.getPreferredSize().height + padding)); 375 } 376 377 /** 378 * Set the label position of an existing label 379 * @param existingLabel 380 */ 381 public static void setLabelPosition(JLabel existingLabel) { 382 setLabelPosition(existingLabel, Position.LEFT); 383 } 384 385 public static void setLabelPosition(JLabel existingLabel, Position position) { 386 switch (position) { 387 case LEFT: 388 existingLabel.setHorizontalTextPosition(SwingConstants.LEFT); 389 existingLabel.setHorizontalAlignment(SwingConstants.LEFT); 390 break; 391 392 case RIGHT: 393 existingLabel.setHorizontalTextPosition(SwingConstants.RIGHT); 394 existingLabel.setHorizontalAlignment(SwingConstants.RIGHT); 395 break; 396 397 case CENTER: 398 existingLabel.setHorizontalTextPosition(SwingConstants.CENTER); 399 existingLabel.setHorizontalAlignment(SwingConstants.CENTER); 400 break; 401 402 default: 403 existingLabel.setHorizontalTextPosition(SwingConstants.LEFT); 404 existingLabel.setHorizontalAlignment(SwingConstants.LEFT); 405 break; 406 } 407 } 408 409 /** 410 * Set the bold attribute of an existing label 411 * @param existingLabel 412 * @param bold 413 */ 414 public static void setLabelBold(JLabel existingLabel, boolean bold) { 415 Font f = existingLabel.getFont(); 416 if (bold) { 417 existingLabel.setFont(f.deriveFont(f.getStyle() ^ Font.BOLD)); 418 } else { 419 existingLabel.setFont(f.deriveFont(f.getStyle() | Font.BOLD)); 420 } 421 } 422 423 /** 424 * Set the foreground color of an existing component 425 * @param existingComponent 426 */ 427 public static void setComponentColor(JComponent existingComponent) { 428 setComponentColor(existingComponent, TextColor.NORMAL); 429 } 430 431 public static void setComponentColor(JComponent existingComponent, TextColor color) { 432 switch (color) { 433 case NORMAL: 434 existingComponent.setForeground(new Color(0, 0, 0)); 435 break; 436 437 case STATUS: 438 existingComponent.setForeground(MCV_BLUE_DARK); 439 break; 440 441 default: 442 existingComponent.setForeground(new Color(0, 0, 0)); 443 break; 444 } 445 } 446 447 /** 448 * Custom makeImageButton to ensure proper sizing and mouseborder are set 449 */ 450 public static JButton makeImageButton(String iconName, 451 final Object object, 452 final String methodName, 453 final Object arg, 454 final String tooltip 455 ) { 456 final JButton btn = makeImageButton(iconName, tooltip); 457 return (JButton)GuiUtils.addActionListener(btn, object, methodName, arg); 458 } 459 460 /** 461 * Custom makeImageButton to ensure proper sizing and mouseborder are set 462 */ 463 public static JButton makeImageButton(String iconName, String tooltip) { 464// boolean addMouseOverBorder = true; 465 466 ImageIcon imageIcon = GuiUtils.getImageIcon(iconName); 467 if (imageIcon.getIconWidth() > 22 || imageIcon.getIconHeight() > 22) { 468 Image scaledImage = imageIcon.getImage().getScaledInstance(22, 22, Image.SCALE_SMOOTH); 469 imageIcon = new ImageIcon(scaledImage); 470 } 471 472 final JButton btn = GuiUtils.getImageButton(imageIcon); 473 btn.setBackground(null); 474 btn.setContentAreaFilled(false); 475 btn.setSize(new Dimension(24, 24)); 476 btn.setPreferredSize(new Dimension(24, 24)); 477 btn.setMinimumSize(new Dimension(24, 24)); 478// if (addMouseOverBorder) { 479 GuiUtils.makeMouseOverBorder(btn); 480// } 481 btn.setToolTipText(tooltip); 482 return btn; 483 } 484 485 /** 486 * Create a button with text and an icon 487 */ 488 public static JButton makeImageTextButton(String iconName, String label) { 489 JButton newButton = new JButton(label); 490 setButtonImage(newButton, iconName); 491 return newButton; 492 } 493 494 /** 495 * Add an icon to a button... but only if the LookAndFeel supports it 496 */ 497 public static void setButtonImage(JButton existingButton, String iconName) { 498 // TODO: see if this is fixed in some future Apple Java release? 499 // When using Aqua look and feel don't use icons in the buttons 500 // Messes with the button vertical sizing 501 if (existingButton.getBorder().toString().indexOf("Aqua") > 0) { 502 return; 503 } 504 ImageIcon imageIcon = GuiUtils.getImageIcon(iconName); 505 existingButton.setIcon(imageIcon); 506 } 507 508 /** 509 * Add an icon to a menu item 510 */ 511 public static void setMenuImage(JMenuItem existingMenuItem, String iconName) { 512 ImageIcon imageIcon = GuiUtils.getImageIcon(iconName); 513 existingMenuItem.setIcon(imageIcon); 514 } 515 516 public static <E> JComboBox<E> makeComboBox(final E[] items, final E selected) { 517 return makeComboBox(CollectionHelpers.list(items), selected); 518 } 519 520 public static <E> JComboBox<E> makeComboBox(final E[] items, final E selected, final Width width) { 521 return makeComboBox(CollectionHelpers.list(items), selected, width); 522 } 523 524 public static <E> JComboBox<E> makeComboBox(final Collection<E> items, final E selected) { 525 return makeComboBox(items, selected, null); 526 } 527 528 public static <E> JComboBox<E> makeComboBox(final Collection<E> items, final E selected, final Width width) { 529 JComboBox<E> newComboBox = getEditableBox(items, selected); 530 setComponentWidth(newComboBox, width); 531 return newComboBox; 532 } 533 534 public static <E> void setListData(final JComboBox<E> box, final Collection<E> items, final E selected) { 535 box.removeAllItems(); 536 if (items != null) { 537 for (E o : items) { 538 box.addItem(o); 539 } 540 if (selected != null && !items.contains(selected)) { 541 box.addItem(selected); 542 } 543 } 544 } 545 546 public static <E> JComboBox<E> getEditableBox(final Collection<E> items, final E selected) { 547 JComboBox<E> fld = new JComboBox<>(); 548 fld.setEditable(true); 549 setListData(fld, items, selected); 550 if (selected != null) { 551 fld.setSelectedItem(selected); 552 } 553 return fld; 554 } 555 556 /** 557 * Create a standard sized text field. 558 * 559 * @param value Text to place within the text field. Should not be {@code null}. 560 * 561 * @return {@link JTextField} with initial text taken from {@code value}. 562 */ 563 public static JTextField makeTextField(String value) { 564 return makeTextField(value, null); 565 } 566 567 public static JTextField makeTextField(String value, Width width) { 568 JTextField newTextField = new McVTextField(value); 569 setComponentWidth(newTextField, width); 570 return newTextField; 571 } 572 573 /** 574 * Create some custom text entry widgets 575 */ 576 public static McVTextField makeTextFieldLimit(String defaultString, int limit) { 577 return new McVTextField(defaultString, limit); 578 } 579 580 public static McVTextField makeTextFieldUpper(String defaultString, int limit) { 581 return new McVTextField(defaultString, limit, true); 582 } 583 584 public static McVTextField makeTextFieldAllow(String defaultString, int limit, boolean upper, String allow) { 585 McVTextField newField = new McVTextField(defaultString, limit, upper); 586 newField.setAllow(allow); 587 return newField; 588 } 589 590 public static McVTextField makeTextFieldDeny(String defaultString, int limit, boolean upper, String deny) { 591 McVTextField newField = new McVTextField(defaultString, limit, upper); 592 newField.setDeny(deny); 593 return newField; 594 } 595 596 public static McVTextField makeTextFieldAllow(String defaultString, int limit, boolean upper, char... allow) { 597 McVTextField newField = new McVTextField(defaultString, limit, upper); 598 newField.setAllow(allow); 599 return newField; 600 } 601 602 public static McVTextField makeTextFieldDeny(String defaultString, int limit, boolean upper, char... deny) { 603 McVTextField newField = new McVTextField(defaultString, limit, upper); 604 newField.setDeny(deny); 605 return newField; 606 } 607 608 public static McVTextField makeTextFieldAllow(String defaultString, int limit, boolean upper, Pattern allow) { 609 McVTextField newField = new McVTextField(defaultString, limit, upper); 610 newField.setAllow(allow); 611 return newField; 612 } 613 614 public static McVTextField makeTextFieldDeny(String defaultString, int limit, boolean upper, Pattern deny) { 615 McVTextField newField = new McVTextField(defaultString, limit, upper); 616 newField.setDeny(deny); 617 return newField; 618 } 619 620 /** 621 * Use GroupLayout for stacking components vertically. 622 * Set center to resize vertically. 623 * 624 * @param top Component to place at the top of the newly created panel. Should not be {@code null}. 625 * @param center Component to place in the center of the newly created panel. Should not be {@code null}. 626 * @param bottom Component to place at the bottom of the newly created panel. Should not be {@code null}. 627 * 628 * @return New {@link JPanel} with the given components in the top, center, and bottom positions. 629 */ 630 public static JPanel topCenterBottom(JComponent top, JComponent center, JComponent bottom) { 631 JPanel newPanel = new JPanel(); 632 633 GroupLayout layout = new GroupLayout(newPanel); 634 newPanel.setLayout(layout); 635 layout.setHorizontalGroup( 636 layout.createParallelGroup(LEADING) 637 .addComponent(top, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 638 .addComponent(center, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 639 .addComponent(bottom, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 640 ); 641 layout.setVerticalGroup( 642 layout.createParallelGroup(LEADING) 643 .addGroup(layout.createSequentialGroup() 644 .addComponent(top, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) 645 .addPreferredGap(RELATED) 646 .addComponent(center, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 647 .addPreferredGap(RELATED) 648 .addComponent(bottom, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 649 ); 650 return newPanel; 651 } 652 653 /** 654 * Use GroupLayout for stacking components vertically. 655 * 656 * @param top Component to place at the top of the newly created panel. Should not be {@code null}. 657 * @param bottom Component to place at the bottom of the newly created panel. Should not be {@code null}. 658 * @param which Which component's size to prefer. Should not be {@code null}. 659 * 660 * @return New {@link JPanel} with the given components. 661 */ 662 public static JPanel topBottom(JComponent top, JComponent bottom, Prefer which) { 663 JPanel newPanel = new JPanel(); 664 665 int topSize = PREFERRED_SIZE; 666 667 if (which == Prefer.TOP) { 668 topSize = Short.MAX_VALUE; 669 } else if (which == Prefer.BOTTOM) { 670 topSize = Short.MAX_VALUE; 671 } 672 673 GroupLayout layout = new GroupLayout(newPanel); 674 newPanel.setLayout(layout); 675 layout.setHorizontalGroup( 676 layout.createParallelGroup(LEADING) 677 .addComponent(top, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 678 .addComponent(bottom, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 679 ); 680 layout.setVerticalGroup( 681 layout.createParallelGroup(LEADING) 682 .addGroup(layout.createSequentialGroup() 683 .addComponent(top, PREFERRED_SIZE, DEFAULT_SIZE, topSize) 684 .addPreferredGap(RELATED) 685 .addComponent(bottom, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 686 ); 687 return newPanel; 688 } 689 690 /** 691 * Use GroupLayout for wrapping components to stop vertical resizing. 692 * 693 * @param left Left component. Should not be {@code null}. 694 * @param right Right component. Should not be {@code null}. 695 * 696 * @return New {@link JPanel} with the given components side-by-side. 697 */ 698 public static JPanel sideBySide(JComponent left, JComponent right) { 699 return sideBySide(left, right, GAP_RELATED); 700 } 701 702 /** 703 * Use GroupLayout for wrapping components to stop vertical resizing. 704 * 705 * @param left Left component. Should not be {@code null}. 706 * @param right Right component. Should not be {@code null}. 707 * @param gap Gap between {@code left} and {@code right}. 708 * 709 * @return New {@link JPanel} with the given components side-by-side, 710 * separated by value from {@code gap}. 711 */ 712 public static JPanel sideBySide(JComponent left, JComponent right, int gap) { 713 JPanel newPanel = new JPanel(); 714 715 GroupLayout layout = new GroupLayout(newPanel); 716 newPanel.setLayout(layout); 717 layout.setHorizontalGroup( 718 layout.createParallelGroup(LEADING) 719 .addGroup(layout.createSequentialGroup() 720 .addComponent(left, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 721 .addGap(gap) 722 .addComponent(right, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 723 ); 724 layout.setVerticalGroup( 725 layout.createParallelGroup(LEADING) 726 .addGroup(layout.createSequentialGroup() 727 .addGroup(layout.createParallelGroup(LEADING) 728 .addComponent(left, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) 729 .addComponent(right, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE))) 730 ); 731 732 return newPanel; 733 } 734 735 /** 736 * Use GroupLayout for wrapping a list of components horizontally. 737 * 738 * @param components Components to stack horizontally. Should not be {@code null}. 739 * 740 * @return {@link JPanel} with the given components. 741 */ 742 public static JPanel horizontal(Component... components) { 743 JPanel newPanel = new JPanel(); 744 745 GroupLayout layout = new GroupLayout(newPanel); 746 newPanel.setLayout(layout); 747 748 SequentialGroup hGroup = layout.createSequentialGroup(); 749 for (int i = 0; i < components.length; i++) { 750 if (i > 0) { 751 hGroup.addGap(GAP_RELATED); 752 } 753 hGroup.addComponent(components[i], DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE); 754 } 755 756 SequentialGroup vGroup = layout.createSequentialGroup(); 757 ParallelGroup vInner = layout.createParallelGroup(LEADING); 758 for (int i = 0; i < components.length; i++) { 759 vInner.addComponent(components[i], PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE); 760 } 761 vGroup.addGroup(vInner); 762 763 layout.setHorizontalGroup(layout.createParallelGroup(LEADING).addGroup(hGroup)); 764 layout.setVerticalGroup(layout.createParallelGroup(LEADING).addGroup(vGroup)); 765 766 return newPanel; 767 } 768 769 /** 770 * Use GroupLayout for wrapping a list of components vertically. 771 * 772 * @param components Components to stack vertically. Should not be {@code null}. 773 * 774 * @return {@link JPanel} with the given components. 775 */ 776 public static JPanel vertical(Component... components) { 777 JPanel newPanel = new JPanel(); 778 779 GroupLayout layout = new GroupLayout(newPanel); 780 newPanel.setLayout(layout); 781 782 ParallelGroup hGroup = layout.createParallelGroup(LEADING); 783 for (int i = 0; i < components.length; i++) { 784 hGroup.addComponent(components[i], DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE); 785 } 786 787 int vSize = PREFERRED_SIZE; 788 789 ParallelGroup vGroup = layout.createParallelGroup(LEADING); 790 SequentialGroup vInner = layout.createSequentialGroup(); 791 for (int i = 0; i < components.length; i++) { 792 if (i > 0) { 793 vInner.addGap(GAP_RELATED); 794 } 795 if (i == components.length-1) { 796 vSize = Short.MAX_VALUE; 797 } 798 vInner.addComponent(components[i], PREFERRED_SIZE, DEFAULT_SIZE, vSize); 799 } 800 vGroup.addGroup(vInner); 801 layout.setHorizontalGroup(layout.createParallelGroup(LEADING).addGroup(hGroup)); 802 layout.setVerticalGroup(layout.createParallelGroup(LEADING).addGroup(vGroup)); 803 return newPanel; 804 } 805 806 /** 807 * Hack apart an IDV button panel and do a few things: 808 * - Reorder the buttons based on OS preference 809 * Windows: OK on left 810 * Mac: OK on right 811 * - Add icons when we understand the button name 812 * 813 * @param idvButtonPanel {@link JPanel} to scan for understood button names. Should not be {@code null}. 814 * 815 * @return The given {@code JPanel} with pretty buttons (where possible). 816 */ 817 // TODO: Revisit this? Could hamper GUI performance. But it is niiice... 818 public static JPanel makePrettyButtons(JPanel idvButtonPanel) { 819 // These are the buttons we know about 820 JButton buttonOK = null; 821 JButton buttonApply = null; 822 JButton buttonCancel = null; 823 JButton buttonHelp = null; 824 JButton buttonNew = null; 825 JButton buttonReset = null; 826 JButton buttonYes = null; 827 JButton buttonNo = null; 828 829 // First pull apart the panel and see if it looks like we expect 830 Component[] comps = idvButtonPanel.getComponents(); 831 832 // These are the buttons we don't know about 833 List<JButton> buttonList = new ArrayList<JButton>(comps.length); 834 835 for (int i = 0; i < comps.length; i++) { 836 if (!(comps[i] instanceof JButton)) { 837 continue; 838 } 839 JButton button = (JButton)comps[i]; 840 if ("OK".equals(button.getText())) { 841 buttonOK = makePrettyButton(button); 842 } else if ("Apply".equals(button.getText())) { 843 buttonApply = makePrettyButton(button); 844 } else if ("Cancel".equals(button.getText())) { 845 buttonCancel = makePrettyButton(button); 846 } else if ("Help".equals(button.getText())) { 847 buttonHelp = makePrettyButton(button); 848 } else if ("New".equals(button.getText())) { 849 buttonNew = makePrettyButton(button); 850 } else if ("Reset".equals(button.getText())) { 851 buttonReset = makePrettyButton(button); 852 } else if ("Yes".equals(button.getText())) { 853 buttonYes = makePrettyButton(button); 854 } else if ("No".equals(button.getText())) { 855 buttonNo = makePrettyButton(button); 856 } else { 857 buttonList.add(button); 858 } 859 } 860 861 // If we are on a Mac, this is the order (right aligned) 862 // Help, New, Reset, No, Yes, Cancel, Apply, OK 863 if (System.getProperty("os.name").contains("Mac OS X")) { 864 JPanel newButtonPanel = new JPanel(); 865 if (buttonHelp != null) { 866 newButtonPanel.add(buttonHelp); 867 } 868 if (buttonNew != null) { 869 newButtonPanel.add(buttonNew); 870 } 871 if (buttonReset != null) { 872 newButtonPanel.add(buttonReset); 873 } 874 if (buttonNo != null) { 875 newButtonPanel.add(buttonNo); 876 } 877 if (buttonYes != null) { 878 newButtonPanel.add(buttonYes); 879 } 880 if (buttonCancel != null) { 881 newButtonPanel.add(buttonCancel); 882 } 883 if (buttonApply != null) { 884 newButtonPanel.add(buttonApply); 885 } 886 if (buttonOK != null) { 887 newButtonPanel.add(buttonOK); 888 } 889 if (!buttonList.isEmpty()) { 890 return GuiUtils.right(GuiUtils.hbox(GuiUtils.hbox(buttonList), newButtonPanel)); 891 } else { 892 return GuiUtils.right(newButtonPanel); 893 } 894 } 895 896 // If we are not on a Mac, this is the order (center aligned) 897 // OK, Apply, Cancel, Yes, No, Reset, New, Help 898 if (!System.getProperty("os.name").contains("Mac OS X")) { 899 JPanel newButtonPanel = new JPanel(); 900 if (buttonOK != null) { 901 newButtonPanel.add(buttonOK); 902 } 903 if (buttonApply != null) { 904 newButtonPanel.add(buttonApply); 905 } 906 if (buttonCancel != null) { 907 newButtonPanel.add(buttonCancel); 908 } 909 if (buttonYes != null) { 910 newButtonPanel.add(buttonYes); 911 } 912 if (buttonNo != null) { 913 newButtonPanel.add(buttonNo); 914 } 915 if (buttonReset != null) { 916 newButtonPanel.add(buttonReset); 917 } 918 if (buttonNew != null) { 919 newButtonPanel.add(buttonNew); 920 } 921 if (buttonHelp != null) { 922 newButtonPanel.add(buttonHelp); 923 } 924 if (!buttonList.isEmpty()) { 925 return GuiUtils.center(GuiUtils.hbox(GuiUtils.hbox(buttonList), newButtonPanel)); 926 } else { 927 return GuiUtils.center(newButtonPanel); 928 } 929 } 930 931 return idvButtonPanel; 932 } 933 934 /** 935 * Take a list of buttons and make them pretty. 936 * 937 * @param buttonList List of buttons. Should not be {@code null}. 938 * 939 * @return {@link List} of pretty buttons. 940 */ 941 public static List makePrettyButtons(List buttonList) { 942 int size = buttonList.size(); 943 List newButtons = arrList(size); 944 for (int i = 0; i < size; i++) { 945 if (buttonList.get(i) instanceof JButton) { 946 newButtons.add(makePrettyButton((JButton)(buttonList.get(i)))); 947 } else { 948 newButtons.add(buttonList.get(i)); 949 } 950 } 951 return newButtons; 952 } 953 954 /** 955 * Convenience method to make a button based solely on its name. 956 * 957 * @param name Button text. Should not be {@code null}. 958 * 959 * @return A {@literal "pretty"} button. 960 */ 961 public static JButton makePrettyButton(String name) { 962 return makePrettyButton(new JButton(name)); 963 } 964 965 /** 966 * Add icons when we understand the button name. 967 * 968 * @param button Button to make pretty. Should not be {@code null}. 969 * 970 * @return button Either the given {@code button} with an icon, or just the given 971 * {@code button} (if the name was not understood). 972 */ 973 public static JButton makePrettyButton(JButton button) { 974 McVGuiUtils.setComponentWidth(button, Width.ONEHALF); 975 if ("OK".equals(button.getText())) { 976 McVGuiUtils.setButtonImage(button, ICON_ACCEPT_SMALL); 977 } else if ("Apply".equals(button.getText())) { 978 McVGuiUtils.setButtonImage(button, ICON_APPLY_SMALL); 979 } else if ("Cancel".equals(button.getText())) { 980 McVGuiUtils.setButtonImage(button, ICON_CANCEL_SMALL); 981 } else if ("Help".equals(button.getText())) { 982 McVGuiUtils.setButtonImage(button, ICON_HELP_SMALL); 983 } else if ("New".equals(button.getText())) { 984 McVGuiUtils.setButtonImage(button, ICON_ADD_SMALL); 985 } else if ("Reset".equals(button.getText())) { 986 McVGuiUtils.setButtonImage(button, ICON_UNDO_SMALL); 987 } else if ("Yes".equals(button.getText())) { 988 McVGuiUtils.setButtonImage(button, ICON_ACCEPT_SMALL); 989 } else if ("No".equals(button.getText())) { 990 McVGuiUtils.setButtonImage(button, ICON_CANCEL_SMALL); 991 } else if ("Close".equals(button.getText())) { 992 McVGuiUtils.setButtonImage(button, ICON_CANCEL_SMALL); 993 } else if ("Previous".equals(button.getText())) { 994 McVGuiUtils.setButtonImage(button, ICON_PREVIOUS_SMALL); 995 } else if ("Next".equals(button.getText())) { 996 McVGuiUtils.setButtonImage(button, ICON_NEXT_SMALL); 997 } else if ("Random".equals(button.getText())) { 998 McVGuiUtils.setButtonImage(button, ICON_RANDOM_SMALL); 999 } else if ("Support Form".equals(button.getText())) { 1000 McVGuiUtils.setButtonImage(button, ICON_SUPPORT_SMALL); 1001 } 1002 return button; 1003 } 1004 1005 /** 1006 * Print the hierarchy of components. 1007 */ 1008 public static void printUIComponents(JComponent parent) { 1009 printUIComponents(parent, 0, 0); 1010 } 1011 1012 public static void printUIComponents(JComponent parent, int index, int depth) { 1013 if (parent == null) { 1014 System.err.println("McVGuiUtils.printUIComponents: null parent"); 1015 return; 1016 } 1017 Component[] children = parent.getComponents(); 1018 int childcount = children.length; 1019 1020 String indent = ""; 1021 for (int d=0; d<depth; d++) { 1022 indent += " "; 1023 } 1024 System.out.println(indent + index + ": " + parent); 1025 1026 if (childcount > 0) { 1027 for (int c=0; c<childcount; c++) { 1028 if (children[c] instanceof JComponent) { 1029 printUIComponents((JComponent)children[c], c, depth+1); 1030 } 1031 } 1032 } 1033 } 1034 1035 /** 1036 * Calls {@link SwingUtilities#invokeLater(Runnable)} if the current thread 1037 * is not the event dispatch thread. If this thread <b>is</b> the EDT, 1038 * then call {@link Runnable#run()} for {@code r}. 1039 * 1040 * <p>Remember, you <i>do not</i> want to execute long-running tasks in the 1041 * event dispatch thread--it'll lock up the GUI. 1042 * 1043 * @param r Code to run in the event dispatch thread. Cannot be {@code null}. 1044 */ 1045 public static void runOnEDT(final Runnable r) { 1046 if (SwingUtilities.isEventDispatchThread()) { 1047 r.run(); 1048 } else { 1049 SwingUtilities.invokeLater(r); 1050 } 1051 } 1052 1053 /** 1054 * Executes the specified {@link Callable} on the EDT thread. 1055 * 1056 * <p>If the calling thread is already the EDT thread, this invocation 1057 * simply delegates to call(), otherwise the callable is placed in Swing's 1058 * event dispatch queue and the method waits for the result.</p> 1059 * 1060 * @param <T> Result type of the {@code callable}. 1061 * @param callable Callable task. Cannot be {@code null}. 1062 * 1063 * @return Computed result 1064 */ 1065 public static <T> T getFromEDT(final Callable<T> callable) { 1066 // credit for this belongs to: 1067 // http://stackoverflow.com/a/31156045 1068 final RunnableFuture<T> f = new FutureTask<>(callable); 1069 1070 if (SwingUtilities.isEventDispatchThread()) { 1071 f.run(); 1072 } else { 1073 SwingUtilities.invokeLater(f); 1074 } 1075 1076 T result = null; 1077 try { 1078 result = f.get(); 1079 } catch (InterruptedException | ExecutionException e) { 1080 logger.error("Thread execution problem", e); 1081 } 1082 return result; 1083 } 1084 1085 // private static <E> List<E> sizedList() { 1086 // McIDASV mcv = McIDASV.getStaticMcv(); 1087 // int viewManagerCount = ESTIMATED_VM_COUNT; 1088 // if (mcv != null) { 1089 // ViewManagerManager vmm = cast(mcv.getVMManager()); 1090 // viewManagerCount = vmm.getViewManagers().size(); 1091 // } 1092 // return arrList(viewManagerCount); 1093 // } 1094 1095 1096 private static int getVMCount() { 1097 McIDASV mcv = McIDASV.getStaticMcv(); 1098 int viewManagerCount = ESTIMATED_VM_COUNT; 1099 if (mcv != null) { 1100 ViewManagerManager vmm = (ViewManagerManager)mcv.getVMManager(); 1101 viewManagerCount = vmm.getViewManagerCount(); 1102 } 1103 return viewManagerCount; 1104 } 1105 1106 private static int getHolderCount() { 1107 McIDASV mcv = McIDASV.getStaticMcv(); 1108 int holderCount = ESTIMATED_VM_COUNT; 1109 if (mcv != null) { 1110 UIManager uiManager = (UIManager)mcv.getIdvUIManager(); 1111 holderCount = uiManager.getComponentHolderCount(); 1112 } 1113 return holderCount; 1114 } 1115 1116 private static int getGroupCount() { 1117 McIDASV mcv = McIDASV.getStaticMcv(); 1118 int groupCount = ESTIMATED_VM_COUNT; 1119 if (mcv != null) { 1120 UIManager uiManager = (UIManager)mcv.getIdvUIManager(); 1121 groupCount = uiManager.getComponentGroupCount(); 1122 } 1123 return groupCount; 1124 } 1125 1126 public static List<ViewManager> getActiveViewManagers() { 1127 IdvWindow activeWindow = IdvWindow.getActiveWindow(); 1128 List<ViewManager> vms; 1129 if (activeWindow != null) { 1130 vms = getViewManagers(activeWindow); 1131 } else { 1132 vms = Collections.emptyList(); 1133 } 1134 return vms; 1135 } 1136 1137 public static List<ViewManager> getAllViewManagers() { 1138 McIDASV mcv = McIDASV.getStaticMcv(); 1139 List<ViewManager> vms = Collections.emptyList(); 1140 if (mcv != null) { 1141 ViewManagerManager vmm = (ViewManagerManager)mcv.getVMManager(); 1142 vms = arrList(vmm.getViewManagers()); 1143 } 1144 return vms; 1145 } 1146 1147 /** 1148 * Attempt to find the {@link IdvWindow} that contains the given 1149 * {@link ViewManager}. 1150 * 1151 * @param vm {@code ViewManager} whose {@code IdvWindow} is needed. 1152 * Cannot be {@code null}. 1153 * 1154 * @return Either the {@code IdvWindow} containing {@code vm}, or 1155 * {@code null}. 1156 */ 1157 public static IdvWindow getWindowForViewManager(final ViewManager vm) { 1158 IdvWindow result = null; 1159 for (IdvWindow w : getAllDisplayWindows()) { 1160 List<ViewManager> viewManagers = getViewManagers(w); 1161 if (viewManagers.contains(vm)) { 1162 result = w; 1163 break; 1164 } 1165 } 1166 return result; 1167 } 1168 1169 public static List<Object> getShareGroupsInWindow(final IdvWindow window) { 1170 List<ViewManager> vms = arrList(getVMCount()); 1171 vms.addAll(window.getViewManagers()); 1172 for (IdvComponentHolder holder : getComponentHolders(window)) { 1173 List<ViewManager> holderVms = holder.getViewManagers(); 1174 if (holderVms != null) { 1175 vms.addAll(holderVms); 1176 } 1177 } 1178 Set<Object> groupIds = newHashSet(vms.size()); 1179 for (ViewManager vm : vms) { 1180 groupIds.add(vm.getShareGroup()); 1181 } 1182 return arrList(groupIds); 1183 } 1184 1185 public static List<Object> getAllShareGroups() { 1186 List<ViewManager> vms = getAllViewManagers(); 1187 Set<Object> groupIds = newHashSet(vms.size()); 1188 for (ViewManager vm : vms) { 1189 groupIds.add(vm.getShareGroup()); 1190 } 1191 return arrList(groupIds); 1192 } 1193 1194 public static List<ViewManager> getViewManagersInGroup(final Object sharedGroup) { 1195 List<ViewManager> allVMs = getAllViewManagers(); 1196 List<ViewManager> filtered = arrList(allVMs.size()); 1197 for (ViewManager vm : allVMs) { 1198 if (vm.getShareGroup().equals(sharedGroup)) { 1199 filtered.add(vm); 1200 } 1201 } 1202 return filtered; 1203 } 1204 1205 public static List<ViewManager> getViewManagers(final WindowInfo info) { 1206 List<ViewManager> vms = arrList(getVMCount()); 1207 for (IdvComponentHolder holder : getComponentHolders(info)) { 1208 List<ViewManager> holderVms = holder.getViewManagers(); 1209 if (holderVms != null) { 1210 vms.addAll(holderVms); 1211 } 1212 } 1213 return vms; 1214 } 1215 1216 public static List<ViewManager> getViewManagers(final IdvWindow window) { 1217 List<ViewManager> vms = arrList(getVMCount()); 1218 vms.addAll(window.getViewManagers()); 1219 for (IdvComponentHolder holder : getComponentHolders(window)) { 1220 List<ViewManager> holderVms = holder.getViewManagers(); 1221 if (holderVms != null) { 1222 vms.addAll(holderVms); 1223 } 1224 } 1225 return vms; 1226 } 1227 1228 /** 1229 * @return Whether or not {@code h} contains some UI component like 1230 * the dashboard of field selector. Yes, it can happen! 1231 */ 1232 public static boolean isUIHolder(final IdvComponentHolder h) { 1233 if (McvComponentHolder.TYPE_DYNAMIC_SKIN.equals(h.getType())) { 1234 return false; 1235 } 1236 return h.getViewManagers().isEmpty(); 1237 } 1238 1239 /** 1240 * @return Whether or not {@code h} is a dynamic skin. 1241 */ 1242 public static boolean isDynamicSkin(final IdvComponentHolder h) { 1243 return McvComponentHolder.TYPE_DYNAMIC_SKIN.equals(h.getType()); 1244 } 1245 1246 /** 1247 * @return Whether or not {@code windows} has at least one dynamic 1248 * skin. 1249 */ 1250 public static boolean hasDynamicSkins(final List<WindowInfo> windows) { 1251 for (WindowInfo window : windows) { 1252 for (IdvComponentHolder holder : getComponentHolders(window)) { 1253 if (isDynamicSkin(holder)) { 1254 return true; 1255 } 1256 } 1257 } 1258 return false; 1259 } 1260 1261 /** 1262 * @return The component holders within {@code windowInfo}. 1263 * @see #getComponentHolders(IdvComponentGroup) 1264 */ 1265 public static List<IdvComponentHolder> getComponentHolders(final WindowInfo windowInfo) { 1266 Collection<Object> comps = 1267 (Collection<Object>)windowInfo.getPersistentComponents().values(); 1268 List<IdvComponentHolder> holders = arrList(getHolderCount()); 1269 for (Object comp : comps) { 1270 if (!(comp instanceof IdvComponentGroup)) { 1271 continue; 1272 } 1273 holders.addAll(getComponentHolders((IdvComponentGroup)comp)); 1274 } 1275 return holders; 1276 } 1277 1278 /** 1279 * @return The component holders within {@code idvWindow}. 1280 * @see #getComponentHolders(IdvComponentGroup) 1281 */ 1282 public static List<IdvComponentHolder> getComponentHolders(final IdvWindow idvWindow) { 1283 List<IdvComponentHolder> holders = arrList(getHolderCount()); 1284 for (IdvComponentGroup group : (List<IdvComponentGroup>)idvWindow.getComponentGroups()) { 1285 holders.addAll(getComponentHolders(group)); 1286 } 1287 return holders; 1288 } 1289 1290 /** 1291 * @return <b>Recursively</b> searches {@code group} to find any 1292 * component holders. 1293 */ 1294 public static List<IdvComponentHolder> getComponentHolders(final IdvComponentGroup group) { 1295 List<IdvComponentHolder> holders = arrList(getHolderCount()); 1296 List<ComponentHolder> comps = (List<ComponentHolder>)group.getDisplayComponents(); 1297 if (comps.isEmpty()) { 1298 return holders; 1299 } 1300 for (ComponentHolder comp : comps) { 1301 if (comp instanceof IdvComponentGroup idvGroup) { 1302 holders.addAll(getComponentHolders(idvGroup)); 1303 } else if (comp instanceof IdvComponentHolder idvHolder) { 1304 holders.add(idvHolder); 1305 } 1306 } 1307 return holders; 1308 } 1309 1310 /** 1311 * @return <b>Recursively</b> searches {@code group} for any nested 1312 * component groups. 1313 */ 1314 public static List<IdvComponentGroup> getComponentGroups(final IdvComponentGroup group) { 1315 List<IdvComponentGroup> groups = arrList(getGroupCount()); 1316 groups.add(group); 1317 1318 List<ComponentHolder> comps = (List<ComponentHolder>)group.getDisplayComponents(); 1319 if (comps.isEmpty()) { 1320 return groups; 1321 } 1322 for (ComponentHolder comp : comps) { 1323 if (comp instanceof IdvComponentGroup idvHolder) { 1324 groups.addAll(getComponentGroups(idvHolder)); 1325 } 1326 } 1327 return groups; 1328 } 1329 1330 /** 1331 * @return Component groups contained in {@code window}. 1332 * @see #getComponentGroups(IdvComponentGroup) 1333 */ 1334 public static List<IdvComponentGroup> getComponentGroups(final WindowInfo window) { 1335 Collection<Object> comps = (Collection<Object>)window.getPersistentComponents().values(); 1336 for (Object obj : comps) { 1337 if (obj instanceof IdvComponentGroup comp) { 1338 return getComponentGroups(comp); 1339 } 1340 } 1341 return Collections.emptyList(); 1342 } 1343 1344 /** 1345 * @return Component groups contained in {@code windows}. 1346 * @see #getComponentGroups(IdvComponentGroup) 1347 */ 1348 public static List<IdvComponentGroup> getComponentGroups(final List<WindowInfo> windows) { 1349 List<IdvComponentGroup> groups = arrList(getGroupCount()); 1350 for (WindowInfo window : windows) { 1351 groups.addAll(getComponentGroups(window)); 1352 } 1353 return groups; 1354 } 1355 1356 /** 1357 * @return The component group within {@code window}. 1358 */ 1359 public static IdvComponentGroup getComponentGroup(final IdvWindow window) { 1360 List<IdvComponentGroup> groups = window.getComponentGroups(); 1361 if (!groups.isEmpty()) { 1362 return groups.getFirst(); 1363 } 1364 return null; 1365 } 1366 1367 /** 1368 * @return Whether {@code group} contains any component groups. 1369 */ 1370 public static boolean hasNestedGroups(final IdvComponentGroup group) { 1371 List<ComponentHolder> comps = (List<ComponentHolder>)group.getDisplayComponents(); 1372 for (ComponentHolder comp : comps) { 1373 if (comp instanceof IdvComponentGroup) { 1374 return true; 1375 } 1376 } 1377 return false; 1378 } 1379 1380 1381 /** 1382 * Find the window associated with the given {@code IdvComponentHolder} (aka tab). 1383 * 1384 * @param holder Holder whose {@code IdvWindow} we need to find. Should not be {@code null}. 1385 * 1386 * @return Either the {@code IdvWindow} containing the tab associated with the given {@code holder}, 1387 * or {@code null} if there was somehow no window associated with the holder. 1388 */ 1389 public static IdvWindow getWindowForHolder(final IdvComponentHolder holder) { 1390 List<IdvWindow> allWindows = getAllDisplayWindows(); 1391 IdvWindow result = null; 1392 for (IdvWindow window : allWindows) { 1393 List<IdvComponentHolder> holders = getComponentHolders(window); 1394 if (holders.contains(holder)) { 1395 result = window; 1396 break; 1397 } 1398 } 1399 return result; 1400 } 1401 1402 /** 1403 * Get the list of all active component holders in a McIDAS-V session. 1404 * 1405 * <p>This method is a essentially a {@literal "get all tabs"} method.</p> 1406 * 1407 * @return All active component holders in McIDAS-V. 1408 */ 1409 // TODO: needs update for nested groups 1410 public static List<IdvComponentHolder> getAllComponentHolders() { 1411 List<IdvComponentHolder> holders = arrList(getHolderCount()); 1412 for (IdvComponentGroup g : getAllComponentGroups()) { 1413 holders.addAll(g.getDisplayComponents()); 1414 } 1415 return holders; 1416 } 1417 1418 /** 1419 * Get the list of all active component groups in a McIDAS-V session. 1420 * 1421 * <p>There is a single component group per window, so this method could be 1422 * used as a way to get all display windows. That said, you probably want to 1423 * look at {@link #getAllDisplayWindows()} first.</p> 1424 * 1425 * @return All active component groups in McIDAS-V. 1426 */ 1427 // TODO: needs update for nested groups 1428 public static List<IdvComponentGroup> getAllComponentGroups() { 1429 List<IdvComponentGroup> groups = arrList(getGroupCount()); 1430 for (IdvWindow w : getAllDisplayWindows()) { 1431 groups.addAll(w.getComponentGroups()); 1432 } 1433 return groups; 1434 } 1435 1436 /** 1437 * Get the list of all {@link IdvWindow IdvWindows} that contain at least 1438 * one of what McIDAS-V would consider a {@literal "main display"}. 1439 * 1440 * @return All windows that contain at least one component group. 1441 */ 1442 public static List<IdvWindow> getAllDisplayWindows() { 1443 List<IdvWindow> allWindows = (List<IdvWindow>)IdvWindow.getWindows(); 1444 List<IdvWindow> windows = arrList(allWindows.size()); 1445 for (IdvWindow w : allWindows) { 1446 if (!w.getComponentGroups().isEmpty()) { 1447 windows.add(w); 1448 } 1449 } 1450 return windows; 1451 } 1452 1453 /** 1454 * @return The component holder positioned after the active component holder. 1455 */ 1456 public static IdvComponentHolder getAfterActiveHolder() { 1457 return getAfterHolder(getActiveComponentHolder()); 1458 } 1459 1460 /** 1461 * @return The component holder positioned before the active component holder. 1462 */ 1463 public static IdvComponentHolder getBeforeActiveHolder() { 1464 return getBeforeHolder(getActiveComponentHolder()); 1465 } 1466 1467 /** 1468 * @return The active component holder in the active window. 1469 */ 1470 public static IdvComponentHolder getActiveComponentHolder() { 1471 IdvWindow window = IdvWindow.getActiveWindow(); 1472 McvComponentGroup group = (McvComponentGroup)getComponentGroup(window); 1473 return (IdvComponentHolder)group.getActiveComponentHolder(); 1474 } 1475 1476 /** 1477 * @return The component holder positioned after {@code current}. 1478 */ 1479 public static IdvComponentHolder getAfterHolder(final IdvComponentHolder current) { 1480 List<IdvComponentHolder> holders = getAllComponentHolders(); 1481 int currentIndex = holders.indexOf(current); 1482 return holders.get((currentIndex + 1) % holders.size()); 1483 } 1484 1485 /** 1486 * @return The component holder positioned before {@code current}. 1487 */ 1488 public static IdvComponentHolder getBeforeHolder(final IdvComponentHolder current) { 1489 List<IdvComponentHolder> holders = getAllComponentHolders(); 1490 int currentIndex = holders.indexOf(current); 1491 int newidx = (currentIndex - 1) % holders.size(); 1492 if (newidx == -1) { 1493 newidx = holders.size() - 1; 1494 } 1495 return holders.get(newidx); 1496 } 1497 1498 /** 1499 * @param w {@link IdvWindow} whose component groups you want (as 1500 * {@link McvComponentGroup}s). 1501 * 1502 * @return A {@link List} of {@code McvComponentGroup}s or an empty list. 1503 * If there were no {@code McvComponentGroup}s in {@code w}, 1504 * <b>or</b> if {@code w} is {@code null}, an empty {@code List} is returned. 1505 */ 1506 public static List<McvComponentGroup> idvGroupsToMcv(final IdvWindow w) { 1507 if (w == null) { 1508 return Collections.emptyList(); 1509 } 1510 final List<IdvComponentGroup> idvLandGroups = w.getComponentGroups(); 1511 final List<McvComponentGroup> groups = arrList(idvLandGroups.size()); 1512 for (IdvComponentGroup group : idvLandGroups) { 1513 groups.add((McvComponentGroup)group); 1514 } 1515 return groups; 1516 } 1517 1518 public static void compGroup(final IdvComponentGroup g) { 1519 compGroup(g, 0); 1520 } 1521 1522 public static void compGroup(final IdvComponentGroup g, final int level) { 1523 p("Comp Group", level); 1524 p(" name=" + g.getName(), level); 1525 p(" id=" + g.getUniqueId(), level); 1526 p(" layout=" + g.getLayout(), level); 1527 p(" comp count=" + g.getDisplayComponents().size() + ": ", level); 1528 for (Object comp : g.getDisplayComponents()) { 1529 if (comp instanceof IdvComponentHolder) { 1530 compHolder((IdvComponentHolder)comp, level+1); 1531 } else if (comp instanceof IdvComponentGroup) { 1532 compGroup((IdvComponentGroup)comp, level+1); 1533 } else { 1534 p(" umm=" + comp.getClass().getName(), level); 1535 } 1536 } 1537 } 1538 1539 public static void compHolder(final IdvComponentHolder h, final int level) { 1540 p("Comp Holder", level); 1541 p(" cat=" + h.getCategory(), level); 1542 p(" name=" + h.getName(), level); 1543 p(" id=" + h.getUniqueId(), level); 1544 if (h.getViewManagers() == null) { 1545 System.err.println(" null vms!"); 1546 return; 1547 } 1548 p(" vm count=" + h.getViewManagers().size() + ": ", level); 1549 for (ViewManager vm : (List<ViewManager>)h.getViewManagers()) { 1550 p(" " + vmType(vm) + "=" + vm.getViewDescriptor().getName(), level); 1551 } 1552 } 1553 1554 public static List<ViewManager> findvms(final List<WindowInfo> windows) { 1555 List<ViewManager> vms = new ArrayList<ViewManager>(); 1556 for (WindowInfo window : windows) { 1557 for (IdvComponentHolder h : getComponentHolders(window)) { 1558 if (h.getViewManagers() != null) { 1559 vms.addAll((List<ViewManager>)h.getViewManagers()); 1560 } else { 1561 System.err.println(h.getUniqueId() + " has no vms!"); 1562 } 1563 } 1564 } 1565 for (ViewManager vm : vms) { 1566 System.err.println("vm=" + vm.getViewDescriptor().getName()); 1567 } 1568 return vms; 1569 } 1570 1571 private static String vmType(final ViewManager vm) { 1572 if (vm instanceof MapViewManager) { 1573 if (((MapViewManager)vm).getUseGlobeDisplay()) { 1574 return "Globe"; 1575 } else { 1576 return "Map"; 1577 } 1578 } 1579 return "Other"; 1580 } 1581 1582 private static String pad(final String str, final int pad) { 1583 char[] padding = new char[pad*2]; 1584 for (int i = 0; i < pad*2; i++) { 1585 padding[i] = ' '; 1586 } 1587 return new String(padding).concat(str); 1588 } 1589 1590 private static void p(final String str, final int padding) { 1591 System.err.println(pad(str, padding)); 1592 } 1593 1594 /** 1595 * Find the {@literal "bounds"} for the physical display at {@code index}. 1596 * 1597 * @param index Zero-based index of the desired physical display. 1598 * 1599 * @return Either a {@link java.awt.Rectangle} representing the display's 1600 * bounds, or {@code null} if {@code index} is invalid. 1601 */ 1602 public static Rectangle getDisplayBoundsFor(final int index) { 1603 return SystemState.getDisplayBounds().get(index); 1604 } 1605 1606 /** 1607 * Tries to determine the physical display that contains the given 1608 * {@link java.awt.Rectangle}. <b>This method (currently) fails for 1609 * {@code Rectangle}s that span multiple displays!</b> 1610 * 1611 * @param rect {@code Rectangle} to test. Should not be {@code null}. 1612 * 1613 * @return Either the (zero-based) index of the physical display, or 1614 * {@code -1} if there was no match. 1615 */ 1616 public static int findDisplayNumberForRectangle(final Rectangle rect) { 1617 Map<Integer, Rectangle> bounds = SystemState.getDisplayBounds(); 1618 int index = -1; 1619 for (Entry<Integer, Rectangle> entry : bounds.entrySet()) { 1620 if (entry.getValue().contains(rect)) { 1621 index = entry.getKey(); 1622 break; 1623 } 1624 } 1625 return index; 1626 } 1627 1628 /** 1629 * Tries to determine the physical display that contains the given 1630 * {@link java.awt.Component}. <b>This method (currently) fails for 1631 * {@code Component}s that span multiple displays!</b> 1632 * 1633 * @param comp {@code Component} to test. Should not be {@code null}. 1634 * 1635 * @return Either the (zero-based) index of the physical display, or 1636 * {@code -1} if there was no match. 1637 */ 1638 public static int findDisplayNumberForComponent(final Component comp) { 1639 return findDisplayNumberForRectangle( 1640 new Rectangle(comp.getLocation(), comp.getSize())); 1641 } 1642 1643 /** 1644 * Tries to determine the physical display that contains the given 1645 * {@link ucar.unidata.ui.MultiFrame}. <b>This method (currently) fails 1646 * for {@code MultiFrame}s that span multiple displays!</b> 1647 * 1648 * @param mf {@code MultiFrame} to test. Should not be {@code null}. 1649 * 1650 * @return Either the (zero-based) index of the physical display, or 1651 * {@code -1} if there was no match. 1652 */ 1653 public static int findDisplayNumberForMultiFrame(final MultiFrame mf) { 1654 return findDisplayNumberForRectangle( 1655 new Rectangle(mf.getLocation(), mf.getSize())); 1656 } 1657 1658 /** 1659 * Tries to determine the physical display that contains the rectangle 1660 * defined by the specified coordinates. <b>This method (currently) fails 1661 * for coordinates that span multiple displays!</b> 1662 * 1663 * @param x X coordinate of the upper-left corner. 1664 * @param y Y coordinate of the upper-left corner. 1665 * @param width Width of the rectangle. 1666 * @param height Height of the rectangle. 1667 * 1668 * @return Either the (zero-based) index of the physical display, or 1669 * {@code -1} if there was no match. 1670 * 1671 * @see java.awt.Rectangle#Rectangle(int, int, int, int) 1672 */ 1673 public static int findDisplayNumberForCoords(final int x, final int y, 1674 final int width, final int height) 1675 { 1676 return findDisplayNumberForRectangle( 1677 new Rectangle(x, y, width, height)); 1678 } 1679 1680 /** 1681 * Tries to determine which physical display contains the 1682 * {@link java.awt.Component} or {@link ucar.unidata.ui.MultiFrame} that 1683 * fired the given event. <b>This method (currently) fails for coordinates 1684 * that span multiple displays!</b> 1685 * 1686 * @param event {@code EventObject} to test. Should not be {@code null}. 1687 * 1688 * @return Either the (zero-based) index of the physical display, or 1689 * {@code -1} if there was no match. 1690 */ 1691 public static int findDisplayNumberForEvent(final EventObject event) { 1692 int idx = -1; 1693 Object src = event.getSource(); 1694 if (event instanceof HierarchyEvent) { 1695 src = ((HierarchyEvent)event).getChanged(); 1696 } 1697 if (src != null) { 1698 if (src instanceof Component) { 1699 idx = findDisplayNumberForComponent((Component)src); 1700 } else if (src instanceof MultiFrame) { 1701 idx = findDisplayNumberForMultiFrame((MultiFrame)src); 1702 } 1703 } 1704 return idx; 1705 } 1706 1707 // thing below here are courtesy http://tips4java.wordpress.com/2008/11/13/swing-utils/ 1708 1709 /** 1710 * Convenience method for searching below {@code container} in the 1711 * component hierarchy and return nested components that are instances of 1712 * class {@code clazz} it finds. Returns an empty list if no such 1713 * components exist in the container. 1714 * <P> 1715 * Invoking this method with a class parameter of JComponent.class 1716 * will return all nested components. 1717 * <P> 1718 * This method invokes 1719 * {@link #getDescendantsOfType(Class, java.awt.Container, boolean)} 1720 * 1721 * @param clazz the class of components whose instances are to be found. 1722 * @param container the container at which to begin the search 1723 * @return the List of components 1724 */ 1725 public static <T extends JComponent> List<T> getDescendantsOfType( 1726 Class<T> clazz, Container container) { 1727 return getDescendantsOfType(clazz, container, true); 1728 } 1729 1730 /** 1731 * Convenience method for searching below {@code container} in the 1732 * component hierarchy and return nested components that are instances of 1733 * class {@code clazz} it finds. Returns an empty list if no such 1734 * components exist in the container. 1735 * <P> 1736 * Invoking this method with a class parameter of JComponent.class 1737 * will return all nested components. 1738 * 1739 * @param clazz the class of components whose instances are to be found. 1740 * @param container the container at which to begin the search 1741 * @param nested true to list components nested within another listed 1742 * component, false otherwise 1743 * @return the List of components 1744 */ 1745 public static <T extends JComponent> List<T> getDescendantsOfType( 1746 Class<T> clazz, Container container, boolean nested) { 1747 List<T> tList = new ArrayList<>(); 1748 for (Component component : container.getComponents()) { 1749 if (clazz.isAssignableFrom(component.getClass())) { 1750 tList.add(clazz.cast(component)); 1751 } 1752 if (nested || !clazz.isAssignableFrom(component.getClass())) { 1753 tList.addAll(McVGuiUtils.getDescendantsOfType(clazz, 1754 (Container)component, nested)); 1755 } 1756 } 1757 return tList; 1758 } 1759 1760 /** 1761 * Convenience method that searches below {@code container} in the 1762 * component hierarchy and returns the first found component that is an 1763 * instance of class {@code clazz} having the bound property value. 1764 * Returns {@code null} if such component cannot be found. 1765 * <P> 1766 * This method invokes 1767 * {@link #getDescendantOfType(Class, java.awt.Container, String, Object, boolean)} 1768 * 1769 * @param clazz the class of component whose instance is to be found. 1770 * @param container the container at which to begin the search 1771 * @param property the className of the bound property, exactly as expressed in 1772 * the accessor e.g. {@literal "Text"} for getText(), {@literal "Value"} 1773 * for getValue(). 1774 * @param value the value of the bound property 1775 * @return the component, or null if no such component exists in the 1776 * container 1777 * @throws java.lang.IllegalArgumentException if the bound property does 1778 * not exist for the class or cannot be accessed 1779 */ 1780 public static <T extends JComponent> T getDescendantOfType( 1781 Class<T> clazz, Container container, String property, Object value) 1782 throws IllegalArgumentException 1783 { 1784 return getDescendantOfType(clazz, container, property, value, true); 1785 } 1786 1787 /** 1788 * Convenience method that searches below {@code container} in the 1789 * component hierarchy and returns the first found component that is an 1790 * instance of class {@code clazz} and has the bound property value. 1791 * Returns {@code null} if such component cannot be found. 1792 * 1793 * @param clazz the class of component whose instance to be found. 1794 * @param container the container at which to begin the search 1795 * @param property the className of the bound property, exactly as expressed in 1796 * the accessor e.g. {@literal "Text"} for getText(), {@literal "Value"} 1797 * for getValue(). 1798 * @param value the value of the bound property 1799 * @param nested true to list components nested within another component 1800 * which is also an instance of {@code clazz}, false otherwise. 1801 * 1802 * @return the component, or null if no such component exists in the 1803 * container. 1804 * 1805 * @throws java.lang.IllegalArgumentException if the bound property does 1806 * not exist for the class or cannot be accessed. 1807 */ 1808 public static <T extends JComponent> T getDescendantOfType(Class<T> clazz, 1809 Container container, 1810 String property, 1811 Object value, 1812 boolean nested) 1813 throws IllegalArgumentException 1814 { 1815 List<T> list = getDescendantsOfType(clazz, container, nested); 1816 return getComponentFromList(clazz, list, property, value); 1817 } 1818 1819 /** 1820 * Convenience method for searching below {@code container} in the 1821 * component hierarchy and return nested components of class 1822 * {@code clazz} it finds. Returns an empty list if no such 1823 * components exist in the container. 1824 * <P> 1825 * This method invokes 1826 * {@link #getDescendantsOfClass(Class, java.awt.Container, boolean)}. 1827 * 1828 * @param clazz the class of components to be found. 1829 * @param container the container at which to begin the search 1830 * @return the List of components 1831 */ 1832 public static <T extends JComponent> List<T> getDescendantsOfClass( 1833 Class<T> clazz, Container container) 1834 { 1835 return getDescendantsOfClass(clazz, container, true); 1836 } 1837 1838 /** 1839 * Convenience method for searching below {@code container} in the 1840 * component hierarchy and return nested components of class 1841 * {@code clazz} it finds. Returns an empty list if no such 1842 * components exist in the container. 1843 * 1844 * @param clazz the class of components to be found. 1845 * @param container the container at which to begin the search 1846 * @param nested true to list components nested within another listed 1847 * component, false otherwise 1848 * 1849 * @return the List of components 1850 */ 1851 public static <T extends JComponent> List<T> getDescendantsOfClass( 1852 Class<T> clazz, Container container, boolean nested) 1853 { 1854 List<T> tList = new ArrayList<>(); 1855 for (Component component : container.getComponents()) { 1856 if (clazz.equals(component.getClass())) { 1857 tList.add(clazz.cast(component)); 1858 } 1859 if (nested || !clazz.equals(component.getClass())) { 1860 tList.addAll(McVGuiUtils.getDescendantsOfClass(clazz, 1861 (Container)component, nested)); 1862 } 1863 } 1864 return tList; 1865 } 1866 1867 /** 1868 * Convenience method that searches below {@code container} in the 1869 * component hierarchy in a depth first manner and returns the first 1870 * found component of class {@code clazz} having the bound property 1871 * value. 1872 * <P> 1873 * Returns {@code null} if such component cannot be found. 1874 * <P> 1875 * This method invokes 1876 * {@link #getDescendantOfClass(Class, java.awt.Container, String, Object, boolean)} 1877 * 1878 * @param clazz the class of component to be found. 1879 * @param container the container at which to begin the search 1880 * @param property the className of the bound property, exactly as expressed in 1881 * the accessor e.g. {@literal "Text"} for getText(), {@literal "Value"} 1882 * for getValue(). This parameter is case-sensitive. 1883 * @param value the value of the bound property 1884 * 1885 * @return the component, or null if no such component exists in the 1886 * container's hierarchy. 1887 * 1888 * @throws java.lang.IllegalArgumentException if the bound property does 1889 * not exist for the class or cannot be accessed 1890 */ 1891 public static <T extends JComponent> T getDescendantOfClass(Class<T> clazz, 1892 Container container, 1893 String property, 1894 Object value) 1895 throws IllegalArgumentException 1896 { 1897 return getDescendantOfClass(clazz, container, property, value, true); 1898 } 1899 1900 /** 1901 * Convenience method that searches below {@code container} in the 1902 * component hierarchy in a depth first manner and returns the first 1903 * found component of class {@code clazz} having the bound property 1904 * value. 1905 * <P> 1906 * Returns {@code null} if such component cannot be found. 1907 * 1908 * @param clazz the class of component to be found. 1909 * @param container the container at which to begin the search 1910 * @param property the className of the bound property, exactly as expressed 1911 * in the accessor e.g. {@literal "Text"} for getText(), {@literal "Value"} 1912 * for getValue(). This parameter is case sensitive. 1913 * @param value the value of the bound property 1914 * @param nested true to include components nested within another listed 1915 * component, false otherwise. 1916 * 1917 * @return the component, or null if no such component exists in the 1918 * container's hierarchy. 1919 * 1920 * @throws java.lang.IllegalArgumentException if the bound property does 1921 * not exist for the class or cannot be accessed 1922 */ 1923 public static <T extends JComponent> T getDescendantOfClass(Class<T> clazz, 1924 Container container, 1925 String property, 1926 Object value, 1927 boolean nested) 1928 throws IllegalArgumentException 1929 { 1930 List<T> list = getDescendantsOfClass(clazz, container, nested); 1931 return getComponentFromList(clazz, list, property, value); 1932 } 1933 1934 private static <T extends JComponent> T getComponentFromList(Class<T> clazz, 1935 List<T> list, 1936 String property, 1937 Object value) 1938 throws IllegalArgumentException 1939 { 1940 T retVal = null; 1941 Method method = null; 1942 try { 1943 method = clazz.getMethod("get" + property); 1944 } catch (NoSuchMethodException ex) { 1945 try { 1946 method = clazz.getMethod("is" + property); 1947 } catch (NoSuchMethodException ex1) { 1948 throw new IllegalArgumentException("Property " + property + 1949 " not found in class " + clazz.getName(), ex1); 1950 } 1951 } 1952 try { 1953 for (T t : list) { 1954 Object testVal = method.invoke(t); 1955 if (equals(value, testVal)) { 1956 return t; 1957 } 1958 } 1959 } catch (InvocationTargetException ex) { 1960 throw new IllegalArgumentException( 1961 "Error accessing property " + property + 1962 " in class " + clazz.getName(), ex); 1963 } catch (IllegalAccessException ex) { 1964 throw new IllegalArgumentException( 1965 "Property " + property + 1966 " cannot be accessed in class " + clazz.getName(), ex); 1967 } catch (SecurityException ex) { 1968 throw new IllegalArgumentException( 1969 "Property " + property + 1970 " cannot be accessed in class " + clazz.getName(), ex); 1971 } 1972 return retVal; 1973 } 1974 1975 /** 1976 * Convenience method for determining whether two objects are either 1977 * equal or both null. 1978 * 1979 * @param obj1 the first reference object to compare. 1980 * @param obj2 the second reference object to compare. 1981 * @return true if obj1 and obj2 are equal or if both are null, 1982 * false otherwise 1983 */ 1984 public static boolean equals(Object obj1, Object obj2) { 1985 return (obj1 == null) ? (obj2 == null) : obj1.equals(obj2); 1986 } 1987 1988 /** 1989 * Convenience method for mapping a container in the hierarchy to its 1990 * contained components. The keys are the containers, and the values 1991 * are lists of contained components. 1992 * <P> 1993 * Implementation note: The returned value is a HashMap and the values 1994 * are of type ArrayList. This is subject to change, so callers should 1995 * code against the interfaces Map and List. 1996 * 1997 * @param container The JComponent to be mapped 1998 * @param nested true to drill down to nested containers, false otherwise 1999 * @return the Map of the UI 2000 */ 2001 public static Map<JComponent, List<JComponent>> getComponentMap( 2002 JComponent container, boolean nested) 2003 { 2004 List<JComponent> descendants = 2005 getDescendantsOfType(JComponent.class, container, false); 2006 Map<JComponent, List<JComponent>> retVal = 2007 new HashMap<>(descendants.size()); 2008 for (JComponent component : descendants) { 2009 if (!retVal.containsKey(container)) { 2010 retVal.put(container, 2011 new ArrayList<JComponent>()); 2012 } 2013 retVal.get(container).add(component); 2014 if (nested) { 2015 retVal.putAll(getComponentMap(component, nested)); 2016 } 2017 } 2018 return retVal; 2019 } 2020 2021 /** 2022 * Convenience method for retrieving a subset of the UIDefaults pertaining 2023 * to a particular class. 2024 * 2025 * @param clazz the class of interest 2026 * @return the UIDefaults of the class 2027 */ 2028 public static UIDefaults getUIDefaultsOfClass(Class<?> clazz) { 2029 String name = clazz.getName(); 2030 name = name.substring(name.lastIndexOf(".") + 2); 2031 return getUIDefaultsOfClass(name); 2032 } 2033 2034 /** 2035 * Convenience method for retrieving a subset of the UIDefaults pertaining 2036 * to a particular class. 2037 * 2038 * @param className fully qualified name of the class of interest 2039 * @return the UIDefaults of the class named 2040 */ 2041 public static UIDefaults getUIDefaultsOfClass(String className) { 2042 UIDefaults retVal = new UIDefaults(); 2043 UIDefaults defaults = javax.swing.UIManager.getLookAndFeelDefaults(); 2044 List<?> listKeys = Collections.list(defaults.keys()); 2045 for (Object key : listKeys) { 2046 if ((key instanceof String) && ((String) key).startsWith(className)) { 2047 String stringKey = (String) key; 2048 String property = stringKey; 2049 if (stringKey.contains(".")) { 2050 property = stringKey.substring(stringKey.indexOf(".") + 1); 2051 } 2052 retVal.put(property, defaults.get(key)); 2053 } 2054 } 2055 return retVal; 2056 } 2057 2058 /** 2059 * Convenience method for retrieving the UIDefault for a single property 2060 * of a particular class. 2061 * 2062 * @param clazz the class of interest 2063 * @param property the property to query 2064 * @return the UIDefault property, or null if not found 2065 */ 2066 public static Object getUIDefaultOfClass(Class<?> clazz, String property) { 2067 Object retVal = null; 2068 UIDefaults defaults = getUIDefaultsOfClass(clazz); 2069 List<Object> listKeys = Collections.list(defaults.keys()); 2070 for (Object key : listKeys) { 2071 if (key.equals(property)) { 2072 return defaults.get(key); 2073 } 2074 if (key.toString().equalsIgnoreCase(property)) { 2075 retVal = defaults.get(key); 2076 } 2077 } 2078 return retVal; 2079 } 2080 2081 /** 2082 * Exclude methods that return values that are meaningless to the user 2083 */ 2084 static Set<String> setExclude = HashSet.newHashSet(10); 2085 static { 2086 setExclude.add("getFocusCycleRootAncestor"); 2087 setExclude.add("getAccessibleContext"); 2088 setExclude.add("getColorModel"); 2089 setExclude.add("getGraphics"); 2090 setExclude.add("getGraphicsConfiguration"); 2091 } 2092 2093 /** 2094 * Convenience method for obtaining most non-null human-readable properties 2095 * of a JComponent. Array properties are not included. 2096 * <P> 2097 * Implementation note: The returned value is a HashMap. This is subject 2098 * to change, so callers should code against the interface Map. 2099 * 2100 * @param component the component whose proerties are to be determined 2101 * @return the class and value of the properties 2102 */ 2103 public static Map<Object, Object> getProperties(JComponent component) { 2104 Class<?> clazz = component.getClass(); 2105 Method[] methods = clazz.getMethods(); 2106 Map<Object, Object> retVal = new HashMap<>(methods.length); 2107 Object value = null; 2108 for (Method method : methods) { 2109 if (method.getName().matches("^(is|get).*") && 2110 (method.getParameterTypes().length == 0)) { 2111 try { 2112 Class<?> returnType = method.getReturnType(); 2113 if ((returnType != void.class) && 2114 !returnType.getName().startsWith("[") && 2115 !setExclude.contains(method.getName())) { 2116 String key = method.getName(); 2117 value = method.invoke(component); 2118 if ((value != null) && !(value instanceof Component)) { 2119 retVal.put(key, value); 2120 } 2121 } 2122 // ignore exceptions that arise if the property could not be accessed 2123 } catch (IllegalAccessException ex) { 2124 } catch (IllegalArgumentException ex) { 2125 } catch (InvocationTargetException ex) { 2126 } 2127 } 2128 } 2129 return retVal; 2130 } 2131 2132 /** 2133 * Convenience method to obtain the Swing class from which this 2134 * component was directly or indirectly derived. 2135 * 2136 * @param component The component whose Swing superclass is to be 2137 * determined 2138 * @return The nearest Swing class in the inheritance tree 2139 */ 2140 public static <T extends JComponent> Class<?> getJClass(T component) { 2141 Class<?> clazz = component.getClass(); 2142 while (!clazz.getName().matches("javax.swing.J[^.]*$")) { 2143 clazz = clazz.getSuperclass(); 2144 } 2145 return clazz; 2146 } 2147 2148 /** 2149 * Gets the {@literal "text"} contents of a {@link JTextComponent} without 2150 * the possibility of a {@code NullPointerException}. 2151 * 2152 * @param textComponent {@code JTextComponent} whose contents should be 2153 * extracted. {@code null} is allowed. 2154 * 2155 * @return Either the results of {@link JTextComponent#getText()} or an 2156 * empty {@code String} if {@code textComponent} or {@code getText()} are 2157 * {@code null}. 2158 */ 2159 public static String safeGetText(JTextComponent textComponent) { 2160 String value = ""; 2161 if ((textComponent != null) && (textComponent.getText() != null)) { 2162 value = textComponent.getText(); 2163 } 2164 return value; 2165 } 2166}