1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.java.swing.plaf.windows; 27 28 import java.beans.PropertyChangeListener; 29 import java.beans.PropertyChangeEvent; 30 import javax.swing.plaf.basic.*; 31 import javax.swing.plaf.*; 32 import javax.swing.border.*; 33 import javax.swing.*; 34 import java.awt.event.*; 35 import java.awt.*; 36 37 import static com.sun.java.swing.plaf.windows.TMSchema.Part; 38 import static com.sun.java.swing.plaf.windows.TMSchema.State; 39 import static com.sun.java.swing.plaf.windows.XPStyle.Skin; 40 41 import sun.swing.DefaultLookup; 42 import sun.swing.StringUIClientPropertyKey; 43 44 import com.sun.java.swing.plaf.windows.WindowsBorders.DashedBorder; 45 46 /** 47 * Windows combo box. 48 * <p> 49 * <strong>Warning:</strong> 50 * Serialized objects of this class will not be compatible with 51 * future Swing releases. The current serialization support is appropriate 52 * for short term storage or RMI between applications running the same 53 * version of Swing. A future release of Swing will provide support for 54 * long term persistence. 55 * 56 * @author Tom Santos 57 * @author Igor Kushnirskiy 58 */ 59 60 public class WindowsComboBoxUI extends BasicComboBoxUI { 61 62 private static final MouseListener rolloverListener = 63 new MouseAdapter() { 64 private void handleRollover(MouseEvent e, boolean isRollover) { 65 JComboBox<?> comboBox = getComboBox(e); 66 WindowsComboBoxUI comboBoxUI = getWindowsComboBoxUI(e); 67 if (comboBox == null || comboBoxUI == null) { 68 return; 69 } 70 if (! comboBox.isEditable()) { 71 //mouse over editable ComboBox does not switch rollover 72 //for the arrow button 73 ButtonModel m = null; 74 if (comboBoxUI.arrowButton != null) { 75 m = comboBoxUI.arrowButton.getModel(); 76 } 77 if (m != null ) { 78 m.setRollover(isRollover); 79 } 80 } 81 comboBoxUI.isRollover = isRollover; 82 comboBox.repaint(); 83 } 84 85 public void mouseEntered(MouseEvent e) { 86 handleRollover(e, true); 87 } 88 89 public void mouseExited(MouseEvent e) { 90 handleRollover(e, false); 91 } 92 93 private JComboBox<?> getComboBox(MouseEvent event) { 94 Object source = event.getSource(); 95 JComboBox<?> rv = null; 96 if (source instanceof JComboBox) { 97 rv = (JComboBox) source; 98 } else if (source instanceof XPComboBoxButton) { 99 rv = ((XPComboBoxButton) source) 100 .getWindowsComboBoxUI().comboBox; 101 } else if (source instanceof JTextField && 102 ((JTextField) source).getParent() instanceof JComboBox) { 103 rv = (JComboBox) ((JTextField) source).getParent(); 104 } 105 return rv; 106 } 107 108 private WindowsComboBoxUI getWindowsComboBoxUI(MouseEvent event) { 109 JComboBox<?> comboBox = getComboBox(event); 110 WindowsComboBoxUI rv = null; 111 if (comboBox != null 112 && comboBox.getUI() instanceof WindowsComboBoxUI) { 113 rv = (WindowsComboBoxUI) comboBox.getUI(); 114 } 115 return rv; 116 } 117 118 }; 119 private boolean isRollover = false; 120 121 private static final PropertyChangeListener componentOrientationListener = 122 new PropertyChangeListener() { 123 public void propertyChange(PropertyChangeEvent e) { 124 String propertyName = e.getPropertyName(); 125 Object source = null; 126 if ("componentOrientation" == propertyName 127 && (source = e.getSource()) instanceof JComboBox 128 && ((JComboBox) source).getUI() instanceof 129 WindowsComboBoxUI) { 130 JComboBox<?> comboBox = (JComboBox) source; 131 WindowsComboBoxUI comboBoxUI = (WindowsComboBoxUI) comboBox.getUI(); 132 if (comboBoxUI.arrowButton instanceof XPComboBoxButton) { 133 ((XPComboBoxButton) comboBoxUI.arrowButton).setPart( 134 (comboBox.getComponentOrientation() == 135 ComponentOrientation.RIGHT_TO_LEFT) 136 ? Part.CP_DROPDOWNBUTTONLEFT 137 : Part.CP_DROPDOWNBUTTONRIGHT); 138 } 139 } 140 } 141 }; 142 143 public static ComponentUI createUI(JComponent c) { 144 return new WindowsComboBoxUI(); 145 } 146 147 public void installUI( JComponent c ) { 148 super.installUI( c ); 149 isRollover = false; 150 comboBox.setRequestFocusEnabled( true ); 151 if (XPStyle.getXP() != null && arrowButton != null) { 152 //we can not do it in installListeners because arrowButton 153 //is initialized after installListeners is invoked 154 comboBox.addMouseListener(rolloverListener); 155 arrowButton.addMouseListener(rolloverListener); 156 // set empty border as default to see vista animated border 157 comboBox.setBorder(new EmptyBorder(1,1,1,1)); 158 } 159 } 160 161 public void uninstallUI(JComponent c ) { 162 comboBox.removeMouseListener(rolloverListener); 163 if(arrowButton != null) { 164 arrowButton.removeMouseListener(rolloverListener); 165 } 166 super.uninstallUI( c ); 167 } 168 169 /** 170 * {@inheritDoc} 171 * @since 1.6 172 */ 173 @Override 174 protected void installListeners() { 175 super.installListeners(); 176 XPStyle xp = XPStyle.getXP(); 177 //button glyph for LTR and RTL combobox might differ 178 if (xp != null 179 && xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT)) { 180 comboBox.addPropertyChangeListener("componentOrientation", 181 componentOrientationListener); 182 } 183 } 184 185 /** 186 * {@inheritDoc} 187 * @since 1.6 188 */ 189 @Override 190 protected void uninstallListeners() { 191 super.uninstallListeners(); 192 comboBox.removePropertyChangeListener("componentOrientation", 193 componentOrientationListener); 194 } 195 196 /** 197 * {@inheritDoc} 198 * @since 1.6 199 */ 200 protected void configureEditor() { 201 super.configureEditor(); 202 if (XPStyle.getXP() != null) { 203 editor.addMouseListener(rolloverListener); 204 } 205 } 206 207 /** 208 * {@inheritDoc} 209 * @since 1.6 210 */ 211 protected void unconfigureEditor() { 212 super.unconfigureEditor(); 213 editor.removeMouseListener(rolloverListener); 214 } 215 216 /** 217 * {@inheritDoc} 218 * @since 1.6 219 */ 220 public void paint(Graphics g, JComponent c) { 221 if (XPStyle.getXP() != null) { 222 paintXPComboBoxBackground(g, c); 223 } 224 super.paint(g, c); 225 } 226 227 State getXPComboBoxState(JComponent c) { 228 State state = State.NORMAL; 229 if (!c.isEnabled()) { 230 state = State.DISABLED; 231 } else if (isPopupVisible(comboBox)) { 232 state = State.PRESSED; 233 } else if (comboBox.isEditable() 234 && comboBox.getEditor().getEditorComponent().isFocusOwner()) { 235 state = State.PRESSED; 236 } else if (isRollover) { 237 state = State.HOT; 238 } 239 return state; 240 } 241 242 private void paintXPComboBoxBackground(Graphics g, JComponent c) { 243 XPStyle xp = XPStyle.getXP(); 244 if (xp == null) { 245 return; 246 } 247 State state = getXPComboBoxState(c); 248 Skin skin = null; 249 if (! comboBox.isEditable() 250 && xp.isSkinDefined(c, Part.CP_READONLY)) { 251 skin = xp.getSkin(c, Part.CP_READONLY); 252 } 253 if (skin == null) { 254 skin = xp.getSkin(c, Part.CP_BORDER); 255 } 256 skin.paintSkin(g, 0, 0, c.getWidth(), c.getHeight(), state); 257 } 258 259 /** 260 * If necessary paints the currently selected item. 261 * 262 * @param g Graphics to paint to 263 * @param bounds Region to paint current value to 264 * @param hasFocus whether or not the JComboBox has focus 265 * @throws NullPointerException if any of the arguments are null. 266 * @since 1.5 267 */ 268 public void paintCurrentValue(Graphics g, Rectangle bounds, 269 boolean hasFocus) { 270 XPStyle xp = XPStyle.getXP(); 271 if ( xp != null) { 272 bounds.x += 2; 273 bounds.y += 2; 274 bounds.width -= 4; 275 bounds.height -= 4; 276 } else { 277 bounds.x += 1; 278 bounds.y += 1; 279 bounds.width -= 2; 280 bounds.height -= 2; 281 } 282 if (! comboBox.isEditable() 283 && xp != null 284 && xp.isSkinDefined(comboBox, Part.CP_READONLY)) { 285 // On vista for READNLY ComboBox 286 // color for currentValue is the same as for any other item 287 288 // mostly copied from javax.swing.plaf.basic.BasicComboBoxUI.paintCurrentValue 289 ListCellRenderer<Object> renderer = comboBox.getRenderer(); 290 Component c; 291 if ( hasFocus && !isPopupVisible(comboBox) ) { 292 c = renderer.getListCellRendererComponent( 293 listBox, 294 comboBox.getSelectedItem(), 295 -1, 296 true, 297 false ); 298 } else { 299 c = renderer.getListCellRendererComponent( 300 listBox, 301 comboBox.getSelectedItem(), 302 -1, 303 false, 304 false ); 305 } 306 c.setFont(comboBox.getFont()); 307 if ( comboBox.isEnabled() ) { 308 c.setForeground(comboBox.getForeground()); 309 c.setBackground(comboBox.getBackground()); 310 } else { 311 c.setForeground(DefaultLookup.getColor( 312 comboBox, this, "ComboBox.disabledForeground", null)); 313 c.setBackground(DefaultLookup.getColor( 314 comboBox, this, "ComboBox.disabledBackground", null)); 315 } 316 boolean shouldValidate = false; 317 if (c instanceof JPanel) { 318 shouldValidate = true; 319 } 320 currentValuePane.paintComponent(g, c, comboBox, bounds.x, bounds.y, 321 bounds.width, bounds.height, shouldValidate); 322 323 } else { 324 super.paintCurrentValue(g, bounds, hasFocus); 325 } 326 } 327 328 /** 329 * {@inheritDoc} 330 * @since 1.6 331 */ 332 public void paintCurrentValueBackground(Graphics g, Rectangle bounds, 333 boolean hasFocus) { 334 if (XPStyle.getXP() == null) { 335 super.paintCurrentValueBackground(g, bounds, hasFocus); 336 } 337 } 338 339 public Dimension getMinimumSize( JComponent c ) { 340 Dimension d = super.getMinimumSize(c); 341 if (XPStyle.getXP() != null) { 342 d.width += 7; 343 boolean isEditable = false; 344 if (c instanceof JComboBox) { 345 isEditable = ((JComboBox) c).isEditable(); 346 } 347 if (((JComboBox)c).getBorder() instanceof EmptyBorder) { 348 d.height += isEditable ? 2 : 4; 349 } else { 350 d.height += isEditable ? 4 : 6; 351 } 352 } else { 353 d.width += 4; 354 d.height += 2; 355 } 356 return d; 357 } 358 359 /** 360 * Creates a layout manager for managing the components which make up the 361 * combo box. 362 * 363 * @return an instance of a layout manager 364 */ 365 protected LayoutManager createLayoutManager() { 366 return new BasicComboBoxUI.ComboBoxLayoutManager() { 367 public void layoutContainer(Container parent) { 368 super.layoutContainer(parent); 369 370 if (XPStyle.getXP() != null && arrowButton != null) { 371 Dimension d = parent.getSize(); 372 Insets insets = getInsets(); 373 374 int borderInsetsCorrection = 0; 375 if (((JComboBox)parent).getBorder() instanceof EmptyBorder) { 376 borderInsetsCorrection = 1; 377 } 378 arrowButton.setBounds( 379 WindowsGraphicsUtils.isLeftToRight((JComboBox)parent) 380 ? (d.width - (insets.right - borderInsetsCorrection) 381 - arrowButton.getPreferredSize().width) 382 : insets.left - borderInsetsCorrection, 383 insets.top - borderInsetsCorrection, 384 arrowButton.getPreferredSize().width, 385 d.height - (insets.top - borderInsetsCorrection) - 386 (insets.bottom - borderInsetsCorrection)); 387 } 388 } 389 }; 390 } 391 392 protected void installKeyboardActions() { 393 super.installKeyboardActions(); 394 } 395 396 protected ComboPopup createPopup() { 397 return new WinComboPopUp(comboBox); 398 } 399 400 /** 401 * Creates the default editor that will be used in editable combo boxes. 402 * A default editor will be used only if an editor has not been 403 * explicitly set with <code>setEditor</code>. 404 * 405 * @return a <code>ComboBoxEditor</code> used for the combo box 406 * @see javax.swing.JComboBox#setEditor 407 */ 408 protected ComboBoxEditor createEditor() { 409 return new WindowsComboBoxEditor(); 410 } 411 412 /** 413 * {@inheritDoc} 414 * @since 1.6 415 */ 416 @Override 417 protected ListCellRenderer<Object> createRenderer() { 418 XPStyle xp = XPStyle.getXP(); 419 if (xp != null && xp.isSkinDefined(comboBox, Part.CP_READONLY)) { 420 return new WindowsComboBoxRenderer(); 421 } else { 422 return super.createRenderer(); 423 } 424 } 425 426 /** 427 * Creates an button which will be used as the control to show or hide 428 * the popup portion of the combo box. 429 * 430 * @return a button which represents the popup control 431 */ 432 protected JButton createArrowButton() { 433 XPStyle xp = XPStyle.getXP(); 434 if (xp != null) { 435 return new XPComboBoxButton(xp); 436 } else { 437 return super.createArrowButton(); 438 } 439 } 440 441 @SuppressWarnings("serial") // Superclass is not serializable across versions 442 private class XPComboBoxButton extends XPStyle.GlyphButton { 443 private State prevState = null; 444 445 public XPComboBoxButton(XPStyle xp) { 446 super(comboBox, 447 (! xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT)) 448 ? Part.CP_DROPDOWNBUTTON 449 : (comboBox.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) 450 ? Part.CP_DROPDOWNBUTTONLEFT 451 : Part.CP_DROPDOWNBUTTONRIGHT 452 ); 453 setRequestFocusEnabled(false); 454 } 455 456 @Override 457 protected State getState() { 458 State rv; 459 460 getModel().setPressed(comboBox.isPopupVisible()); 461 462 rv = super.getState(); 463 XPStyle xp = XPStyle.getXP(); 464 if (rv != State.DISABLED 465 && comboBox != null && ! comboBox.isEditable() 466 && xp != null && xp.isSkinDefined(comboBox, 467 Part.CP_DROPDOWNBUTTONRIGHT)) { 468 /* 469 * for non editable ComboBoxes Vista seems to have the 470 * same glyph for all non DISABLED states 471 */ 472 rv = State.NORMAL; 473 } 474 if (rv == State.NORMAL && (prevState == State.HOT || prevState == State.PRESSED)) { 475 /* 476 * State NORMAL of combobox button cannot overpaint states HOT or PRESSED 477 * Therefore HOT state must be painted from alpha 1 to 0 and not as usual that 478 * NORMAL state is painted from alpha 0 to alpha 1. 479 */ 480 skin.switchStates(true); 481 } 482 if (rv != prevState) { 483 prevState = rv; 484 } 485 486 return rv; 487 } 488 489 public Dimension getPreferredSize() { 490 return new Dimension(17, 21); 491 } 492 493 void setPart(Part part) { 494 setPart(comboBox, part); 495 } 496 497 WindowsComboBoxUI getWindowsComboBoxUI() { 498 return WindowsComboBoxUI.this; 499 } 500 } 501 502 503 /** 504 * Subclassed to add Windows specific Key Bindings. 505 * This class is now obsolete and doesn't do anything. 506 * Only included for backwards API compatibility. 507 * Do not call or override. 508 * 509 * @deprecated As of Java 2 platform v1.4. 510 */ 511 @Deprecated 512 @SuppressWarnings("serial") // Superclass is not serializable across versions 513 protected class WindowsComboPopup extends BasicComboPopup { 514 515 public WindowsComboPopup( JComboBox<Object> cBox ) { 516 super( cBox ); 517 } 518 519 protected KeyListener createKeyListener() { 520 return new InvocationKeyHandler(); 521 } 522 523 protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { 524 protected InvocationKeyHandler() { 525 WindowsComboPopup.this.super(); 526 } 527 } 528 } 529 530 @SuppressWarnings("serial") // Same-version serialization only 531 protected class WinComboPopUp extends BasicComboPopup { 532 private Skin listBoxBorder = null; 533 private XPStyle xp; 534 535 public WinComboPopUp(JComboBox<Object> combo) { 536 super(combo); 537 xp = XPStyle.getXP(); 538 if (xp != null && xp.isSkinDefined(combo, Part.LBCP_BORDER_NOSCROLL)) { 539 this.listBoxBorder = new Skin(combo, Part.LBCP_BORDER_NOSCROLL); 540 this.setBorder(new EmptyBorder(1,1,1,1)); 541 } 542 } 543 544 protected KeyListener createKeyListener() { 545 return new InvocationKeyHandler(); 546 } 547 548 protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { 549 protected InvocationKeyHandler() { 550 WinComboPopUp.this.super(); 551 } 552 } 553 554 protected void paintComponent(Graphics g) { 555 super.paintComponent(g); 556 if (this.listBoxBorder != null) { 557 this.listBoxBorder.paintSkinRaw(g, this.getX(), this.getY(), 558 this.getWidth(), this.getHeight(), State.HOT); 559 } 560 } 561 } 562 563 564 /** 565 * Subclassed to highlight selected item in an editable combo box. 566 */ 567 public static class WindowsComboBoxEditor 568 extends BasicComboBoxEditor.UIResource { 569 570 /** 571 * {@inheritDoc} 572 * @since 1.6 573 */ 574 protected JTextField createEditorComponent() { 575 JTextField editor = super.createEditorComponent(); 576 Border border = (Border)UIManager.get("ComboBox.editorBorder"); 577 578 if (border != null) { 579 editor.setBorder(border); 580 } 581 editor.setOpaque(false); 582 return editor; 583 } 584 585 public void setItem(Object item) { 586 super.setItem(item); 587 Object focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 588 if ((focus == editor) || (focus == editor.getParent())) { 589 editor.selectAll(); 590 } 591 } 592 } 593 594 /** 595 * Subclassed to set opacity {@code false} on the renderer 596 * and to show border for focused cells. 597 */ 598 @SuppressWarnings("serial") // Superclass is not serializable across versions 599 private static class WindowsComboBoxRenderer 600 extends BasicComboBoxRenderer.UIResource { 601 private static final Object BORDER_KEY 602 = new StringUIClientPropertyKey("BORDER_KEY"); 603 private static final Border NULL_BORDER = new EmptyBorder(0, 0, 0, 0); 604 605 // Create own version of DashedBorder with more space on left side 606 private class WindowsComboBoxDashedBorder extends DashedBorder { 607 608 public WindowsComboBoxDashedBorder(Color color, int thickness) { 609 super(color, thickness); 610 } 611 612 public WindowsComboBoxDashedBorder(Color color) { 613 super(color); 614 } 615 616 @Override 617 public Insets getBorderInsets(Component c, Insets i) { 618 return new Insets(0,2,0,0); 619 } 620 } 621 622 public WindowsComboBoxRenderer() { 623 super(); 624 625 // correct space on the left side of text items in the combo popup list 626 Insets i = getBorder().getBorderInsets(this); 627 setBorder(new EmptyBorder(0, 2, 0, i.right)); 628 } 629 /** 630 * {@inheritDoc} 631 */ 632 @Override 633 public Component getListCellRendererComponent( 634 JList<?> list, 635 Object value, 636 int index, 637 boolean isSelected, 638 boolean cellHasFocus) { 639 Component rv = 640 super.getListCellRendererComponent(list, value, index, 641 isSelected, cellHasFocus); 642 if (rv instanceof JComponent) { 643 JComponent component = (JComponent) rv; 644 if (index == -1 && isSelected) { 645 Border border = component.getBorder(); 646 Border dashedBorder = 647 new WindowsComboBoxDashedBorder(list.getForeground()); 648 component.setBorder(dashedBorder); 649 //store current border in client property if needed 650 if (component.getClientProperty(BORDER_KEY) == null) { 651 component.putClientProperty(BORDER_KEY, 652 (border == null) ? NULL_BORDER : border); 653 } 654 } else { 655 if (component.getBorder() instanceof 656 WindowsBorders.DashedBorder) { 657 Object storedBorder = component.getClientProperty(BORDER_KEY); 658 if (storedBorder instanceof Border) { 659 component.setBorder( 660 (storedBorder == NULL_BORDER) ? null 661 : (Border) storedBorder); 662 } 663 component.putClientProperty(BORDER_KEY, null); 664 } 665 } 666 if (index == -1) { 667 component.setOpaque(false); 668 component.setForeground(list.getForeground()); 669 } else { 670 component.setOpaque(true); 671 } 672 } 673 return rv; 674 } 675 676 } 677 }