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 45 /** 46 * Windows combo box. 47 * <p> 48 * <strong>Warning:</strong> 49 * Serialized objects of this class will not be compatible with 50 * future Swing releases. The current serialization support is appropriate 51 * for short term storage or RMI between applications running the same 52 * version of Swing. A future release of Swing will provide support for 53 * long term persistence. 54 * 55 * @author Tom Santos 56 * @author Igor Kushnirskiy 57 */ 58 59 public class WindowsComboBoxUI extends BasicComboBoxUI { 60 61 private static final MouseListener rolloverListener = 62 new MouseAdapter() { 63 private void handleRollover(MouseEvent e, boolean isRollover) { 64 JComboBox comboBox = getComboBox(e); 65 WindowsComboBoxUI comboBoxUI = getWindowsComboBoxUI(e); 66 if (comboBox == null || comboBoxUI == null) { 67 return; 68 } 69 if (! comboBox.isEditable()) { 70 //mouse over editable ComboBox does not switch rollover 71 //for the arrow button 72 ButtonModel m = null; 73 if (comboBoxUI.arrowButton != null) { 74 m = comboBoxUI.arrowButton.getModel(); 75 } 76 if (m != null ) { 77 m.setRollover(isRollover); 78 } 79 } 80 comboBoxUI.isRollover = isRollover; 81 comboBox.repaint(); 82 } 83 84 public void mouseEntered(MouseEvent e) { 85 handleRollover(e, true); 86 } 87 88 public void mouseExited(MouseEvent e) { 89 handleRollover(e, false); 90 } 91 92 private JComboBox getComboBox(MouseEvent event) { 93 Object source = event.getSource(); 94 JComboBox rv = null; 95 if (source instanceof JComboBox) { 96 rv = (JComboBox) source; 97 } else if (source instanceof XPComboBoxButton) { 98 rv = ((XPComboBoxButton) source) 99 .getWindowsComboBoxUI().comboBox; 100 } 101 return rv; 102 } 103 104 private WindowsComboBoxUI getWindowsComboBoxUI(MouseEvent event) { 105 JComboBox comboBox = getComboBox(event); 106 WindowsComboBoxUI rv = null; 107 if (comboBox != null 108 && comboBox.getUI() instanceof WindowsComboBoxUI) { 109 rv = (WindowsComboBoxUI) comboBox.getUI(); 110 } 111 return rv; 112 } 113 114 }; 115 private boolean isRollover = false; 116 117 private static final PropertyChangeListener componentOrientationListener = 118 new PropertyChangeListener() { 119 public void propertyChange(PropertyChangeEvent e) { 120 String propertyName = e.getPropertyName(); 121 Object source = null; 122 if ("componentOrientation" == propertyName 123 && (source = e.getSource()) instanceof JComboBox 124 && ((JComboBox) source).getUI() instanceof 125 WindowsComboBoxUI) { 126 JComboBox comboBox = (JComboBox) source; 127 WindowsComboBoxUI comboBoxUI = (WindowsComboBoxUI) comboBox.getUI(); 128 if (comboBoxUI.arrowButton instanceof XPComboBoxButton) { 129 ((XPComboBoxButton) comboBoxUI.arrowButton).setPart( 130 (comboBox.getComponentOrientation() == 131 ComponentOrientation.RIGHT_TO_LEFT) 132 ? Part.CP_DROPDOWNBUTTONLEFT 133 : Part.CP_DROPDOWNBUTTONRIGHT); 134 } 135 } 136 } 137 }; 138 139 public static ComponentUI createUI(JComponent c) { 140 return new WindowsComboBoxUI(); 141 } 142 143 public void installUI( JComponent c ) { 144 super.installUI( c ); 145 isRollover = false; 146 comboBox.setRequestFocusEnabled( true ); 147 if (XPStyle.getXP() != null && arrowButton != null) { 148 //we can not do it in installListeners because arrowButton 149 //is initialized after installListeners is invoked 150 comboBox.addMouseListener(rolloverListener); 151 arrowButton.addMouseListener(rolloverListener); 152 } 153 } 154 155 public void uninstallUI(JComponent c ) { 156 comboBox.removeMouseListener(rolloverListener); 157 if(arrowButton != null) { 158 arrowButton.removeMouseListener(rolloverListener); 159 } 160 super.uninstallUI( c ); 161 } 162 163 /** 164 * {@inheritDoc} 165 * @since 1.6 166 */ 167 @Override 168 protected void installListeners() { 169 super.installListeners(); 170 XPStyle xp = XPStyle.getXP(); 171 //button glyph for LTR and RTL combobox might differ 172 if (xp != null 173 && xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT)) { 174 comboBox.addPropertyChangeListener("componentOrientation", 175 componentOrientationListener); 176 } 177 } 178 179 /** 180 * {@inheritDoc} 181 * @since 1.6 182 */ 183 @Override 184 protected void uninstallListeners() { 185 super.uninstallListeners(); 186 comboBox.removePropertyChangeListener("componentOrientation", 187 componentOrientationListener); 188 } 189 190 /** 191 * {@inheritDoc} 192 * @since 1.6 193 */ 194 protected void configureEditor() { 195 super.configureEditor(); 196 if (XPStyle.getXP() != null) { 197 editor.addMouseListener(rolloverListener); 198 } 199 } 200 201 /** 202 * {@inheritDoc} 203 * @since 1.6 204 */ 205 protected void unconfigureEditor() { 206 super.unconfigureEditor(); 207 editor.removeMouseListener(rolloverListener); 208 } 209 210 /** 211 * {@inheritDoc} 212 * @since 1.6 213 */ 214 public void paint(Graphics g, JComponent c) { 215 if (XPStyle.getXP() != null) { 216 paintXPComboBoxBackground(g, c); 217 } 218 super.paint(g, c); 219 } 220 221 State getXPComboBoxState(JComponent c) { 222 State state = State.NORMAL; 223 if (!c.isEnabled()) { 224 state = State.DISABLED; 225 } else if (isPopupVisible(comboBox)) { 226 state = State.PRESSED; 227 } else if (isRollover) { 228 state = State.HOT; 229 } 230 return state; 231 } 232 233 private void paintXPComboBoxBackground(Graphics g, JComponent c) { 234 XPStyle xp = XPStyle.getXP(); 235 if (xp == null) { 236 return; 237 } 238 State state = getXPComboBoxState(c); 239 Skin skin = null; 240 if (! comboBox.isEditable() 241 && xp.isSkinDefined(c, Part.CP_READONLY)) { 242 skin = xp.getSkin(c, Part.CP_READONLY); 243 } 244 if (skin == null) { 245 skin = xp.getSkin(c, Part.CP_COMBOBOX); 246 } 247 skin.paintSkin(g, 0, 0, c.getWidth(), c.getHeight(), state); 248 } 249 250 /** 251 * If necessary paints the currently selected item. 252 * 253 * @param g Graphics to paint to 254 * @param bounds Region to paint current value to 255 * @param hasFocus whether or not the JComboBox has focus 256 * @throws NullPointerException if any of the arguments are null. 257 * @since 1.5 258 */ 259 public void paintCurrentValue(Graphics g, Rectangle bounds, 260 boolean hasFocus) { 261 XPStyle xp = XPStyle.getXP(); 262 if ( xp != null) { 263 bounds.x += 2; 264 bounds.y += 2; 265 bounds.width -= 4; 266 bounds.height -= 4; 267 } else { 268 bounds.x += 1; 269 bounds.y += 1; 270 bounds.width -= 2; 271 bounds.height -= 2; 272 } 273 if (! comboBox.isEditable() 274 && xp != null 275 && xp.isSkinDefined(comboBox, Part.CP_READONLY)) { 276 // On vista for READNLY ComboBox 277 // color for currentValue is the same as for any other item 278 279 // mostly copied from javax.swing.plaf.basic.BasicComboBoxUI.paintCurrentValue 280 ListCellRenderer renderer = comboBox.getRenderer(); 281 Component c; 282 if ( hasFocus && !isPopupVisible(comboBox) ) { 283 c = renderer.getListCellRendererComponent( 284 listBox, 285 comboBox.getSelectedItem(), 286 -1, 287 true, 288 false ); 289 } else { 290 c = renderer.getListCellRendererComponent( 291 listBox, 292 comboBox.getSelectedItem(), 293 -1, 294 false, 295 false ); 296 } 297 c.setFont(comboBox.getFont()); 298 if ( comboBox.isEnabled() ) { 299 c.setForeground(comboBox.getForeground()); 300 c.setBackground(comboBox.getBackground()); 301 } else { 302 c.setForeground(DefaultLookup.getColor( 303 comboBox, this, "ComboBox.disabledForeground", null)); 304 c.setBackground(DefaultLookup.getColor( 305 comboBox, this, "ComboBox.disabledBackground", null)); 306 } 307 boolean shouldValidate = false; 308 if (c instanceof JPanel) { 309 shouldValidate = true; 310 } 311 currentValuePane.paintComponent(g, c, comboBox, bounds.x, bounds.y, 312 bounds.width, bounds.height, shouldValidate); 313 314 } else { 315 super.paintCurrentValue(g, bounds, hasFocus); 316 } 317 } 318 319 /** 320 * {@inheritDoc} 321 * @since 1.6 322 */ 323 public void paintCurrentValueBackground(Graphics g, Rectangle bounds, 324 boolean hasFocus) { 325 if (XPStyle.getXP() == null) { 326 super.paintCurrentValueBackground(g, bounds, hasFocus); 327 } 328 } 329 330 public Dimension getMinimumSize( JComponent c ) { 331 Dimension d = super.getMinimumSize(c); 332 if (XPStyle.getXP() != null) { 333 d.width += 5; 334 } else { 335 d.width += 4; 336 } 337 d.height += 2; 338 return d; 339 } 340 341 /** 342 * Creates a layout manager for managing the components which make up the 343 * combo box. 344 * 345 * @return an instance of a layout manager 346 */ 347 protected LayoutManager createLayoutManager() { 348 return new BasicComboBoxUI.ComboBoxLayoutManager() { 349 public void layoutContainer(Container parent) { 350 super.layoutContainer(parent); 351 352 if (XPStyle.getXP() != null && arrowButton != null) { 353 Dimension d = parent.getSize(); 354 Insets insets = getInsets(); 355 int buttonWidth = arrowButton.getPreferredSize().width; 356 arrowButton.setBounds(WindowsGraphicsUtils.isLeftToRight((JComboBox)parent) 357 ? (d.width - insets.right - buttonWidth) 358 : insets.left, 359 insets.top, 360 buttonWidth, d.height - insets.top - insets.bottom); 361 } 362 } 363 }; 364 } 365 366 protected void installKeyboardActions() { 367 super.installKeyboardActions(); 368 } 369 370 protected ComboPopup createPopup() { 371 return super.createPopup(); 372 } 373 374 /** 375 * Creates the default editor that will be used in editable combo boxes. 376 * A default editor will be used only if an editor has not been 377 * explicitly set with <code>setEditor</code>. 378 * 379 * @return a <code>ComboBoxEditor</code> used for the combo box 380 * @see javax.swing.JComboBox#setEditor 381 */ 382 protected ComboBoxEditor createEditor() { 383 return new WindowsComboBoxEditor(); 384 } 385 386 /** 387 * {@inheritDoc} 388 * @since 1.6 389 */ 390 @Override 391 protected ListCellRenderer createRenderer() { 392 XPStyle xp = XPStyle.getXP(); 393 if (xp != null && xp.isSkinDefined(comboBox, Part.CP_READONLY)) { 394 return new WindowsComboBoxRenderer(); 395 } else { 396 return super.createRenderer(); 397 } 398 } 399 400 /** 401 * Creates an button which will be used as the control to show or hide 402 * the popup portion of the combo box. 403 * 404 * @return a button which represents the popup control 405 */ 406 protected JButton createArrowButton() { 407 XPStyle xp = XPStyle.getXP(); 408 if (xp != null) { 409 return new XPComboBoxButton(xp); 410 } else { 411 return super.createArrowButton(); 412 } 413 } 414 415 @SuppressWarnings("serial") // Superclass is not serializable across versions 416 private class XPComboBoxButton extends XPStyle.GlyphButton { 417 public XPComboBoxButton(XPStyle xp) { 418 super(null, 419 (! xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT)) 420 ? Part.CP_DROPDOWNBUTTON 421 : (comboBox.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) 422 ? Part.CP_DROPDOWNBUTTONLEFT 423 : Part.CP_DROPDOWNBUTTONRIGHT 424 ); 425 setRequestFocusEnabled(false); 426 } 427 428 @Override 429 protected State getState() { 430 State rv; 431 rv = super.getState(); 432 XPStyle xp = XPStyle.getXP(); 433 if (rv != State.DISABLED 434 && comboBox != null && ! comboBox.isEditable() 435 && xp != null && xp.isSkinDefined(comboBox, 436 Part.CP_DROPDOWNBUTTONRIGHT)) { 437 /* 438 * for non editable ComboBoxes Vista seems to have the 439 * same glyph for all non DISABLED states 440 */ 441 rv = State.NORMAL; 442 } 443 return rv; 444 } 445 446 public Dimension getPreferredSize() { 447 return new Dimension(17, 21); 448 } 449 450 void setPart(Part part) { 451 setPart(comboBox, part); 452 } 453 454 WindowsComboBoxUI getWindowsComboBoxUI() { 455 return WindowsComboBoxUI.this; 456 } 457 } 458 459 460 /** 461 * Subclassed to add Windows specific Key Bindings. 462 * This class is now obsolete and doesn't do anything. 463 * Only included for backwards API compatibility. 464 * Do not call or override. 465 * 466 * @deprecated As of Java 2 platform v1.4. 467 */ 468 @Deprecated 469 @SuppressWarnings("serial") // Superclass is not serializable across versions 470 protected class WindowsComboPopup extends BasicComboPopup { 471 472 public WindowsComboPopup( JComboBox cBox ) { 473 super( cBox ); 474 } 475 476 protected KeyListener createKeyListener() { 477 return new InvocationKeyHandler(); 478 } 479 480 protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { 481 protected InvocationKeyHandler() { 482 WindowsComboPopup.this.super(); 483 } 484 } 485 } 486 487 488 /** 489 * Subclassed to highlight selected item in an editable combo box. 490 */ 491 public static class WindowsComboBoxEditor 492 extends BasicComboBoxEditor.UIResource { 493 494 /** 495 * {@inheritDoc} 496 * @since 1.6 497 */ 498 protected JTextField createEditorComponent() { 499 JTextField editor = super.createEditorComponent(); 500 Border border = (Border)UIManager.get("ComboBox.editorBorder"); 501 if (border != null) { 502 editor.setBorder(border); 503 } 504 editor.setOpaque(false); 505 return editor; 506 } 507 508 public void setItem(Object item) { 509 super.setItem(item); 510 Object focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 511 if ((focus == editor) || (focus == editor.getParent())) { 512 editor.selectAll(); 513 } 514 } 515 } 516 517 /** 518 * Subclassed to set opacity {@code false} on the renderer 519 * and to show border for focused cells. 520 */ 521 @SuppressWarnings("serial") // Superclass is not serializable across versions 522 private static class WindowsComboBoxRenderer 523 extends BasicComboBoxRenderer.UIResource { 524 private static final Object BORDER_KEY 525 = new StringUIClientPropertyKey("BORDER_KEY"); 526 private static final Border NULL_BORDER = new EmptyBorder(0, 0, 0, 0); 527 /** 528 * {@inheritDoc} 529 */ 530 @Override 531 public Component getListCellRendererComponent( 532 JList list, 533 Object value, 534 int index, 535 boolean isSelected, 536 boolean cellHasFocus) { 537 Component rv = 538 super.getListCellRendererComponent(list, value, index, 539 isSelected, cellHasFocus); 540 if (rv instanceof JComponent) { 541 JComponent component = (JComponent) rv; 542 if (index == -1 && isSelected) { 543 Border border = component.getBorder(); 544 Border dashedBorder = 545 new WindowsBorders.DashedBorder(list.getForeground()); 546 component.setBorder(dashedBorder); 547 //store current border in client property if needed 548 if (component.getClientProperty(BORDER_KEY) == null) { 549 component.putClientProperty(BORDER_KEY, 550 (border == null) ? NULL_BORDER : border); 551 } 552 } else { 553 if (component.getBorder() instanceof 554 WindowsBorders.DashedBorder) { 555 Object storedBorder = component.getClientProperty(BORDER_KEY); 556 if (storedBorder instanceof Border) { 557 component.setBorder( 558 (storedBorder == NULL_BORDER) ? null 559 : (Border) storedBorder); 560 } 561 component.putClientProperty(BORDER_KEY, null); 562 } 563 } 564 if (index == -1) { 565 component.setOpaque(false); 566 component.setForeground(list.getForeground()); 567 } else { 568 component.setOpaque(true); 569 } 570 } 571 return rv; 572 } 573 574 } 575 }