1 /*
   2  * Copyright (c) 2002, 2019, 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     public abstract void setFileName(String fileName);
 232     public abstract 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.isEmpty() ||
 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 }