001 package jigcell.compare.ui; 002 003 import java.awt.Color; 004 import java.awt.Component; 005 import java.awt.event.ActionListener; 006 import java.awt.event.KeyEvent; 007 import java.beans.PropertyChangeEvent; 008 import java.beans.PropertyChangeListener; 009 import javax.swing.AbstractCellEditor; 010 import javax.swing.DefaultCellEditor; 011 import javax.swing.JButton; 012 import javax.swing.JCheckBox; 013 import javax.swing.JComboBox; 014 import javax.swing.JTable; 015 import javax.swing.JTextField; 016 import javax.swing.KeyStroke; 017 import javax.swing.UIManager; 018 import javax.swing.table.AbstractTableModel; 019 import javax.swing.table.DefaultTableCellRenderer; 020 import javax.swing.table.TableCellEditor; 021 import javax.swing.table.TableCellRenderer; 022 import javax.swing.table.TableColumn; 023 import javax.swing.table.TableColumnModel; 024 import javax.swing.table.TableModel; 025 import jigcell.compare.ITab; 026 import jigcell.compare.impl.Compare; 027 import jigcell.compare.impl.Config; 028 029 /** 030 * Extends JTable by enhancing the default table editor, renderer, and layout. Provides additional support for building custom table models. 031 * 032 * <p> 033 * This code is licensed under the DARPA BioCOMP Open Source License. See LICENSE for more details. 034 * </p> 035 * 036 * @author Nicholas Allen 037 */ 038 039 public class BasicTable extends JTable { 040 041 /** 042 * Client property for delayed initialization 043 */ 044 045 protected final static String CLIENT_COMPONENTSHOWN = "jigcell_component_shown"; 046 047 /** 048 * Table model 049 */ 050 051 protected BasicTableModel model; 052 053 /** 054 * Base class of the data model used by views. 055 */ 056 057 public abstract static class BasicTableModel extends AbstractTableModel { 058 059 /** 060 * Weights for resizing columns 061 */ 062 063 protected double columnWeights []; 064 065 /** 066 * Column markers of the table 067 */ 068 069 protected Marker markers []; 070 071 /** 072 * Provides quick access to information about columns in this model. 073 */ 074 075 protected static class Marker { 076 077 /** 078 * Unique identifier for this column. 079 */ 080 081 private final String identifier; 082 083 /** 084 * Name of this column for the table header. 085 */ 086 087 private String name; 088 089 /** 090 * Creates a new table marker. 091 * 092 * @param identifier Identifier for this table column 093 * @param name Name for the table column in this model 094 */ 095 096 public Marker (String identifier, String name) { 097 assert identifier != null; 098 this.identifier = identifier; 099 this.name = name; 100 } 101 102 public boolean equals (Object o) { 103 return o instanceof Marker && ((Marker) o).identifier.equals (identifier); 104 } 105 106 /** 107 * The name of this column for the table header. 108 */ 109 110 public String getName () { 111 return name; 112 } 113 114 public int hashCode () { 115 return identifier.hashCode (); 116 } 117 118 /** 119 * Sets the name of this column for the table header. 120 * 121 * @param name Name 122 */ 123 124 public void setName (String name) { 125 this.name = name; 126 } 127 128 public String toString () { 129 return getName (); 130 } 131 } 132 133 /** 134 * Creates a new table model. 135 */ 136 137 public BasicTableModel () {} 138 139 /** 140 * Fills along a column with a particular value. 141 * 142 * @param column Column 143 * @param startRow First row 144 * @param endRow Last row 145 * @param value Value 146 */ 147 148 public void fillRange (int column, int startRow, int endRow, Object value) { 149 while (startRow <= endRow) 150 setValueAt (value, startRow++, column); 151 } 152 153 /** 154 * Fills along a column. 155 * 156 * @param row Row 157 * @param column Column 158 * @param startRow First row 159 * @param endRow Last row 160 */ 161 162 public void fillRangeAt (int row, int column, int startRow, int endRow) { 163 fillRange (column, startRow, endRow, getValueAt (row, column)); 164 } 165 166 /** 167 * @see jigcell.compare.ui.ICellEditorTab#fillSelected(int,int,int[]) 168 */ 169 170 public void fillSelected (int row, int column, int rows []) { 171 for (int i = 0, l = rows.length; i < l; i++) { 172 int selection = rows [i]; 173 fillRangeAt (row, column, selection, selection); 174 } 175 } 176 177 /** 178 * The index of a column. If no column has the specified marker, the result is -1. 179 * 180 * @param marker Column marker 181 */ 182 183 public int findColumn (Marker marker) { 184 assert marker != null; 185 for (int i = markers.length - 1; i >= 0; i--) 186 if (markers [i].equals (marker)) 187 return i; 188 return -1; 189 } 190 191 /** 192 * The type of objects in a column. 193 * 194 * @param column Index of column 195 */ 196 197 public Class getColumnClass (int column) { 198 return String.class; 199 } 200 201 /** 202 * The number of columns in the table. 203 */ 204 205 public int getColumnCount () { 206 return markers.length; 207 } 208 209 /** 210 * The name of a column. 211 * 212 * @param column Index of column 213 */ 214 215 public String getColumnName (int column) { 216 return column >= 0 && column < markers.length ? markers [column].getName () : ""; 217 } 218 219 public double [] getColumnWeights () { 220 return columnWeights; 221 } 222 223 /** 224 * {@inheritDoc} 225 */ 226 227 public Object getValueAt (int row, int column) { 228 return null; 229 } 230 231 public void setColumnWeights (double columnWeights []) { 232 assert columnWeights == null || columnWeights.length == getColumnCount (); 233 this.columnWeights = columnWeights; 234 } 235 236 /** 237 * Sets the table markers used by this mode. 238 * 239 * @param markers Table markers 240 */ 241 242 protected void setColumnMarkers (Marker markers []) { 243 assert markers != null; 244 this.markers = markers; 245 } 246 } 247 248 /** 249 * Default editor for a BasicTable. 250 */ 251 252 public static class BasicEditor extends DefaultCellEditor implements PropertyChangeListener { 253 254 /** 255 * Configuration property key for the modified cell foreground color 256 */ 257 258 public final static String CONFIG_FOREGROUNDMODIFIED = "BasicTable.foregroundModifiedCell"; 259 260 /** 261 * Default foreground color for modified cells 262 */ 263 264 protected final static Color DEFAULT_FOREGROUNDMODIFIED = new Color (0, 0, 255); 265 266 /** 267 * Host view of this table 268 */ 269 270 private IBasicTableHost host; 271 272 /** 273 * Creates a new editor. 274 */ 275 276 public BasicEditor () { 277 this (null, new JTextField ()); 278 } 279 280 /** 281 * Creates a new editor. 282 * 283 * @param host Host view of this table 284 */ 285 286 public BasicEditor (IBasicTableHost host) { 287 this (host, new JTextField ()); 288 } 289 290 /** 291 * Creates a new editor. 292 * 293 * @param host Host view of this table 294 * @param checkBox Editor control 295 */ 296 297 public BasicEditor (IBasicTableHost host, JCheckBox checkBox) { 298 super (checkBox); 299 registerHost (host); 300 } 301 302 /** 303 * Creates a new editor. 304 * 305 * @param host Host view of this table 306 * @param comboBox Editor control 307 */ 308 309 public BasicEditor (IBasicTableHost host, JComboBox comboBox) { 310 super (comboBox); 311 registerHost (host); 312 } 313 314 /** 315 * Creates a new editor. 316 * 317 * @param host Host view of this table 318 * @param textField Editor control 319 */ 320 321 public BasicEditor (IBasicTableHost host, JTextField textField) { 322 super (textField); 323 registerHost (host); 324 } 325 326 /** 327 * A color for use with this table. 328 * 329 * @param key Color key 330 */ 331 332 public Color getColor (String key) { 333 return UIManager.getColor (mungeKey (key)); 334 } 335 336 public Component getTableCellEditorComponent (JTable table, Object value, boolean selected, int row, int column) { 337 Component cell = super.getTableCellEditorComponent (table, value, selected, row, column); 338 cell.setForeground (getColor ("BasicEditor.foregroundModified")); 339 return cell; 340 } 341 342 public void propertyChange (PropertyChangeEvent e) { 343 if (Compare.PROPERTY_CONFIG_EDIT.equals (e.getPropertyName ())) 344 readConfiguration (ITab.STATE_RUNNING); 345 } 346 347 /** 348 * Sets a color for use with this editor. 349 * 350 * @param key Color key 351 * @param color Color 352 */ 353 354 public void putColor (String key, Color color) { 355 UIManager.put (mungeKey (key), color); 356 } 357 358 /** 359 * @see jigcell.compare.ITab#readConfiguration(java.lang.String) 360 */ 361 362 public void readConfiguration (String state) { 363 putColor ("BasicEditor.foregroundModified", getColorFromConfig (CONFIG_FOREGROUNDMODIFIED, DEFAULT_FOREGROUNDMODIFIED)); 364 } 365 366 /** 367 * Color specified by the configuration of the host view. 368 * 369 * @param key Configuration key 370 * @param defaultColor Color to use if configuration does not specify a value 371 */ 372 373 protected Color getColorFromConfig (String key, Color defaultColor) { 374 return host == null ? defaultColor : Config.convertToColor (host.getCompare ().getConfig ().findValue (host.getConfigMarkers (), key), 375 defaultColor); 376 } 377 378 /** 379 * Creates the actual key value based on a generic key value. 380 * 381 * @param key Key 382 */ 383 384 private String mungeKey (String key) { 385 return (host == null ? getClass ().toString () : host.getHostIdentifier ()) + "%" + key; 386 } 387 388 /** 389 * Hooks this editor up to the host so that configuration data can be obtained. This method should only be called once and before 390 * any configuration data is needed. 391 * 392 * @param host Host view of this table 393 */ 394 395 private void registerHost (IBasicTableHost host) { 396 this.host = host; 397 putColor ("BasicEditor.foregroundModified", DEFAULT_FOREGROUNDMODIFIED); 398 if (host == null) 399 return; 400 host.getCompare ().addPropertyChangeListener (this); 401 readConfiguration (ITab.STATE_INITIALIZE); 402 } 403 } 404 405 /** 406 * Default renderer for a BasicTable. 407 */ 408 409 public static class BasicRenderer extends DefaultTableCellRenderer implements PropertyChangeListener { 410 411 /** 412 * Configuration property key for the editable cell background color 413 */ 414 415 public final static String CONFIG_BACKGROUNDEDITABLE = "BasicTable.backgroundEditableCell"; 416 417 /** 418 * Configuration property key for the uneditable cell background color 419 */ 420 421 public final static String CONFIG_BACKGROUNDUNEDITABLE = "BasicTable.backgroundUneditableCell"; 422 423 /** 424 * Default background color for editable cells 425 */ 426 427 protected final static Color DEFAULT_BACKGROUNDEDITABLE = new Color (255, 255, 255); 428 429 /** 430 * Default backgorund color for uneditable cells 431 */ 432 433 protected final static Color DEFAULT_BACKGROUNDUNEDITABLE = new Color (240, 240, 240); 434 435 /** 436 * Host view of this table 437 */ 438 439 private IBasicTableHost host; 440 441 /** 442 * Creates a new renderer. 443 */ 444 445 public BasicRenderer () { 446 this (null); 447 } 448 449 /** 450 * Creates a new renderer. 451 * 452 * @param host Host view of this table 453 */ 454 455 public BasicRenderer (IBasicTableHost host) { 456 super (); 457 registerHost (host); 458 } 459 460 /** 461 * A color for use with this renderer. 462 * 463 * @param key Color key 464 */ 465 466 public Color getColor (String key) { 467 return UIManager.getColor (mungeKey (key)); 468 } 469 470 public Component getTableCellRendererComponent (JTable table, Object value, boolean selected, boolean focus, int row, int column) { 471 Component cell = super.getTableCellRendererComponent (table, value, selected, focus, row, column); 472 if (!selected) 473 cell.setBackground (table.isCellEditable (row, column) ? getColor ("BasicRenderer.backgroundEditable") : 474 getColor ("BasicRenderer.backgroundUneditable")); 475 return cell; 476 } 477 478 public void propertyChange (PropertyChangeEvent e) { 479 if (Compare.PROPERTY_CONFIG_EDIT.equals (e.getPropertyName ())) 480 readConfiguration (ITab.STATE_RUNNING); 481 } 482 483 /** 484 * Sets a color for use with this renderer. 485 * 486 * @param key Color key 487 * @param color Color 488 */ 489 490 public void putColor (String key, Color color) { 491 UIManager.put (mungeKey (key), color); 492 } 493 494 /** 495 * @see jigcell.compare.ITab#readConfiguration(java.lang.String) 496 */ 497 498 public void readConfiguration (String state) { 499 putColor ("BasicRenderer.backgroundEditable", getColorFromConfig (CONFIG_BACKGROUNDEDITABLE, DEFAULT_BACKGROUNDEDITABLE)); 500 putColor ("BasicRenderer.backgroundUneditable", getColorFromConfig (CONFIG_BACKGROUNDUNEDITABLE, DEFAULT_BACKGROUNDUNEDITABLE)); 501 if (host != null) 502 host.getTable ().repaint (); 503 } 504 505 /** 506 * Color specified by the configuration of the host view. 507 * 508 * @param key Configuration key 509 * @param defaultColor Color to use if configuration does not specify a value 510 */ 511 512 protected Color getColorFromConfig (String key, Color defaultColor) { 513 return host == null ? defaultColor : Config.convertToColor (host.getCompare ().getConfig ().findValue (host.getConfigMarkers (), key), 514 defaultColor); 515 } 516 517 /** 518 * Creates the actual key value based on a generic key value. 519 * 520 * @param key Key 521 */ 522 523 private String mungeKey (String key) { 524 return (host == null ? getClass ().toString () : host.getHostIdentifier ()) + "%" + key; 525 } 526 527 /** 528 * Hooks this renderer up to the host so that configuration data can be obtained. This method should only be called once and before 529 * any configuration data is needed. 530 * 531 * @param host Host view of this table 532 */ 533 534 private void registerHost (IBasicTableHost host) { 535 this.host = host; 536 putColor ("BasicRenderer.backgroundEditable", DEFAULT_BACKGROUNDEDITABLE); 537 putColor ("BasicRenderer.backgroundUneditable", DEFAULT_BACKGROUNDUNEDITABLE); 538 if (host == null) 539 return; 540 host.getCompare ().addPropertyChangeListener (this); 541 readConfiguration (ITab.STATE_INITIALIZE); 542 } 543 } 544 545 /** 546 * Editor that displays a button in a table cell 547 */ 548 549 public abstract static class ButtonEditor extends AbstractCellEditor implements ActionListener, TableCellEditor { 550 551 /** 552 * Column being edited 553 */ 554 555 protected int column; 556 557 /** 558 * Row being edited 559 */ 560 561 protected int row; 562 563 /** 564 * Button 565 */ 566 567 protected JButton button; 568 569 /** 570 * Editor value 571 */ 572 573 protected Object value; 574 575 /** 576 * Creates a new button editor. 577 */ 578 579 public ButtonEditor () { 580 button = new JButton (); 581 button.addActionListener (this); 582 } 583 584 public Object getCellEditorValue () { 585 return value; 586 } 587 588 public Component getTableCellEditorComponent (JTable table, Object value, boolean isSelected, int row, int column) { 589 this.row = row; 590 this.column = column; 591 this.value = value; 592 button.setText (value == null ? "" : value.toString ()); 593 return button; 594 } 595 596 /** 597 * Column being edited. 598 */ 599 600 protected int getColumn () { 601 return column; 602 } 603 604 /** 605 * Row being edited. 606 */ 607 608 protected int getRow () { 609 return row; 610 } 611 } 612 613 /** 614 * Renderer for displaying a button in a table cell 615 */ 616 617 public static class ButtonRenderer extends JButton implements TableCellRenderer { 618 619 /** 620 * Creates a new button renderer. 621 */ 622 623 public ButtonRenderer () {} 624 625 public Component getTableCellRendererComponent (JTable table, Object value, boolean selected, boolean focus, int row, int column) { 626 setText (value == null ? "" : value.toString ()); 627 return this; 628 } 629 } 630 631 /** 632 * Computes the widths for table columns, trying to make every column the same size. 633 * 634 * @param maxWidth Maximum width of a column 635 * @param parentWidth Width of the table 636 * @param currentWidths Current widths of the columns 637 */ 638 639 protected static int [] resizeColumnsEqually (int maxWidth, int parentWidth, int currentWidths []) { 640 int columnCount = currentWidths.length; 641 int width = parentWidth / columnCount; 642 if (width >= maxWidth) { 643 int pos = 0; 644 for (int _width = width + 1, totalWidth = width * columnCount; totalWidth < parentWidth; pos++, totalWidth++) 645 currentWidths [pos] = _width; 646 for (; pos < columnCount; pos++) 647 currentWidths [pos] = width; 648 return currentWidths; 649 } 650 int totalWidth = 0; 651 for (int i = 0; i < columnCount; i++) 652 totalWidth += currentWidths [i]; 653 for (int minPos; totalWidth < parentWidth; currentWidths [minPos]++, totalWidth++) { 654 minPos = 0; 655 int minWidth = currentWidths [minPos]; 656 for (int i = 1; i < columnCount; i++) { 657 width = currentWidths [i]; 658 if (width > minWidth) 659 continue; 660 minWidth = width; 661 minPos = i; 662 } 663 } 664 return currentWidths; 665 } 666 667 /** 668 * Computes the widths for table columns, trying to make the ratios of column width to column weight the same. Column sizes will not be 669 * decreased below the requested minimums even if this will make the columns too large to fit in the view. Columns with a weight of 0 are 670 * never given more space than they start with. 671 * 672 * @param maxWidth Maximum width of a column 673 * @param parentWidth Width of the table 674 * @param minWidths Minimum widths of the columns 675 * @param currentWidths Current widths of the columns 676 * @param columnWeights Column weights 677 */ 678 679 protected static int [] resizeColumnsUnequally (int maxWidth, int parentWidth, int minWidths [], int currentWidths [], 680 double columnWeights []) { 681 int columnCount = columnWeights.length; 682 int startPos; 683 for (startPos = 0; startPos < columnCount; startPos++) 684 if (columnWeights [startPos] != 0.0) 685 break; 686 if (startPos == columnCount) 687 return resizeColumnsEqually (maxWidth, parentWidth, currentWidths); 688 int totalMinWidth = 0; 689 maxWidth = 0; 690 for (int i = 0; i < columnCount; i++) { 691 int width = minWidths [i]; 692 totalMinWidth += width; 693 if (width > maxWidth) 694 maxWidth = width; 695 } 696 if (totalMinWidth > parentWidth) 697 return minWidths; 698 int totalWidth = 0; 699 double columnDeltas [] = new double [columnCount]; 700 for (int i = 0; i < columnCount; i++) { 701 int width = currentWidths [i]; 702 totalWidth += width; 703 double weight = columnWeights [i]; 704 columnDeltas [i] = weight == 0.0 ? Double.NaN : (width - maxWidth) / weight; 705 } 706 for (startPos = 0; Double.isNaN (columnDeltas [startPos]); ) 707 startPos++; 708 for (; totalWidth < parentWidth; totalWidth++) { 709 int bestPos = startPos; 710 double bestDelta = columnDeltas [bestPos]; 711 for (int i = startPos + 1; i < columnCount; i++) { 712 double delta = columnDeltas [i]; 713 if (delta < bestDelta) { 714 bestPos = i; 715 bestDelta = delta; 716 } 717 } 718 columnDeltas [bestPos] = (++currentWidths [bestPos] - maxWidth) / columnWeights [bestPos]; 719 } 720 if (totalWidth == parentWidth) 721 return currentWidths; 722 for (int i = 0; i < columnCount; i++) 723 if (currentWidths [i] == minWidths [i]) 724 columnDeltas [i] = Double.NaN; 725 while (startPos < columnCount && Double.isNaN (columnDeltas [startPos])) 726 startPos++; 727 if (startPos == columnCount) 728 return currentWidths; 729 for (int i = startPos; i < columnCount; i++) { 730 double weight = columnWeights [i]; 731 columnDeltas [i] = weight == 0.0 ? Double.NaN : (currentWidths [i] - maxWidth) / weight; 732 } 733 for (; totalWidth > parentWidth; totalWidth--) { 734 int bestPos = startPos; 735 double bestDelta = columnDeltas [bestPos]; 736 for (int i = startPos + 1; i < columnCount; i++) { 737 double delta = columnDeltas [i]; 738 if (delta > bestDelta) { 739 bestPos = i; 740 bestDelta = delta; 741 } 742 } 743 int width = --currentWidths [bestPos]; 744 if (width != minWidths [bestPos]) { 745 columnDeltas [bestPos] = (width - maxWidth) / columnWeights [bestPos]; 746 continue; 747 } 748 columnDeltas [bestPos] = Double.NaN; 749 if (bestPos != startPos) 750 continue; 751 startPos++; 752 while (startPos < columnCount && Double.isNaN (columnDeltas [startPos])) 753 startPos++; 754 if (startPos == columnCount) 755 return currentWidths; 756 } 757 return currentWidths; 758 } 759 760 /** 761 * Creates a new table. 762 * 763 * @param model Model 764 */ 765 766 public BasicTable (BasicTableModel model) { 767 super (model); 768 setFocusCycleRoot (true); 769 setDefaultRenderer (Object.class, new BasicRenderer ()); 770 setDefaultEditor (Object.class, new BasicEditor ()); 771 } 772 773 public void doLayout () { 774 if (getClientProperty (CLIENT_COMPONENTSHOWN) == null) { 775 putClientProperty (CLIENT_COMPONENTSHOWN, this); 776 TableCellRenderer renderer = getTableHeader ().getDefaultRenderer (); 777 TableColumnModel columnModel = getColumnModel (); 778 double columnWeights [] = model.getColumnWeights (); 779 if (columnWeights == null) 780 for (int i = 0, l = getColumnCount (); i < l; i++) 781 columnModel.getColumn (i).setMinWidth (renderer.getTableCellRendererComponent (this, getColumnName (i), false, false, -1, i) 782 .getPreferredSize ().width + 5); 783 else { 784 for (int i = 0, l = getColumnCount (); i < l; i++) { 785 TableColumn column = columnModel.getColumn (i); 786 int width = renderer.getTableCellRendererComponent (this, getColumnName (i), false, false, -1, i).getPreferredSize ().width + 5; 787 column.setMinWidth (width); 788 if (columnWeights [i] == 0.0) 789 column.setMaxWidth (width); 790 } 791 } 792 } 793 if (getTableHeader ().getResizingColumn () == null) 794 resizeColumns (); 795 super.doLayout (); 796 } 797 798 /** 799 * The index of a column in this table. If no displayed column has the specified marker, the result is -1. 800 * 801 * @param marker Column marker 802 */ 803 804 public int findColumn (BasicTableModel.Marker marker) { 805 return convertColumnIndexToView (model.findColumn (marker)); 806 } 807 808 public Component prepareEditor (TableCellEditor editor, int row, int column) { 809 Component component = super.prepareEditor (editor, row, column); 810 if (component instanceof JButton) 811 return component; 812 component.requestFocus (); 813 if (component instanceof JTextField) 814 ((JTextField) component).selectAll (); 815 return component; 816 } 817 818 /** 819 * Sets the widths for table columns. 820 */ 821 822 public void resizeColumns () { 823 int columnCount = getColumnCount (); 824 int maxWidth = 0; 825 int totalWidth = 0; 826 int currentWidths [] = new int [columnCount]; 827 TableColumnModel columnModel = getColumnModel (); 828 for (int i = 0; i < columnCount; i++) { 829 int width = currentWidths [i] = columnModel.getColumn (i).getWidth (); 830 totalWidth += width; 831 if (width > maxWidth) 832 maxWidth = width; 833 } 834 double columnWeights [] = model.getColumnWeights (); 835 if (columnWeights == null) 836 currentWidths = resizeColumnsEqually (maxWidth, getParent ().getWidth (), currentWidths); 837 else { 838 int minWidths [] = new int [columnCount]; 839 for (int i = 0; i < columnCount; i++) 840 minWidths [i] = columnModel.getColumn (i).getMinWidth (); 841 currentWidths = resizeColumnsUnequally (maxWidth, getParent ().getWidth (), minWidths, currentWidths, columnWeights); 842 } 843 for (int i = 0; i < columnCount; i++) 844 columnModel.getColumn (i).setPreferredWidth (currentWidths [i]); 845 } 846 847 public void setModel (TableModel model) { 848 if (this.model != null) 849 throw new IllegalStateException ("BasicTable model can only be set by constructor."); 850 if (!(model instanceof BasicTableModel)) 851 throw new IllegalArgumentException ("BasicTable can only be used with a BasicTableModel."); 852 super.setModel (model); 853 this.model = (BasicTableModel) model; 854 } 855 856 protected boolean processKeyBinding (KeyStroke keyStroke, KeyEvent e, int condition, boolean pressed) { 857 if (e.isAltDown () && !e.isAltGraphDown () || e.isControlDown () || e.isMetaDown ()) 858 return false; 859 return super.processKeyBinding (keyStroke, e, condition, pressed); 860 } 861 }