1 /* 2 * Copyright (c) 2002, 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 package sun.swing.plaf.synth; 26 27 import javax.swing.plaf.synth.*; 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.beans.*; 31 import java.io.File; 32 import java.util.regex.*; 33 34 import javax.swing.*; 35 import javax.swing.border.*; 36 import javax.swing.event.*; 37 import javax.swing.filechooser.*; 38 import javax.swing.plaf.*; 39 import javax.swing.plaf.basic.BasicFileChooserUI; 40 41 /** 42 * Synth FileChooserUI. 43 * 44 * Note: This class is abstract. It does not actually create the file chooser GUI. 45 * <p> 46 * Note that the classes in the com.sun.java.swing.plaf.synth 47 * package are not 48 * part of the core Java APIs. They are a part of Sun's JDK and JRE 49 * distributions. Although other licensees may choose to distribute 50 * these classes, developers cannot depend on their availability in 51 * non-Sun implementations. Additionally this API may change in 52 * incompatible ways between releases. While this class is public, it 53 * shoud be considered an implementation detail, and subject to change. 54 * 55 * @author Leif Samuelsson 56 * @author Jeff Dinkins 57 */ 58 public abstract class SynthFileChooserUI extends BasicFileChooserUI implements 59 SynthUI { 60 private JButton approveButton, cancelButton; 61 62 private SynthStyle style; 63 64 // Some generic FileChooser functions 65 private Action fileNameCompletionAction = new FileNameCompletionAction(); 66 67 private FileFilter actualFileFilter = null; 68 private GlobFilter globFilter = null; 69 70 public static ComponentUI createUI(JComponent c) { 71 return new SynthFileChooserUIImpl((JFileChooser)c); 72 } 73 74 public SynthFileChooserUI(JFileChooser b) { 75 super(b); 76 } 77 78 public SynthContext getContext(JComponent c) { 79 return new SynthContext(c, Region.FILE_CHOOSER, style, 80 getComponentState(c)); 81 } 82 83 protected SynthContext getContext(JComponent c, int state) { 84 Region region = SynthLookAndFeel.getRegion(c); 85 return new SynthContext(c, Region.FILE_CHOOSER, style, state); 86 } 87 88 private Region getRegion(JComponent c) { 89 return SynthLookAndFeel.getRegion(c); 90 } 91 92 private int getComponentState(JComponent c) { 93 if (c.isEnabled()) { 94 if (c.isFocusOwner()) { 95 return ENABLED | FOCUSED; 96 } 97 return ENABLED; 98 } 99 return DISABLED; 100 } 101 102 private void updateStyle(JComponent c) { 103 SynthStyle newStyle = SynthLookAndFeel.getStyleFactory().getStyle(c, 104 Region.FILE_CHOOSER); 105 if (newStyle != style) { 106 if (style != null) { 107 style.uninstallDefaults(getContext(c, ENABLED)); 108 } 109 style = newStyle; 110 SynthContext context = getContext(c, ENABLED); 111 style.installDefaults(context); 112 Border border = c.getBorder(); 113 if (border == null || border instanceof UIResource) { 114 c.setBorder(new UIBorder(style.getInsets(context, null))); 115 } 116 117 directoryIcon = style.getIcon(context, "FileView.directoryIcon"); 118 fileIcon = style.getIcon(context, "FileView.fileIcon"); 119 computerIcon = style.getIcon(context, "FileView.computerIcon"); 120 hardDriveIcon = style.getIcon(context, "FileView.hardDriveIcon"); 121 floppyDriveIcon = style.getIcon(context, "FileView.floppyDriveIcon"); 122 123 newFolderIcon = style.getIcon(context, "FileChooser.newFolderIcon"); 124 upFolderIcon = style.getIcon(context, "FileChooser.upFolderIcon"); 125 homeFolderIcon = style.getIcon(context, "FileChooser.homeFolderIcon"); 126 detailsViewIcon = style.getIcon(context, "FileChooser.detailsViewIcon"); 127 listViewIcon = style.getIcon(context, "FileChooser.listViewIcon"); 128 } 129 } 130 131 public void installUI(JComponent c) { 132 super.installUI(c); 133 SwingUtilities.replaceUIActionMap(c, createActionMap()); 134 } 135 136 public void installComponents(JFileChooser fc) { 137 SynthContext context = getContext(fc, ENABLED); 138 139 cancelButton = new JButton(cancelButtonText); 140 cancelButton.setName("SynthFileChooser.cancelButton"); 141 cancelButton.setIcon(context.getStyle().getIcon(context, "FileChooser.cancelIcon")); 142 cancelButton.setMnemonic(cancelButtonMnemonic); 143 cancelButton.setToolTipText(cancelButtonToolTipText); 144 cancelButton.addActionListener(getCancelSelectionAction()); 145 146 approveButton = new JButton(getApproveButtonText(fc)); 147 approveButton.setName("SynthFileChooser.approveButton"); 148 approveButton.setIcon(context.getStyle().getIcon(context, "FileChooser.okIcon")); 149 approveButton.setMnemonic(getApproveButtonMnemonic(fc)); 150 approveButton.setToolTipText(getApproveButtonToolTipText(fc)); 151 approveButton.addActionListener(getApproveSelectionAction()); 152 153 } 154 155 public void uninstallComponents(JFileChooser fc) { 156 fc.removeAll(); 157 } 158 159 protected void installListeners(JFileChooser fc) { 160 super.installListeners(fc); 161 162 getModel().addListDataListener(new ListDataListener() { 163 public void contentsChanged(ListDataEvent e) { 164 // Update the selection after JList has been updated 165 new DelayedSelectionUpdater(); 166 } 167 public void intervalAdded(ListDataEvent e) { 168 new DelayedSelectionUpdater(); 169 } 170 public void intervalRemoved(ListDataEvent e) { 171 } 172 }); 173 174 } 175 176 private class DelayedSelectionUpdater implements Runnable { 177 DelayedSelectionUpdater() { 178 SwingUtilities.invokeLater(this); 179 } 180 181 public void run() { 182 updateFileNameCompletion(); 183 } 184 } 185 186 protected abstract ActionMap createActionMap(); 187 188 189 protected void installDefaults(JFileChooser fc) { 190 super.installDefaults(fc); 191 updateStyle(fc); 192 } 193 194 protected void uninstallDefaults(JFileChooser fc) { 195 super.uninstallDefaults(fc); 196 197 SynthContext context = getContext(getFileChooser(), ENABLED); 198 style.uninstallDefaults(context); 199 style = null; 200 } 201 202 protected void installIcons(JFileChooser fc) { 203 // The icons are installed in updateStyle, not here 204 } 205 206 public void update(Graphics g, JComponent c) { 207 SynthContext context = getContext(c); 208 209 if (c.isOpaque()) { 210 g.setColor(style.getColor(context, ColorType.BACKGROUND)); 211 g.fillRect(0, 0, c.getWidth(), c.getHeight()); 212 } 213 214 style.getPainter(context).paintFileChooserBackground(context, 215 g, 0, 0, c.getWidth(), c.getHeight()); 216 paint(context, g); 217 } 218 219 public void paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h) { 220 } 221 222 public void paint(Graphics g, JComponent c) { 223 SynthContext context = getContext(c); 224 225 paint(context, g); 226 } 227 228 protected void paint(SynthContext context, Graphics g) { 229 } 230 231 abstract public void setFileName(String fileName); 232 abstract public String getFileName(); 233 234 protected void doSelectedFileChanged(PropertyChangeEvent e) { 235 } 236 237 protected void doSelectedFilesChanged(PropertyChangeEvent e) { 238 } 239 240 protected void doDirectoryChanged(PropertyChangeEvent e) { 241 } 242 243 protected void doAccessoryChanged(PropertyChangeEvent e) { 244 } 245 246 protected void doFileSelectionModeChanged(PropertyChangeEvent e) { 247 } 248 249 protected void doMultiSelectionChanged(PropertyChangeEvent e) { 250 if (!getFileChooser().isMultiSelectionEnabled()) { 251 getFileChooser().setSelectedFiles(null); 252 } 253 } 254 255 protected void doControlButtonsChanged(PropertyChangeEvent e) { 256 if (getFileChooser().getControlButtonsAreShown()) { 257 approveButton.setText(getApproveButtonText(getFileChooser())); 258 approveButton.setToolTipText(getApproveButtonToolTipText(getFileChooser())); 259 approveButton.setMnemonic(getApproveButtonMnemonic(getFileChooser())); 260 } 261 } 262 263 protected void doAncestorChanged(PropertyChangeEvent e) { 264 } 265 266 public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) { 267 return new SynthFCPropertyChangeListener(); 268 } 269 270 private class SynthFCPropertyChangeListener implements PropertyChangeListener { 271 public void propertyChange(PropertyChangeEvent e) { 272 String prop = e.getPropertyName(); 273 if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) { 274 doFileSelectionModeChanged(e); 275 } else if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) { 276 doSelectedFileChanged(e); 277 } else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) { 278 doSelectedFilesChanged(e); 279 } else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) { 280 doDirectoryChanged(e); 281 } else if (prop == JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY) { 282 doMultiSelectionChanged(e); 283 } else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) { 284 doAccessoryChanged(e); 285 } else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY || 286 prop == JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY || 287 prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY || 288 prop == JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY) { 289 doControlButtonsChanged(e); 290 } else if (prop.equals("componentOrientation")) { 291 ComponentOrientation o = (ComponentOrientation)e.getNewValue(); 292 JFileChooser cc = (JFileChooser)e.getSource(); 293 if (o != (ComponentOrientation)e.getOldValue()) { 294 cc.applyComponentOrientation(o); 295 } 296 } else if (prop.equals("ancestor")) { 297 doAncestorChanged(e); 298 } 299 } 300 } 301 302 303 /** 304 * Responds to a File Name completion request (e.g. Tab) 305 */ 306 private class FileNameCompletionAction extends AbstractAction { 307 protected FileNameCompletionAction() { 308 super("fileNameCompletion"); 309 } 310 311 public void actionPerformed(ActionEvent e) { 312 JFileChooser chooser = getFileChooser(); 313 314 String fileName = getFileName(); 315 316 if (fileName != null) { 317 // Remove whitespace from beginning and end of filename 318 fileName = fileName.trim(); 319 } 320 321 resetGlobFilter(); 322 323 if (fileName == null || fileName.equals("") || 324 (chooser.isMultiSelectionEnabled() && fileName.startsWith("\""))) { 325 return; 326 } 327 328 FileFilter currentFilter = chooser.getFileFilter(); 329 if (globFilter == null) { 330 globFilter = new GlobFilter(); 331 } 332 try { 333 globFilter.setPattern(!isGlobPattern(fileName) ? fileName + "*" : fileName); 334 if (!(currentFilter instanceof GlobFilter)) { 335 actualFileFilter = currentFilter; 336 } 337 chooser.setFileFilter(null); 338 chooser.setFileFilter(globFilter); 339 fileNameCompletionString = fileName; 340 } catch (PatternSyntaxException pse) { 341 // Not a valid glob pattern. Abandon filter. 342 } 343 } 344 } 345 346 private String fileNameCompletionString; 347 348 private void updateFileNameCompletion() { 349 if (fileNameCompletionString != null) { 350 if (fileNameCompletionString.equals(getFileName())) { 351 File[] files = getModel().getFiles().toArray(new File[0]); 352 String str = getCommonStartString(files); 353 if (str != null && str.startsWith(fileNameCompletionString)) { 354 setFileName(str); 355 } 356 fileNameCompletionString = null; 357 } 358 } 359 } 360 361 private String getCommonStartString(File[] files) { 362 String str = null; 363 String str2 = null; 364 int i = 0; 365 if (files.length == 0) { 366 return null; 367 } 368 while (true) { 369 for (int f = 0; f < files.length; f++) { 370 String name = files[f].getName(); 371 if (f == 0) { 372 if (name.length() == i) { 373 return str; 374 } 375 str2 = name.substring(0, i+1); 376 } 377 if (!name.startsWith(str2)) { 378 return str; 379 } 380 } 381 str = str2; 382 i++; 383 } 384 } 385 386 private void resetGlobFilter() { 387 if (actualFileFilter != null) { 388 JFileChooser chooser = getFileChooser(); 389 FileFilter currentFilter = chooser.getFileFilter(); 390 if (currentFilter != null && currentFilter.equals(globFilter)) { 391 chooser.setFileFilter(actualFileFilter); 392 chooser.removeChoosableFileFilter(globFilter); 393 } 394 actualFileFilter = null; 395 } 396 } 397 398 private static boolean isGlobPattern(String fileName) { 399 return ((File.separatorChar == '\\' && fileName.indexOf('*') >= 0) 400 || (File.separatorChar == '/' && (fileName.indexOf('*') >= 0 401 || fileName.indexOf('?') >= 0 402 || fileName.indexOf('[') >= 0))); 403 } 404 405 406 /* A file filter which accepts file patterns containing 407 * the special wildcard '*' on windows, plus '?', and '[ ]' on Unix. 408 */ 409 class GlobFilter extends FileFilter { 410 Pattern pattern; 411 String globPattern; 412 413 public void setPattern(String globPattern) { 414 char[] gPat = globPattern.toCharArray(); 415 char[] rPat = new char[gPat.length * 2]; 416 boolean isWin32 = (File.separatorChar == '\\'); 417 boolean inBrackets = false; 418 int j = 0; 419 420 this.globPattern = globPattern; 421 422 if (isWin32) { 423 // On windows, a pattern ending with *.* is equal to ending with * 424 int len = gPat.length; 425 if (globPattern.endsWith("*.*")) { 426 len -= 2; 427 } 428 for (int i = 0; i < len; i++) { 429 if (gPat[i] == '*') { 430 rPat[j++] = '.'; 431 } 432 rPat[j++] = gPat[i]; 433 } 434 } else { 435 for (int i = 0; i < gPat.length; i++) { 436 switch(gPat[i]) { 437 case '*': 438 if (!inBrackets) { 439 rPat[j++] = '.'; 440 } 441 rPat[j++] = '*'; 442 break; 443 444 case '?': 445 rPat[j++] = inBrackets ? '?' : '.'; 446 break; 447 448 case '[': 449 inBrackets = true; 450 rPat[j++] = gPat[i]; 451 452 if (i < gPat.length - 1) { 453 switch (gPat[i+1]) { 454 case '!': 455 case '^': 456 rPat[j++] = '^'; 457 i++; 458 break; 459 460 case ']': 461 rPat[j++] = gPat[++i]; 462 break; 463 } 464 } 465 break; 466 467 case ']': 468 rPat[j++] = gPat[i]; 469 inBrackets = false; 470 break; 471 472 case '\\': 473 if (i == 0 && gPat.length > 1 && gPat[1] == '~') { 474 rPat[j++] = gPat[++i]; 475 } else { 476 rPat[j++] = '\\'; 477 if (i < gPat.length - 1 && "*?[]".indexOf(gPat[i+1]) >= 0) { 478 rPat[j++] = gPat[++i]; 479 } else { 480 rPat[j++] = '\\'; 481 } 482 } 483 break; 484 485 default: 486 //if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) { 487 if (!Character.isLetterOrDigit(gPat[i])) { 488 rPat[j++] = '\\'; 489 } 490 rPat[j++] = gPat[i]; 491 break; 492 } 493 } 494 } 495 this.pattern = Pattern.compile(new String(rPat, 0, j), Pattern.CASE_INSENSITIVE); 496 } 497 498 public boolean accept(File f) { 499 if (f == null) { 500 return false; 501 } 502 if (f.isDirectory()) { 503 return true; 504 } 505 return pattern.matcher(f.getName()).matches(); 506 } 507 508 public String getDescription() { 509 return globPattern; 510 } 511 } 512 513 514 // ******************************************************* 515 // ************ FileChooser UI PLAF methods ************** 516 // ******************************************************* 517 518 519 // ***************************** 520 // ***** Directory Actions ***** 521 // ***************************** 522 523 public Action getFileNameCompletionAction() { 524 return fileNameCompletionAction; 525 } 526 527 528 protected JButton getApproveButton(JFileChooser fc) { 529 return approveButton; 530 } 531 532 protected JButton getCancelButton(JFileChooser fc) { 533 return cancelButton; 534 } 535 536 537 // Overload to do nothing. We don't have and icon cache. 538 public void clearIconCache() { } 539 540 // Copied as SynthBorder is package private in synth 541 private class UIBorder extends AbstractBorder implements UIResource { 542 private Insets _insets; 543 UIBorder(Insets insets) { 544 if (insets != null) { 545 _insets = new Insets(insets.top, insets.left, insets.bottom, 546 insets.right); 547 } 548 else { 549 _insets = null; 550 } 551 } 552 553 public void paintBorder(Component c, Graphics g, int x, int y, 554 int width, int height) { 555 if (!(c instanceof JComponent)) { 556 return; 557 } 558 JComponent jc = (JComponent)c; 559 SynthContext context = getContext(jc); 560 SynthStyle style = context.getStyle(); 561 if (style != null) { 562 style.getPainter(context).paintFileChooserBorder( 563 context, g, x, y, width, height); 564 } 565 } 566 567 public Insets getBorderInsets(Component c, Insets insets) { 568 if (insets == null) { 569 insets = new Insets(0, 0, 0, 0); 570 } 571 if (_insets != null) { 572 insets.top = _insets.top; 573 insets.bottom = _insets.bottom; 574 insets.left = _insets.left; 575 insets.right = _insets.right; 576 } 577 else { 578 insets.top = insets.bottom = insets.right = insets.left = 0; 579 } 580 return insets; 581 } 582 public boolean isBorderOpaque() { 583 return false; 584 } 585 } 586 }