1 /* 2 * Copyright (c) 1997, 2013, 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 if (rv != State.DISABLED 428 && comboBox != null && ! comboBox.isEditable() 429 && XPStyle.getXP().isSkinDefined(comboBox, 430 Part.CP_DROPDOWNBUTTONRIGHT)) { 431 /* 432 * for non editable ComboBoxes Vista seems to have the 433 * same glyph for all non DISABLED states 434 */ 435 rv = State.NORMAL; 436 } 437 return rv; 438 } 439 440 public Dimension getPreferredSize() { 441 return new Dimension(17, 21); 442 } 443 444 void setPart(Part part) { 445 setPart(comboBox, part); 446 } 447 448 WindowsComboBoxUI getWindowsComboBoxUI() { 449 return WindowsComboBoxUI.this; 450 } 451 } 452 453 454 /** 455 * Subclassed to add Windows specific Key Bindings. 456 * This class is now obsolete and doesn't do anything. 457 * Only included for backwards API compatibility. 458 * Do not call or override. 459 * 460 * @deprecated As of Java 2 platform v1.4. 461 */ 462 @Deprecated 463 @SuppressWarnings("serial") // Superclass is not serializable across versions 464 protected class WindowsComboPopup extends BasicComboPopup { 465 466 public WindowsComboPopup( JComboBox cBox ) { 467 super( cBox ); 468 } 469 470 protected KeyListener createKeyListener() { 471 return new InvocationKeyHandler(); 472 } 473 474 protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { 475 protected InvocationKeyHandler() { 476 WindowsComboPopup.this.super(); 477 } 478 } 479 } 480 481 482 /** 483 * Subclassed to highlight selected item in an editable combo box. 484 */ 485 public static class WindowsComboBoxEditor 486 extends BasicComboBoxEditor.UIResource { 487 488 /** 489 * {@inheritDoc} 490 * @since 1.6 491 */ 492 protected JTextField createEditorComponent() { 493 JTextField editor = super.createEditorComponent(); 494 Border border = (Border)UIManager.get("ComboBox.editorBorder"); 495 if (border != null) { 496 editor.setBorder(border); 497 } 498 editor.setOpaque(false); 499 return editor; 500 } 501 502 public void setItem(Object item) { 503 super.setItem(item); 504 Object focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 505 if ((focus == editor) || (focus == editor.getParent())) { 506 editor.selectAll(); 507 } 508 } 509 } 510 511 /** 512 * Subclassed to set opacity {@code false} on the renderer 513 * and to show border for focused cells. 514 */ 515 @SuppressWarnings("serial") // Superclass is not serializable across versions 516 private static class WindowsComboBoxRenderer 517 extends BasicComboBoxRenderer.UIResource { 518 private static final Object BORDER_KEY 519 = new StringUIClientPropertyKey("BORDER_KEY"); 520 private static final Border NULL_BORDER = new EmptyBorder(0, 0, 0, 0); 521 /** 522 * {@inheritDoc} 523 */ 524 @Override 525 public Component getListCellRendererComponent( 526 JList list, 527 Object value, 528 int index, 529 boolean isSelected, 530 boolean cellHasFocus) { 531 Component rv = 532 super.getListCellRendererComponent(list, value, index, 533 isSelected, cellHasFocus); 534 if (rv instanceof JComponent) { 535 JComponent component = (JComponent) rv; 536 if (index == -1 && isSelected) { 537 Border border = component.getBorder(); 538 Border dashedBorder = 539 new WindowsBorders.DashedBorder(list.getForeground()); 540 component.setBorder(dashedBorder); 541 //store current border in client property if needed 542 if (component.getClientProperty(BORDER_KEY) == null) { 543 component.putClientProperty(BORDER_KEY, 544 (border == null) ? NULL_BORDER : border); 545 } 546 } else { 547 if (component.getBorder() instanceof 548 WindowsBorders.DashedBorder) { 549 Object storedBorder = component.getClientProperty(BORDER_KEY); 550 if (storedBorder instanceof Border) { 551 component.setBorder( 552 (storedBorder == NULL_BORDER) ? null 553 : (Border) storedBorder); 554 } 555 component.putClientProperty(BORDER_KEY, null); 556 } 557 } 558 if (index == -1) { 559 component.setOpaque(false); 560 component.setForeground(list.getForeground()); 561 } else { 562 component.setOpaque(true); 563 } 564 } 565 return rv; 566 } 567 568 } 569 }