1 /*
   2  * Copyright 2010 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 package sun.awt.X11;
  26 
  27 import java.awt.*;
  28 import javax.swing.*;
  29 import java.awt.event.*;
  30 import java.awt.peer.*;
  31 import java.io.*;
  32 import java.util.Locale;
  33 import sun.util.logging.PlatformLogger;
  34 import sun.awt.AWTAccessor;
  35 
  36 /**
  37  * DirectoryDialogPeer for X-Windows.
  38  * 
  39  * @author Costantino Cerbo (c.cerbo@gmail.com)
  40  */
  41 class XDirectoryDialogPeer extends XDialogPeer implements DirectoryDialogPeer,
  42         ActionListener, ItemListener, KeyEventDispatcher, XChoicePeerListener {
  43 
  44     private static final PlatformLogger log = PlatformLogger
  45             .getLogger("sun.awt.X11.XFileDialogPeer");
  46     private static final int PATH_CHOICE_WIDTH = 20;
  47 
  48     private DirectoryDialog directoryDialog;
  49 
  50     // ************** Components in the fileDialogWindow ***************
  51     private List directoryList;
  52     private Choice pathChoice;
  53     private TextField pathField;
  54     private String dir;
  55 
  56     XDirectoryDialogPeer(DirectoryDialog directoryDialog) {
  57         super((Dialog) directoryDialog);
  58         this.directoryDialog = directoryDialog;
  59         if (directoryDialog.getDirectory() == null) {
  60             AWTAccessor.getDirectoryDialogAccessor().setDirectory(
  61                     directoryDialog, System.getProperty("user.dir"));
  62         }
  63         this.dir = directoryDialog.getDirectory();
  64 
  65         Locale l = directoryDialog.getLocale();
  66         UIDefaults uid = XToolkit.getUIDefaults();
  67 
  68         GridBagLayout gbl = new GridBagLayout();
  69         GridBagLayout gblButtons = new GridBagLayout();
  70         GridBagConstraints gbc = new GridBagConstraints();
  71         directoryDialog.setLayout(gbl);
  72         directoryDialog.setMinimumSize(new Dimension(400, 400));
  73 
  74         // create components
  75         Panel buttons = new Panel();
  76         buttons.setLayout(gblButtons);
  77         Button openButton = new Button(uid.getString(
  78                 "FileChooser.openButtonText", l));
  79         openButton.setActionCommand("openButton");
  80 
  81         Button cancelButton = new Button(uid.getString(
  82                 "FileChooser.cancelButtonText", l));
  83         cancelButton.setActionCommand("cancelButton");
  84 
  85         directoryList = new List();
  86 
  87         // the insets used by the components in the dialog
  88         Insets noInset = new Insets(0, 0, 0, 0);
  89         Insets textFieldInset = new Insets(0, 8, 0, 8);
  90         Insets leftListInset = new Insets(0, 8, 0, 4);
  91         Insets separatorInset = new Insets(8, 0, 0, 0);
  92         Insets labelInset = new Insets(0, 8, 0, 0);
  93         Insets buttonsInset = new Insets(10, 8, 10, 8);
  94 
  95         // add components to GridBagLayout "gbl"
  96 
  97         Font f = new Font(Font.DIALOG, Font.PLAIN, 12);
  98 
  99         Label label = new Label(uid.getString("FileChooser.pathLabelText", l));
 100         label.setFont(f);
 101         addComponent(label, gbl, gbc, 0, 0, 1, GridBagConstraints.WEST,
 102                 (Container) directoryDialog, 1, 0, GridBagConstraints.NONE,
 103                 labelInset);
 104 
 105         pathField = new TextField(dir);
 106         listFolders(dir);
 107 
 108         pathChoice = new Choice() {
 109 
 110             @Override
 111             public Dimension getPreferredSize() {
 112                 return new Dimension(PATH_CHOICE_WIDTH, pathField
 113                         .getPreferredSize().height);
 114             }
 115         };
 116         Panel pathPanel = new Panel();
 117         pathPanel.setLayout(new BorderLayout());
 118 
 119         pathPanel.add(pathField, BorderLayout.CENTER);
 120         pathPanel.add(pathChoice, BorderLayout.EAST);
 121         addComponent(pathPanel, gbl, gbc, 0, 1, 2, GridBagConstraints.WEST,
 122                 (Container) directoryDialog, 1, 0,
 123                 GridBagConstraints.HORIZONTAL, textFieldInset);
 124 
 125         label = new Label(uid.getString("FileChooser.foldersLabelText", l));
 126 
 127         label.setFont(f);
 128         addComponent(label, gbl, gbc, 0, 4, 1, GridBagConstraints.WEST,
 129                 (Container) directoryDialog, 1, 0, GridBagConstraints.NONE,
 130                 labelInset);
 131         addComponent(directoryList, gbl, gbc, 0, 5, 1, GridBagConstraints.WEST,
 132                 (Container) directoryDialog, 1, 1, GridBagConstraints.BOTH,
 133                 leftListInset);
 134 
 135         // Separator
 136         addComponent(new Separator(directoryDialog.getSize().width, 2,
 137                 Separator.HORIZONTAL), gbl, gbc, 0, 8, 15,
 138                 GridBagConstraints.WEST, (Container) directoryDialog, 1, 0,
 139                 GridBagConstraints.HORIZONTAL, separatorInset);
 140 
 141         // add buttons to GridBagLayout Buttons
 142         addComponent(openButton, gblButtons, gbc, 0, 0, 1,
 143                 GridBagConstraints.WEST, (Container) buttons, 1, 0,
 144                 GridBagConstraints.NONE, noInset);
 145         addComponent(cancelButton, gblButtons, gbc, 2, 0, 1,
 146                 GridBagConstraints.EAST, (Container) buttons, 1, 0,
 147                 GridBagConstraints.NONE, noInset);
 148 
 149         // add ButtonPanel to the GridBagLayout of this class
 150         addComponent(buttons, gbl, gbc, 0, 9, 2, GridBagConstraints.WEST,
 151                 (Container) directoryDialog, 1, 0,
 152                 GridBagConstraints.HORIZONTAL, buttonsInset);
 153 
 154         directoryList.addActionListener(this);
 155         openButton.addActionListener(this);
 156         cancelButton.addActionListener(this);
 157         pathChoice.addItemListener(this);
 158         pathField.addActionListener(this);
 159 
 160         // b6227750 FileDialog is not disposed when clicking the 'close' (X)
 161         // button on the top right corner, XToolkit
 162         directoryDialog.addWindowListener(new WindowAdapter() {
 163 
 164             @Override
 165             public void windowClosing(WindowEvent e) {
 166                 handleCancel();
 167             }
 168         });
 169     }
 170 
 171     @Override
 172     public void updateIconImages() {
 173         if (winAttr.icons == null) {
 174             winAttr.iconsInherited = false;
 175             winAttr.icons = getDefaultIconInfo();
 176             setIconHints(winAttr.icons);
 177         }
 178     }
 179 
 180     /**
 181      * add Component comp to the container cont. add the component to the
 182      * correct GridBagLayout
 183      */
 184     private void addComponent(Component comp, GridBagLayout gb,
 185             GridBagConstraints c, int gridx, int gridy, int gridwidth,
 186             int anchor, Container cont, int weightx, int weighty, int fill,
 187             Insets in) {
 188         c.gridx = gridx;
 189         c.gridy = gridy;
 190         c.gridwidth = gridwidth;
 191         c.anchor = anchor;
 192         c.weightx = weightx;
 193         c.weighty = weighty;
 194         c.fill = fill;
 195         c.insets = in;
 196         gb.setConstraints(comp, c);
 197         cont.add(comp);
 198     }
 199 
 200     /**
 201      * handle the selection event
 202      */
 203     private void handleSelection() {
 204         String newDir = pathField.getText() + File.separator;
 205         if (directoryList.getSelectedItem() != null) {
 206             if (!newDir.endsWith(File.separator)) {
 207                 newDir += File.separator;
 208             }
 209             newDir += directoryList.getSelectedItem();
 210         }
 211 
 212         if (newDir.equals(dir)) {
 213             return;
 214         }
 215 
 216         listFolders(newDir);
 217         setDirectory(newDir);
 218     }
 219 
 220     @Override
 221     public void setDirectory(String dir) {
 222         if (dir == null) {
 223             this.dir = null;
 224             return;
 225         }
 226 
 227         if (dir.equals(this.dir)) {
 228             return;
 229         }
 230 
 231         int i;
 232         if ((i = dir.indexOf("~")) != -1) {
 233             dir = dir.substring(0, i) + System.getProperty("user.home")
 234                     + dir.substring(i + 1, dir.length());
 235         }
 236 
 237         File fe = new File(dir).getAbsoluteFile();
 238         log.fine("Current directory : " + fe);
 239 
 240         if (!fe.isDirectory()) {
 241             dir = "./";
 242             fe = new File(dir).getAbsoluteFile();
 243 
 244             if (!fe.isDirectory()) {
 245                 return;
 246             }
 247         }
 248         try {
 249             dir = this.dir = fe.getCanonicalPath();
 250         } catch (java.io.IOException ie) {
 251             dir = this.dir = fe.getAbsolutePath();
 252         }
 253         pathField.setText(this.dir);
 254 
 255         if (dir.endsWith("/")) {
 256             this.dir = dir;
 257         } else {
 258             this.dir = dir + "/";
 259         }
 260 
 261         AWTAccessor.getDirectoryDialogAccessor().setDirectory(directoryDialog,
 262                 this.dir);
 263     }
 264 
 265     private void listFolders(String folder) {
 266         File[] files = new File(folder).listFiles();
 267         java.util.Arrays.sort(files);
 268         directoryList.clear();
 269         directoryList.addItem("..");
 270         for (File file : files) {
 271             if (file.isDirectory()) {
 272                 directoryList.addItem(file.getName() + "/");
 273             }
 274         }
 275     }
 276 
 277     /**
 278      * handle the cancel event
 279      */
 280     private void handleCancel() {
 281         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 282                 .removeKeyEventDispatcher(this);
 283 
 284         directoryList.clear();
 285 
 286         AWTAccessor.getDirectoryDialogAccessor().setDirectory(directoryDialog,
 287                 null);
 288 
 289         handleQuitButton();
 290     }
 291 
 292     /**
 293      * handle the quit event
 294      */
 295     private void handleQuitButton() {
 296         dir = null;
 297         directoryDialog.setVisible(false);
 298     }
 299 
 300     private String[] getDirList(String dir) {
 301         if (!dir.endsWith("/")) {
 302             dir = dir + "/";
 303         }
 304         char[] charr = dir.toCharArray();
 305         int numSlashes = 0;
 306         for (int i = 0; i < charr.length; i++) {
 307             if (charr[i] == '/') {
 308                 numSlashes++;
 309             }
 310         }
 311         String[] starr = new String[numSlashes];
 312         int j = 0;
 313         for (int i = charr.length - 1; i >= 0; i--) {
 314             if (charr[i] == '/') {
 315                 starr[j++] = new String(charr, 0, i + 1);
 316             }
 317         }
 318         return starr;
 319     }
 320 
 321     /**
 322      * 
 323      * @see java.awt.event.ItemEvent ItemEvent.ITEM_STATE_CHANGED
 324      */
 325     public void itemStateChanged(ItemEvent itemEvent) {
 326         if (itemEvent.getID() != ItemEvent.ITEM_STATE_CHANGED
 327                 || itemEvent.getStateChange() == ItemEvent.DESELECTED) {
 328             return;
 329         }
 330 
 331         Object source = itemEvent.getSource();
 332         if (source == pathChoice) {
 333             /*
 334              * Update the selection ('folder name' text field) after the current
 335              * item changing in the unfurled choice by the arrow keys. See
 336              * 6259434, 6240074 for more information
 337              */
 338 
 339             String selectedDir = pathChoice.getSelectedItem();
 340             pathField.setText(selectedDir);
 341             handleSelection();
 342         }
 343     }
 344 
 345     public void actionPerformed(ActionEvent actionEvent) {
 346         String actionCommand = actionEvent.getActionCommand();
 347         Object source = actionEvent.getSource();
 348 
 349         if (actionCommand.equals("openButton")) {
 350             handleQuitButton();
 351         } else if (actionCommand.equals("cancelButton")) {
 352             handleCancel();
 353         } else if (source == pathField || source == directoryList) {
 354             handleSelection();
 355         }
 356     }
 357 
 358     public boolean dispatchKeyEvent(KeyEvent keyEvent) {
 359         int id = keyEvent.getID();
 360         int keyCode = keyEvent.getKeyCode();
 361 
 362         if (id == KeyEvent.KEY_PRESSED && keyCode == KeyEvent.VK_ESCAPE) {
 363             synchronized (directoryDialog.getTreeLock()) {
 364                 Component comp = (Component) keyEvent.getSource();
 365                 while (comp != null) {
 366                     // Fix for 6240084 Disposing a file dialog when the
 367                     // drop-down is active does not dispose the dropdown menu,
 368                     // on Xtoolkit
 369                     // See also 6259493
 370                     if (comp == pathChoice) {
 371                         XChoicePeer choicePeer = (XChoicePeer) pathChoice
 372                                 .getPeer();
 373                         if (choicePeer.isUnfurled()) {
 374                             return false;
 375                         }
 376                     }
 377                     if (comp.getPeer() == this) {
 378                         handleCancel();
 379                         return true;
 380                     }
 381                     comp = comp.getParent();
 382                 }
 383             }
 384         }
 385 
 386         return false;
 387     }
 388 
 389     @Override
 390     public void dispose() {
 391         if (directoryDialog != null) {
 392             directoryDialog.removeAll();
 393         }
 394         if (target != null) {
 395             ((Dialog) target).removeAll();
 396         }
 397         super.dispose();
 398     }
 399 
 400     // 03/02/2005 b5097243 Pressing 'ESC' on a file dlg does not dispose the dlg
 401     // on Xtoolkit
 402     @Override
 403     public void setVisible(boolean b) {
 404         super.setVisible(b);
 405         if (b == true) {
 406             // See 6240074 for more information
 407             XChoicePeer choicePeer = (XChoicePeer) pathChoice.getPeer();
 408             choicePeer.setDrawSelectedItem(false);
 409             choicePeer.setAlignUnder(pathField);
 410             choicePeer.addXChoicePeerListener(this);
 411             KeyboardFocusManager.getCurrentKeyboardFocusManager()
 412                     .addKeyEventDispatcher(this);
 413         } else {
 414             // See 6240074 for more information
 415             XChoicePeer choicePeer = (XChoicePeer) pathChoice.getPeer();
 416             choicePeer.removeXChoicePeerListener();
 417             KeyboardFocusManager.getCurrentKeyboardFocusManager()
 418                     .removeKeyEventDispatcher(this);
 419         }
 420     }
 421 
 422     /*
 423      * Adding items to the path choice based on the text string See 6240074 for
 424      * more information
 425      */
 426     public void addItemsToPathChoice(String text) {
 427         String dirList[] = getDirList(text);
 428         for (int i = 0; i < dirList.length; i++) {
 429             pathChoice.addItem(dirList[i]);
 430         }
 431     }
 432 
 433     /*
 434      * Refresh the unfurled choice at the time of the opening choice according
 435      * to the text of the path field See 6240074 for more information
 436      */
 437     public void unfurledChoiceOpening(ListHelper choiceHelper) {
 438 
 439         // When the unfurled choice is opening the first time, we need only to
 440         // add elements, otherwise we've got exception
 441         if (choiceHelper.getItemCount() == 0) {
 442             addItemsToPathChoice(pathField.getText());
 443             return;
 444         }
 445 
 446         // If the set of the directories the exactly same as the used to be then
 447         // dummy
 448         if (pathChoice.getItem(0).equals(pathField.getText())) {
 449             return;
 450         }
 451 
 452         pathChoice.removeAll();
 453         addItemsToPathChoice(pathField.getText());
 454     }
 455 
 456     /*
 457      * Refresh the file dialog at the time of the closing choice according to
 458      * the selected item of the choice See 6240074 for more information
 459      */
 460     public void unfurledChoiceClosing() {
 461         // This is the exactly same code as invoking later at the time of the
 462         // itemStateChanged
 463         // Here is we restore Windows behaviour: change current directory if
 464         // user press 'ESC'
 465         String selectedDir = pathChoice.getSelectedItem();
 466         directoryDialog.setDirectory(selectedDir);
 467     }
 468 }