1 /* 2 * Copyright (c) 2002, 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 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 @SuppressWarnings("serial") // JDK-implementation class 307 private class FileNameCompletionAction extends AbstractAction { 308 protected FileNameCompletionAction() { 309 super("fileNameCompletion"); 310 } 311 312 public void actionPerformed(ActionEvent e) { 313 JFileChooser chooser = getFileChooser(); 314 315 String fileName = getFileName(); 316 317 if (fileName != null) { 318 // Remove whitespace from beginning and end of filename 319 fileName = fileName.trim(); 320 } 321 322 resetGlobFilter(); 323 324 if (fileName == null || fileName.equals("") || 325 (chooser.isMultiSelectionEnabled() && fileName.startsWith("\""))) { 326 return; 327 } 328 329 FileFilter currentFilter = chooser.getFileFilter(); 330 if (globFilter == null) { 331 globFilter = new GlobFilter(); 332 } 333 try { 334 globFilter.setPattern(!isGlobPattern(fileName) ? fileName + "*" : fileName); 335 if (!(currentFilter instanceof GlobFilter)) { 336 actualFileFilter = currentFilter; 337 } 338 chooser.setFileFilter(null); 339 chooser.setFileFilter(globFilter); 340 fileNameCompletionString = fileName; 341 } catch (PatternSyntaxException pse) { 342 // Not a valid glob pattern. Abandon filter. 343 } 344 } 345 } 346 347 private String fileNameCompletionString; 348 349 private void updateFileNameCompletion() { 350 if (fileNameCompletionString != null) { 351 if (fileNameCompletionString.equals(getFileName())) { 352 File[] files = getModel().getFiles().toArray(new File[0]); 353 String str = getCommonStartString(files); 354 if (str != null && str.startsWith(fileNameCompletionString)) { 355 setFileName(str); 356 } 357 fileNameCompletionString = null; 358 } 359 } 360 } 361 362 private String getCommonStartString(File[] files) { 363 String str = null; 364 String str2 = null; 365 int i = 0; 366 if (files.length == 0) { 367 return null; 368 } 369 while (true) { 370 for (int f = 0; f < files.length; f++) { 371 String name = files[f].getName(); 372 if (f == 0) { 373 if (name.length() == i) { 374 return str; 375 } 376 str2 = name.substring(0, i+1); 377 } 378 if (!name.startsWith(str2)) { 379 return str; 380 } 381 } 382 str = str2; 383 i++; 384 } 385 } 386 387 private void resetGlobFilter() { 388 if (actualFileFilter != null) { 389 JFileChooser chooser = getFileChooser(); 390 FileFilter currentFilter = chooser.getFileFilter(); 391 if (currentFilter != null && currentFilter.equals(globFilter)) { 392 chooser.setFileFilter(actualFileFilter); 393 chooser.removeChoosableFileFilter(globFilter); 394 } 395 actualFileFilter = null; 396 } 397 } 398 399 private static boolean isGlobPattern(String fileName) { 400 return ((File.separatorChar == '\\' && fileName.indexOf('*') >= 0) 401 || (File.separatorChar == '/' && (fileName.indexOf('*') >= 0 402 || fileName.indexOf('?') >= 0 403 || fileName.indexOf('[') >= 0))); 404 } 405 406 407 /* A file filter which accepts file patterns containing 408 * the special wildcard '*' on windows, plus '?', and '[ ]' on Unix. 409 */ 410 class GlobFilter extends FileFilter { 411 Pattern pattern; 412 String globPattern; 413 414 public void setPattern(String globPattern) { 415 char[] gPat = globPattern.toCharArray(); 416 char[] rPat = new char[gPat.length * 2]; 417 boolean isWin32 = (File.separatorChar == '\\'); 418 boolean inBrackets = false; 419 int j = 0; 420 421 this.globPattern = globPattern; 422 423 if (isWin32) { 424 // On windows, a pattern ending with *.* is equal to ending with * 425 int len = gPat.length; 426 if (globPattern.endsWith("*.*")) { 427 len -= 2; 428 } 429 for (int i = 0; i < len; i++) { 430 if (gPat[i] == '*') { 431 rPat[j++] = '.'; 432 } 433 rPat[j++] = gPat[i]; 434 } 435 } else { 436 for (int i = 0; i < gPat.length; i++) { 437 switch(gPat[i]) { 438 case '*': 439 if (!inBrackets) { 440 rPat[j++] = '.'; 441 } 442 rPat[j++] = '*'; 443 break; 444 445 case '?': 446 rPat[j++] = inBrackets ? '?' : '.'; 447 break; 448 449 case '[': 450 inBrackets = true; 451 rPat[j++] = gPat[i]; 452 453 if (i < gPat.length - 1) { 454 switch (gPat[i+1]) { 455 case '!': 456 case '^': 457 rPat[j++] = '^'; 458 i++; 459 break; 460 461 case ']': 462 rPat[j++] = gPat[++i]; 463 break; 464 } 465 } 466 break; 467 468 case ']': 469 rPat[j++] = gPat[i]; 470 inBrackets = false; 471 break; 472 473 case '\\': 474 if (i == 0 && gPat.length > 1 && gPat[1] == '~') { 475 rPat[j++] = gPat[++i]; 476 } else { 477 rPat[j++] = '\\'; 478 if (i < gPat.length - 1 && "*?[]".indexOf(gPat[i+1]) >= 0) { 479 rPat[j++] = gPat[++i]; 480 } else { 481 rPat[j++] = '\\'; 482 } 483 } 484 break; 485 486 default: 487 //if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) { 488 if (!Character.isLetterOrDigit(gPat[i])) { 489 rPat[j++] = '\\'; 490 } 491 rPat[j++] = gPat[i]; 492 break; 493 } 494 } 495 } 496 this.pattern = Pattern.compile(new String(rPat, 0, j), Pattern.CASE_INSENSITIVE); 497 } 498 499 public boolean accept(File f) { 500 if (f == null) { 501 return false; 502 } 503 if (f.isDirectory()) { 504 return true; 505 } 506 return pattern.matcher(f.getName()).matches(); 507 } 508 509 public String getDescription() { 510 return globPattern; 511 } 512 } 513 514 515 // ******************************************************* 516 // ************ FileChooser UI PLAF methods ************** 517 // ******************************************************* 518 519 520 // ***************************** 521 // ***** Directory Actions ***** 522 // ***************************** 523 524 public Action getFileNameCompletionAction() { 525 return fileNameCompletionAction; 526 } 527 528 529 protected JButton getApproveButton(JFileChooser fc) { 530 return approveButton; 531 } 532 533 protected JButton getCancelButton(JFileChooser fc) { 534 return cancelButton; 535 } 536 537 538 // Overload to do nothing. We don't have and icon cache. 539 public void clearIconCache() { } 540 541 // Copied as SynthBorder is package private in synth 542 @SuppressWarnings("serial") // JDK-implementation clas 543 private class UIBorder extends AbstractBorder implements UIResource { 544 private Insets _insets; 545 UIBorder(Insets insets) { 546 if (insets != null) { 547 _insets = new Insets(insets.top, insets.left, insets.bottom, 548 insets.right); 549 } 550 else { 551 _insets = null; 552 } 553 } 554 555 public void paintBorder(Component c, Graphics g, int x, int y, 556 int width, int height) { 557 if (!(c instanceof JComponent)) { 558 return; 559 } 560 JComponent jc = (JComponent)c; 561 SynthContext context = getContext(jc); 562 SynthStyle style = context.getStyle(); 563 if (style != null) { 564 style.getPainter(context).paintFileChooserBorder( 565 context, g, x, y, width, height); 566 } 567 } 568 569 public Insets getBorderInsets(Component c, Insets insets) { 570 if (insets == null) { 571 insets = new Insets(0, 0, 0, 0); 572 } 573 if (_insets != null) { 574 insets.top = _insets.top; 575 insets.bottom = _insets.bottom; 576 insets.left = _insets.left; 577 insets.right = _insets.right; 578 } 579 else { 580 insets.top = insets.bottom = insets.right = insets.left = 0; 581 } 582 return insets; 583 } 584 public boolean isBorderOpaque() { 585 return false; 586 } 587 } 588 }