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 26package com.sun.java.swing.plaf.windows; 27 28import java.beans.PropertyChangeListener; 29import java.beans.PropertyChangeEvent; 30import javax.swing.plaf.basic.*; 31import javax.swing.plaf.*; 32import javax.swing.border.*; 33import javax.swing.*; 34import java.awt.event.*; 35import java.awt.*; 36 37import static com.sun.java.swing.plaf.windows.TMSchema.Part; 38import static com.sun.java.swing.plaf.windows.TMSchema.State; 39import static com.sun.java.swing.plaf.windows.XPStyle.Skin; 40 41import sun.swing.DefaultLookup; 42import sun.swing.StringUIClientPropertyKey; 43 44import 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 60public 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(0,0,0,0)); 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 d.height += isEditable ? 4 : 6; 348 } else { 349 d.width += 4; 350 d.height += 2; 351 } 352 return d; 353 } 354 355 /** 356 * Creates a layout manager for managing the components which make up the 357 * combo box. 358 * 359 * @return an instance of a layout manager 360 */ 361 protected LayoutManager createLayoutManager() { 362 return new BasicComboBoxUI.ComboBoxLayoutManager() { 363 public void layoutContainer(Container parent) { 364 super.layoutContainer(parent); 365 366 if (XPStyle.getXP() != null && arrowButton != null) { 367 Dimension d = parent.getSize(); 368 Insets insets = getInsets(); 369 int buttonWidth = arrowButton.getPreferredSize().width; 370 arrowButton.setBounds(WindowsGraphicsUtils.isLeftToRight((JComboBox)parent) 371 ? (d.width - insets.right - buttonWidth) 372 : insets.left, 373 insets.top, 374 buttonWidth, d.height - insets.top - insets.bottom); 375 } 376 } 377 }; 378 } 379 380 protected void installKeyboardActions() { 381 super.installKeyboardActions(); 382 } 383 384 protected ComboPopup createPopup() { 385 return new WinComboPopUp(comboBox); 386 } 387 388 /** 389 * Creates the default editor that will be used in editable combo boxes. 390 * A default editor will be used only if an editor has not been 391 * explicitly set with <code>setEditor</code>. 392 * 393 * @return a <code>ComboBoxEditor</code> used for the combo box 394 * @see javax.swing.JComboBox#setEditor 395 */ 396 protected ComboBoxEditor createEditor() { 397 return new WindowsComboBoxEditor(); 398 } 399 400 /** 401 * {@inheritDoc} 402 * @since 1.6 403 */ 404 @Override 405 protected ListCellRenderer<Object> createRenderer() { 406 XPStyle xp = XPStyle.getXP(); 407 if (xp != null && xp.isSkinDefined(comboBox, Part.CP_READONLY)) { 408 return new WindowsComboBoxRenderer(); 409 } else { 410 return super.createRenderer(); 411 } 412 } 413 414 /** 415 * Creates an button which will be used as the control to show or hide 416 * the popup portion of the combo box. 417 * 418 * @return a button which represents the popup control 419 */ 420 protected JButton createArrowButton() { 421 XPStyle xp = XPStyle.getXP(); 422 if (xp != null) { 423 return new XPComboBoxButton(xp); 424 } else { 425 return super.createArrowButton(); 426 } 427 } 428 429 @SuppressWarnings("serial") // Superclass is not serializable across versions 430 private class XPComboBoxButton extends XPStyle.GlyphButton { 431 private State prevState = null; 432 433 public XPComboBoxButton(XPStyle xp) { 434 super(comboBox, 435 (! xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT)) 436 ? Part.CP_DROPDOWNBUTTON 437 : (comboBox.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) 438 ? Part.CP_DROPDOWNBUTTONLEFT 439 : Part.CP_DROPDOWNBUTTONRIGHT 440 ); 441 setRequestFocusEnabled(false); 442 } 443 444 @Override 445 protected State getState() { 446 State rv; 447 448 getModel().setPressed(comboBox.isPopupVisible()); 449 450 rv = super.getState(); 451 XPStyle xp = XPStyle.getXP(); 452 if (rv != State.DISABLED 453 && comboBox != null && ! comboBox.isEditable() 454 && xp != null && xp.isSkinDefined(comboBox, 455 Part.CP_DROPDOWNBUTTONRIGHT)) { 456 /* 457 * for non editable ComboBoxes Vista seems to have the 458 * same glyph for all non DISABLED states 459 */ 460 rv = State.NORMAL; 461 } 462 if (rv == State.NORMAL && (prevState == State.HOT || prevState == State.PRESSED)) { 463 /* 464 * State NORMAL of combobox button cannot overpaint states HOT or PRESSED 465 * Therefore HOT state must be painted from alpha 1 to 0 and not as usual that 466 * NORMAL state is painted from alpha 0 to alpha 1. 467 */ 468 skin.switchStates(true); 469 } 470 if (rv != prevState) { 471 prevState = rv; 472 } 473 474 return rv; 475 } 476 477 public Dimension getPreferredSize() { 478 return new Dimension(17, 21); 479 } 480 481 void setPart(Part part) { 482 setPart(comboBox, part); 483 } 484 485 WindowsComboBoxUI getWindowsComboBoxUI() { 486 return WindowsComboBoxUI.this; 487 } 488 } 489 490 491 /** 492 * Subclassed to add Windows specific Key Bindings. 493 * This class is now obsolete and doesn't do anything. 494 * Only included for backwards API compatibility. 495 * Do not call or override. 496 * 497 * @deprecated As of Java 2 platform v1.4. 498 */ 499 @Deprecated 500 @SuppressWarnings("serial") // Superclass is not serializable across versions 501 protected class WindowsComboPopup extends BasicComboPopup { 502 503 public WindowsComboPopup( JComboBox<Object> cBox ) { 504 super( cBox ); 505 } 506 507 protected KeyListener createKeyListener() { 508 return new InvocationKeyHandler(); 509 } 510 511 protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { 512 protected InvocationKeyHandler() { 513 WindowsComboPopup.this.super(); 514 } 515 } 516 } 517 518 @SuppressWarnings("serial") // Same-version serialization only 519 protected class WinComboPopUp extends BasicComboPopup { 520 private Skin listBoxBorder = null; 521 private XPStyle xp; 522 523 public WinComboPopUp(JComboBox<Object> combo) { 524 super(combo); 525 xp = XPStyle.getXP(); 526 if (xp != null && xp.isSkinDefined(combo, Part.LBCP_BORDER_NOSCROLL)) { 527 this.listBoxBorder = new Skin(combo, Part.LBCP_BORDER_NOSCROLL); 528 this.setBorder(new EmptyBorder(1,1,1,1)); 529 } 530 } 531 532 protected KeyListener createKeyListener() { 533 return new InvocationKeyHandler(); 534 } 535 536 protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { 537 protected InvocationKeyHandler() { 538 WinComboPopUp.this.super(); 539 } 540 } 541 542 protected void paintComponent(Graphics g) { 543 super.paintComponent(g); 544 if (this.listBoxBorder != null) { 545 this.listBoxBorder.paintSkinRaw(g, this.getX(), this.getY(), 546 this.getWidth(), this.getHeight(), State.HOT); 547 } 548 } 549 } 550 551 552 /** 553 * Subclassed to highlight selected item in an editable combo box. 554 */ 555 public static class WindowsComboBoxEditor 556 extends BasicComboBoxEditor.UIResource { 557 558 /** 559 * {@inheritDoc} 560 * @since 1.6 561 */ 562 protected JTextField createEditorComponent() { 563 JTextField editor = super.createEditorComponent(); 564 Border border = (Border)UIManager.get("ComboBox.editorBorder"); 565 566 if (border != null) { 567 editor.setBorder(border); 568 } 569 editor.setOpaque(false); 570 return editor; 571 } 572 573 public void setItem(Object item) { 574 super.setItem(item); 575 Object focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 576 if ((focus == editor) || (focus == editor.getParent())) { 577 editor.selectAll(); 578 } 579 } 580 } 581 582 /** 583 * Subclassed to set opacity {@code false} on the renderer 584 * and to show border for focused cells. 585 */ 586 @SuppressWarnings("serial") // Superclass is not serializable across versions 587 private static class WindowsComboBoxRenderer 588 extends BasicComboBoxRenderer.UIResource { 589 private static final Object BORDER_KEY 590 = new StringUIClientPropertyKey("BORDER_KEY"); 591 private static final Border NULL_BORDER = new EmptyBorder(0, 0, 0, 0); 592 593 // Create own version of DashedBorder with more space on left side 594 private class WindowsComboBoxDashedBorder extends DashedBorder { 595 596 public WindowsComboBoxDashedBorder(Color color, int thickness) { 597 super(color, thickness); 598 } 599 600 public WindowsComboBoxDashedBorder(Color color) { 601 super(color); 602 } 603 604 @Override 605 public Insets getBorderInsets(Component c, Insets i) { 606 return new Insets(0,2,0,0); 607 } 608 } 609 610 public WindowsComboBoxRenderer() { 611 super(); 612 613 // correct space on the left side of text items in the combo popup list 614 Insets i = getBorder().getBorderInsets(this); 615 setBorder(new EmptyBorder(0, 2, 0, i.right)); 616 } 617 /** 618 * {@inheritDoc} 619 */ 620 @Override 621 public Component getListCellRendererComponent( 622 JList<?> list, 623 Object value, 624 int index, 625 boolean isSelected, 626 boolean cellHasFocus) { 627 Component rv = 628 super.getListCellRendererComponent(list, value, index, 629 isSelected, cellHasFocus); 630 if (rv instanceof JComponent) { 631 JComponent component = (JComponent) rv; 632 if (index == -1 && isSelected) { 633 Border border = component.getBorder(); 634 Border dashedBorder = 635 new WindowsComboBoxDashedBorder(list.getForeground()); 636 component.setBorder(dashedBorder); 637 //store current border in client property if needed 638 if (component.getClientProperty(BORDER_KEY) == null) { 639 component.putClientProperty(BORDER_KEY, 640 (border == null) ? NULL_BORDER : border); 641 } 642 } else { 643 if (component.getBorder() instanceof 644 WindowsBorders.DashedBorder) { 645 Object storedBorder = component.getClientProperty(BORDER_KEY); 646 if (storedBorder instanceof Border) { 647 component.setBorder( 648 (storedBorder == NULL_BORDER) ? null 649 : (Border) storedBorder); 650 } 651 component.putClientProperty(BORDER_KEY, null); 652 } 653 } 654 if (index == -1) { 655 component.setOpaque(false); 656 component.setForeground(list.getForeground()); 657 } else { 658 component.setOpaque(true); 659 } 660 } 661 return rv; 662 } 663 664 } 665} 666