1 package com.sun.java.swing.plaf.windows;
   2 
   3 import java.awt.CardLayout;
   4 import java.awt.Color;
   5 import static java.awt.Color.WHITE;
   6 import java.awt.Component;
   7 import java.awt.Container;
   8 import java.awt.Dimension;
   9 import java.awt.Graphics;
  10 import java.awt.Image;
  11 import java.awt.Insets;
  12 import java.awt.LayoutManager;
  13 import java.awt.SystemColor;
  14 import java.awt.Toolkit;
  15 import java.awt.event.ActionEvent;
  16 import java.awt.event.ActionListener;
  17 import java.awt.event.AdjustmentEvent;
  18 import java.awt.event.AdjustmentListener;
  19 import java.awt.event.FocusAdapter;
  20 import java.awt.event.FocusEvent;
  21 import java.awt.event.FocusListener;
  22 import java.awt.event.KeyAdapter;
  23 import java.awt.event.KeyEvent;
  24 import static java.awt.event.KeyEvent.VK_ENTER;
  25 import static java.awt.event.KeyEvent.VK_ESCAPE;
  26 import java.awt.event.KeyListener;
  27 import java.awt.event.MouseAdapter;
  28 import java.awt.event.MouseEvent;
  29 import java.awt.event.MouseWheelEvent;
  30 import java.awt.event.MouseWheelListener;
  31 import java.awt.image.BufferedImage;
  32 import java.io.File;
  33 import java.io.UncheckedIOException;
  34 import static java.lang.Boolean.TRUE;
  35 import java.util.ArrayList;
  36 import java.util.List;
  37 import javax.swing.Icon;
  38 import javax.swing.ImageIcon;
  39 import javax.swing.JButton;
  40 import javax.swing.JFileChooser;
  41 import static javax.swing.JFileChooser.DIRECTORY_CHANGED_PROPERTY;
  42 import javax.swing.JLabel;
  43 import javax.swing.JMenuItem;
  44 import javax.swing.JOptionPane;
  45 import javax.swing.JPanel;
  46 import javax.swing.JPopupMenu;
  47 import javax.swing.JScrollBar;
  48 import javax.swing.JTextField;
  49 import javax.swing.JToolBar;
  50 import javax.swing.border.Border;
  51 import javax.swing.border.CompoundBorder;
  52 import javax.swing.border.EmptyBorder;
  53 import javax.swing.border.LineBorder;
  54 import javax.swing.border.MatteBorder;
  55 import javax.swing.filechooser.FileSystemView;
  56 import sun.awt.shell.ShellFolder;
  57 
  58 class WindowsAddressBar extends JPanel {
  59 
  60     private final JFileChooser fileChooser;
  61     private final CardLayout cardLayout = new CardLayout();
  62     private final PathEditorField textField = new PathEditorField();
  63     private final WindowsPathField pathField;
  64     private final Border originalTextFieldBorder = textField.getBorder();
  65 
  66     public WindowsAddressBar(JFileChooser fileChooser) {
  67         this.fileChooser = fileChooser;
  68         this.pathField = new WindowsPathField(fileChooser, this);
  69 
  70         setLayout(cardLayout);
  71         add(textField, "text");
  72         add(pathField, "path");
  73         cardLayout.show(this, "path");
  74 
  75         if (Toolkit.getDefaultToolkit().
  76                 getDesktopProperty("win.xpstyle.themeActive") != TRUE) {
  77             textField.setBackground(SystemColor.control);
  78             textField.setOpaque(true);
  79 
  80             pathField.setBackground(SystemColor.control);
  81             pathField.setOpaque(true);
  82         }
  83     }
  84 
  85     private void showTextField() {
  86         textField.refresh();
  87         cardLayout.show(this, "text");
  88         revalidate();
  89         textField.requestFocus();
  90         textField.selectAll();
  91     }
  92 
  93     private void showPathField() {
  94         cardLayout.show(this, "path");
  95         revalidate();
  96     }
  97 
  98     private class PathEditorField extends JTextField implements FocusListener, KeyListener {
  99 
 100         public PathEditorField() {
 101         }
 102 
 103         public void refresh() {
 104             textField.setText(fileChooser.getCurrentDirectory().getPath());
 105             
 106             Icon icon = pathField.getCurrentIcon();
 107             setBorder(new CompoundBorder(
 108                     originalTextFieldBorder, new CompoundBorder(
 109                             new EmptyBorder(1, 2, 0, 0), new CompoundBorder(
 110                                     new MatteBorder(0, icon.getIconWidth(), 0, 0, icon),
 111                                     new EmptyBorder(0, 9, 0, 0)
 112                             )
 113                     )
 114             ));
 115         }
 116 
 117         @Override
 118         public void keyPressed(KeyEvent e) {
 119             switch (e.getKeyCode()) {
 120                 case VK_ENTER:
 121                     String path = textField.getText();
 122                     File file;
 123                     try {
 124                         file = (File) ShellFolder.get("parseDisplayName " + path);
 125                     } catch (UncheckedIOException ex) {
 126                         cantParseDisplayName(path);
 127                         break;
 128                     }
 129                     fileChooser.setCurrentDirectory(file);
 130                 case VK_ESCAPE:
 131                     e.consume();
 132                     showPathField();
 133                     break;
 134             }
 135         }
 136 
 137         private void cantParseDisplayName(String path) {
 138             ((Runnable) Toolkit.getDefaultToolkit().
 139                     getDesktopProperty("win.sound.exclamation")).run();
 140             JOptionPane.showMessageDialog(fileChooser, path + "\n"
 141                     + "The specified path does not exist. \n\n"
 142                     + "Check the path, and then try again. ",
 143                     path, JOptionPane.ERROR_MESSAGE);
 144             textField.selectAll();
 145             textField.requestFocusInWindow();
 146         }
 147 
 148         @Override
 149         public void focusLost(FocusEvent e) {
 150             showPathField();
 151         }
 152 
 153         @Override
 154         public void focusGained(FocusEvent e) {
 155         }
 156 
 157         @Override
 158         public void keyTyped(KeyEvent e) {
 159             throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
 160         }
 161 
 162         @Override
 163         public void keyReleased(KeyEvent e) {
 164             throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
 165         }
 166 
 167     }
 168 
 169     private class WindowsPathField extends JToolBar {
 170 
 171         private final JFileChooser fileChooser;
 172         private final FileSystemView view;
 173         private RootElementLabel rootElementLabel;
 174 
 175         public WindowsPathField(JFileChooser fileChooser, WindowsAddressBar addressBar) {
 176             this.fileChooser = fileChooser;
 177             this.view = fileChooser.getFileSystemView();
 178 
 179             fileChooser.addPropertyChangeListener(
 180                     DIRECTORY_CHANGED_PROPERTY, (p) -> refresh());
 181             setFloatable(false);
 182             setBackground(WHITE);
 183             setBorder(new CompoundBorder(
 184                     new LineBorder(new Color(217, 217, 217)),
 185                     new EmptyBorder(0, 4, 0, 2)
 186             ));
 187             putClientProperty("XPStyle.subAppName", "Header");
 188             putClientProperty("WindowsButtonUI.animateStateChange", TRUE);
 189 
 190             addMouseListener(new MouseAdapter() {
 191                 @Override
 192                 public void mouseReleased(MouseEvent e) {
 193                     addressBar.showTextField();
 194                 }
 195 
 196             });
 197         }
 198 
 199         public Icon getCurrentIcon() {
 200             return rootElementLabel.getIcon();
 201         }
 202 
 203         private void refresh() {
 204             while (getComponentCount() > 0) {
 205                 remove(0);
 206             }
 207 
 208             File dir = fileChooser.getCurrentDirectory();
 209             List<File> path = new ArrayList<>();
 210             do {
 211                 path.add(0, dir);
 212             } while ((dir = dir.getParentFile()) != null);
 213 
 214             add(rootElementLabel = new RootElementLabel(path));
 215             add(new RightArrowButton(path.get(0)));
 216             for (File pathElement : path.subList(1, path.size())) {
 217                 add(new PathElementButton(pathElement));
 218                 add(new RightArrowButton(pathElement));
 219             }
 220 
 221             revalidate();
 222             repaint();
 223 
 224             System.out.println("refreshed");
 225         }
 226 
 227         private final class RootElementLabel extends JLabel {
 228 
 229             private RootElementLabel(List<File> path) {
 230                 File folder = path.get(path.size() - 1);
 231                 if (path.size() == 1) {
 232                     setText(view.getSystemDisplayName(folder));
 233                 }
 234                 setIcon(view.getSystemIcon(folder));
 235                 setBackground(WHITE);
 236             }
 237         }
 238 
 239         private final class PathElementButton extends JButton implements ActionListener {
 240 
 241             private final File folder;
 242 
 243             private PathElementButton(File dir) {
 244                 this.folder = dir;
 245 
 246                 setText(view.getSystemDisplayName(dir));
 247                 addActionListener(this);
 248                 setOpaque(false);
 249                 setBorder(new EmptyBorder(3, 5, 3, 5));
 250             }
 251 
 252             @Override
 253             public void actionPerformed(ActionEvent e) {
 254                 fileChooser.setCurrentDirectory(folder);
 255             }
 256         }
 257 
 258         private class RightArrowButton extends JButton implements ActionListener {
 259 
 260             private final File folder;
 261 
 262             public RightArrowButton(File folder) {
 263                 setIcon(new ImageIcon(initRightArrowImage()));
 264                 setBorder(new EmptyBorder(3, 5, 3, 5));
 265                 setOpaque(false);
 266                 addActionListener(this);
 267                 this.folder = folder;
 268             }
 269 
 270             @Override
 271             public void actionPerformed(ActionEvent e) {
 272                 JPopupMenu popupMenu = new ScrollablePopupMenu();
 273                 File[] files = view.getFiles(folder, false);
 274                 for (File file : files) {
 275                     if (!view.isTraversable(file)) {
 276                         continue;
 277                     }
 278 
 279                     JMenuItem menuItem = new JMenuItem();
 280                     menuItem.setText(view.getSystemDisplayName(file));
 281                     menuItem.addActionListener(evt -> {
 282                         fileChooser.setCurrentDirectory(file);
 283                     });
 284                     popupMenu.add(menuItem);
 285                 }
 286 
 287                 popupMenu.show(this, 0, getHeight());
 288             }
 289 
 290         }
 291 
 292     }
 293 
 294     private static class ScrollablePopupMenu extends JPopupMenu {
 295 
 296         private static final int MAXIMUM_VISIBLE_ROWS = 18;
 297 
 298         public ScrollablePopupMenu() {
 299             setLayout(new ScrollPopupMenuLayout());
 300 
 301             super.add(getScrollBar());
 302             addMouseWheelListener(new MouseWheelListener() {
 303                 @Override
 304                 public void mouseWheelMoved(MouseWheelEvent event) {
 305                     JScrollBar scrollBar = getScrollBar();
 306                     int amount = (event.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL)
 307                             ? event.getUnitsToScroll() * scrollBar.getUnitIncrement()
 308                             : (event.getWheelRotation() < 0 ? -1 : 1) * scrollBar.getBlockIncrement();
 309 
 310                     scrollBar.setValue(scrollBar.getValue() + amount);
 311                     event.consume();
 312                 }
 313             });
 314         }
 315 
 316         private JScrollBar popupScrollBar;
 317 
 318         private JScrollBar getScrollBar() {
 319             if (popupScrollBar == null) {
 320                 popupScrollBar = new JScrollBar(JScrollBar.VERTICAL);
 321                 popupScrollBar.addAdjustmentListener(new AdjustmentListener() {
 322                     @Override
 323                     public void adjustmentValueChanged(AdjustmentEvent e) {
 324                         doLayout();
 325                         repaint();
 326                     }
 327                 });
 328 
 329                 popupScrollBar.setVisible(false);
 330             }
 331 
 332             return popupScrollBar;
 333         }
 334 
 335         public void paintChildren(Graphics g) {
 336             Insets insets = getInsets();
 337             g.clipRect(insets.left, insets.top, getWidth(), getHeight() - insets.top - insets.bottom);
 338             super.paintChildren(g);
 339         }
 340 
 341         protected void addImpl(Component comp, Object constraints, int index) {
 342             super.addImpl(comp, constraints, index);
 343 
 344             if (MAXIMUM_VISIBLE_ROWS < getComponentCount() - 1) {
 345                 getScrollBar().setVisible(true);
 346             }
 347         }
 348 
 349         public void remove(int index) {
 350             // can't remove the scrollbar
 351             ++index;
 352 
 353             super.remove(index);
 354 
 355             if (MAXIMUM_VISIBLE_ROWS >= getComponentCount() - 1) {
 356                 getScrollBar().setVisible(false);
 357             }
 358         }
 359 
 360         public void show(Component invoker, int x, int y) {
 361             JScrollBar scrollBar = getScrollBar();
 362             if (scrollBar.isVisible()) {
 363                 int extent = 0;
 364                 int max = 0;
 365                 int i = 0;
 366                 int unit = -1;
 367                 int width = 0;
 368                 for (Component comp : getComponents()) {
 369                     if (!(comp instanceof JScrollBar)) {
 370                         Dimension preferredSize = comp.getPreferredSize();
 371                         width = Math.max(width, preferredSize.width);
 372                         if (unit < 0) {
 373                             unit = preferredSize.height;
 374                         }
 375                         if (i++ < MAXIMUM_VISIBLE_ROWS) {
 376                             extent += preferredSize.height;
 377                         }
 378                         max += preferredSize.height;
 379                     }
 380                 }
 381 
 382                 Insets insets = getInsets();
 383                 int widthMargin = insets.left + insets.right;
 384                 int heightMargin = insets.top + insets.bottom;
 385                 scrollBar.setUnitIncrement(unit);
 386                 scrollBar.setBlockIncrement(extent);
 387                 scrollBar.setValues(0, heightMargin + extent, 0, heightMargin + max);
 388 
 389                 width += scrollBar.getPreferredSize().width + widthMargin;
 390                 int height = heightMargin + extent;
 391 
 392                 setPopupSize(new Dimension(width, height));
 393             }
 394 
 395             super.show(invoker, x, y);
 396         }
 397 
 398         private static class ScrollPopupMenuLayout implements LayoutManager {
 399 
 400             @Override
 401             public void addLayoutComponent(String name, Component comp) {
 402             }
 403 
 404             @Override
 405             public void removeLayoutComponent(Component comp) {
 406             }
 407 
 408             @Override
 409             public Dimension preferredLayoutSize(Container parent) {
 410                 int visibleAmount = Integer.MAX_VALUE;
 411                 Dimension dim = new Dimension();
 412                 for (Component comp : parent.getComponents()) {
 413                     if (comp.isVisible()) {
 414                         if (comp instanceof JScrollBar) {
 415                             JScrollBar scrollBar = (JScrollBar) comp;
 416                             visibleAmount = scrollBar.getVisibleAmount();
 417                         } else {
 418                             Dimension pref = comp.getPreferredSize();
 419                             dim.width = Math.max(dim.width, pref.width);
 420                             dim.height += pref.height;
 421                         }
 422                     }
 423                 }
 424 
 425                 Insets insets = parent.getInsets();
 426                 dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount);
 427 
 428                 return dim;
 429             }
 430 
 431             @Override
 432             public Dimension minimumLayoutSize(Container parent) {
 433                 int visibleAmount = Integer.MAX_VALUE;
 434                 Dimension dim = new Dimension();
 435                 for (Component comp : parent.getComponents()) {
 436                     if (comp.isVisible()) {
 437                         if (comp instanceof JScrollBar) {
 438                             JScrollBar scrollBar = (JScrollBar) comp;
 439                             visibleAmount = scrollBar.getVisibleAmount();
 440                         } else {
 441                             Dimension min = comp.getMinimumSize();
 442                             dim.width = Math.max(dim.width, min.width);
 443                             dim.height += min.height;
 444                         }
 445                     }
 446                 }
 447 
 448                 Insets insets = parent.getInsets();
 449                 dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount);
 450 
 451                 return dim;
 452             }
 453 
 454             @Override
 455             public void layoutContainer(Container parent) {
 456                 Insets insets = parent.getInsets();
 457 
 458                 int width = parent.getWidth() - insets.left - insets.right;
 459                 int height = parent.getHeight() - insets.top - insets.bottom;
 460 
 461                 int x = insets.left;
 462                 int y = insets.top;
 463                 int position = 0;
 464 
 465                 for (Component comp : parent.getComponents()) {
 466                     if ((comp instanceof JScrollBar) && comp.isVisible()) {
 467                         JScrollBar scrollBar = (JScrollBar) comp;
 468                         Dimension dim = scrollBar.getPreferredSize();
 469                         scrollBar.setBounds(x + width - dim.width, y, dim.width, height);
 470                         width -= dim.width;
 471                         position = scrollBar.getValue();
 472                     }
 473                 }
 474 
 475                 y -= position;
 476                 for (Component comp : parent.getComponents()) {
 477                     if (!(comp instanceof JScrollBar) && comp.isVisible()) {
 478                         Dimension pref = comp.getPreferredSize();
 479                         comp.setBounds(x, y, width, pref.height);
 480                         y += pref.height;
 481                     }
 482                 }
 483             }
 484         }
 485     }
 486 
 487     private static Image initRightArrowImage() {
 488         BufferedImage img = new BufferedImage(5, 7, 6);
 489         img.setRGB(0, 0, 150994944);
 490         img.setRGB(0, 1, 1191182336);
 491         img.setRGB(0, 2, 50331648);
 492         img.setRGB(0, 3, 16777215);
 493         img.setRGB(0, 4, 50331648);
 494         img.setRGB(0, 5, 1191182336);
 495         img.setRGB(0, 6, 150994944);
 496         img.setRGB(1, 0, 1174405120);
 497         img.setRGB(1, 1, 2130706432);
 498         img.setRGB(1, 2, 1526726656);
 499         img.setRGB(1, 3, 100663296);
 500         img.setRGB(1, 4, 1526726656);
 501         img.setRGB(1, 5, 2130706432);
 502         img.setRGB(1, 6, 1174405120);
 503         img.setRGB(2, 0, 50331648);
 504         img.setRGB(2, 1, 1526726656);
 505         img.setRGB(2, 2, 2130706432);
 506         img.setRGB(2, 3, 1996488704);
 507         img.setRGB(2, 4, 2130706432);
 508         img.setRGB(2, 5, 1526726656);
 509         img.setRGB(2, 6, 50331648);
 510         img.setRGB(3, 1, 50331648);
 511         img.setRGB(3, 2, 1526726656);
 512         img.setRGB(3, 3, 2130706432);
 513         img.setRGB(3, 4, 1526726656);
 514         img.setRGB(3, 5, 50331648);
 515         img.setRGB(4, 2, 50331648);
 516         img.setRGB(4, 3, 1040187392);
 517         img.setRGB(4, 4, 50331648);
 518         return img;
 519     }
 520 
 521 }