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