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