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 }