1 package com.sun.java.swing.plaf.windows;
   2 
   3 import com.sun.java.swing.plaf.windows.WindowsFileChooserUI.WindowsFileListUI;
   4 import java.awt.Color;
   5 import java.awt.Component;
   6 import java.awt.Dimension;
   7 import java.awt.Graphics;
   8 import java.awt.Insets;
   9 import java.awt.Rectangle;
  10 import static java.awt.Toolkit.getDefaultToolkit;
  11 import java.awt.event.MouseAdapter;
  12 import java.awt.event.MouseEvent;
  13 import java.beans.PropertyChangeEvent;
  14 import java.beans.PropertyChangeListener;
  15 import java.io.File;
  16 import java.io.FileNotFoundException;
  17 import java.io.UncheckedIOException;
  18 import java.util.Arrays;
  19 import java.util.Collections;
  20 import static java.util.Collections.emptyList;
  21 import java.util.Enumeration;
  22 import java.util.HashMap;
  23 import java.util.List;
  24 import java.util.Map;
  25 import static java.util.stream.Collectors.toList;
  26 import javax.swing.JComponent;
  27 import javax.swing.JFileChooser;
  28 import javax.swing.JTree;
  29 import javax.swing.filechooser.FileSystemView;
  30 import javax.swing.tree.DefaultTreeCellRenderer;
  31 import javax.swing.tree.DefaultTreeModel;
  32 import javax.swing.tree.TreeNode;
  33 import javax.swing.tree.TreePath;
  34 import sun.awt.shell.ShellFolder;
  35 import static sun.awt.shell.ShellFolder.getShellFolder;
  36 import static com.sun.java.swing.plaf.windows.WindowsFileChooserUI.WindowsFileListUI.ITEM_SELECTED_COLOR;
  37 import static com.sun.java.swing.plaf.windows.WindowsFileChooserUI.WindowsFileListUI.ITEM_HOVERED_COLOR;
  38 import java.awt.AlphaComposite;
  39 import java.awt.Composite;
  40 import java.awt.Cursor;
  41 import java.awt.Graphics2D;
  42 import java.awt.Image;
  43 import java.awt.Point;
  44 import java.awt.image.BufferedImage;
  45 import static java.lang.Boolean.TRUE;
  46 import static java.time.Duration.between;
  47 import java.time.Instant;
  48 import static java.time.Instant.now;
  49 import java.util.ArrayList;
  50 import java.util.function.Supplier;
  51 import javax.swing.Icon;
  52 import javax.swing.ImageIcon;
  53 import static javax.swing.JFileChooser.FILE_SYSTEM_VIEW_CHANGED_PROPERTY;
  54 import javax.swing.JScrollPane;
  55 import javax.swing.JSeparator;
  56 import javax.swing.JViewport;
  57 import javax.swing.Timer;
  58 import javax.swing.UIManager;
  59 import javax.swing.filechooser.FileSystemView.EssentialFolderSection;
  60 import javax.swing.tree.TreeSelectionModel;
  61 import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
  62 
  63 public class WindowsNavigationTree extends JTree implements PropertyChangeListener {
  64 
  65     private int hover = -1;
  66     private Point mousePoint;
  67 
  68     private final JFileChooser fileChooser;
  69     private final FileSystemView view;
  70 
  71     private static final Icon EXPAND_ICON = new ImageIcon(initRightArrowNormalImage());
  72     private static final Icon COLLAPSE_ICON = new ImageIcon(initDownArrowNormalImage());
  73 
  74     private static final Icon EXPAND_ICON_HOVER = new ImageIcon(initRightArrowHoverImage());
  75     private static final Icon COLLAPSE_ICON_HOVER = new ImageIcon(initDownArrowHoverImage());
  76 
  77     public WindowsNavigationTree(WindowsFileChooserUI fileChooserUI) {
  78         this.fileChooser = fileChooserUI.getFileChooser();
  79         this.view = fileChooser.getFileSystemView();
  80 
  81         super.setRootVisible(false);
  82         super.setUI(new NavTreeUI());
  83         super.addMouseListener(new MouseAdapter() {
  84 
  85             @Override
  86             public void mouseExited(MouseEvent e) {
  87                 hover = -1;
  88                 setCursor(Cursor.getDefaultCursor());
  89                 repaint();
  90             }
  91 
  92         });
  93         super.addMouseMotionListener(new MouseAdapter() {
  94             @Override
  95             public void mouseMoved(MouseEvent e) {
  96                 mousePoint = e.getPoint();
  97 
  98                 int row = getRowForLocation(e.getX(), e.getY());
  99                 TreePath path = getPathForRow(row);
 100                 if (row == -1 || path.getLastPathComponent() instanceof EmptyNode) {
 101                     hover = -1;
 102                     setCursor(Cursor.getDefaultCursor());
 103                 } else {
 104                     hover = row;
 105                     Rectangle rowBounds = ((NavTreeUI) getUI()).getStrictBounds(path);
 106                     if (rowBounds.contains(mousePoint)) {
 107                         setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
 108                     } else {
 109                         setCursor(Cursor.getDefaultCursor());
 110                     }
 111                 }
 112                 repaint();
 113             }
 114         });
 115         super.setCellRenderer(new DefaultTreeCellRenderer() {
 116 
 117             private boolean isSeparatorNode;
 118 
 119             @Override
 120             public Color getBackgroundNonSelectionColor() {
 121                 return null;
 122             }
 123 
 124             @Override
 125             public Color getBackground() {
 126                 return null;
 127             }
 128 
 129             @Override
 130             public Component getTreeCellRendererComponent(JTree tree, Object value,
 131                     boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
 132                 isSeparatorNode = false;
 133 
 134                 if (value instanceof RootNode) {
 135                     return this;
 136                 }
 137                 if (value instanceof EmptyNode) {
 138                     if (value instanceof SeparatorNode) {
 139                         isSeparatorNode = true;
 140                     }
 141                     setText("");
 142                     setIcon(null);
 143                     return this;
 144                 }
 145 
 146                 File folder = ((FolderTreeNode) value).folder;
 147 
 148                 setIcon(view.getSystemIcon(folder));
 149                 setText(view.getSystemDisplayName(folder));
 150                 return this;
 151             }
 152 
 153             @Override
 154             public Dimension getPreferredSize() {
 155                 if (getText().isEmpty() && getIcon() == null) {
 156                     return new Dimension(0, isSeparatorNode ? 18 : 12);
 157                 } else {
 158                     Dimension result = super.getPreferredSize();;
 159                     result.height = 24;
 160                     return result;
 161                 }
 162             }
 163 
 164         });
 165 
 166         RootNode rootNode = new RootNode();
 167         super.setModel(new DefaultTreeModel(rootNode));
 168 
 169         int childCount = rootNode.getChildCount();
 170         for (int i = childCount - 1; i >= 0; i--) {
 171             if (i != childCount - 2) {
 172                 super.expandRow(i);
 173             }
 174         }
 175 
 176         super.setRowHeight(0);
 177 
 178         getSelectionModel().addTreeSelectionListener(evt -> {
 179             TreePath path = evt.getNewLeadSelectionPath();
 180             if (path == null) {
 181                 return;
 182             }
 183 
 184             if (path.getLastPathComponent() instanceof EmptyNode) {
 185                 // disable selecting an EmptyNode
 186                 
 187                 if (evt.getOldLeadSelectionPath() == null
 188                         || !(evt.getOldLeadSelectionPath().
 189                                 getLastPathComponent() instanceof EmptyNode)) {
 190                     setSelectionPath(evt.getOldLeadSelectionPath());
 191                 }
 192                 return;
 193             }
 194 
 195             FolderTreeNode node = (FolderTreeNode) path.getLastPathComponent();
 196             fileChooser.setCurrentDirectory(node.folder);
 197         });
 198 
 199         getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
 200 
 201         setTransferHandler(fileChooser.getTransferHandler());
 202         putClientProperty("Tree.enableDoubleClickOnExpandControl", TRUE);
 203 
 204         // these 2 values are used in BasicFileChooserUI's FileTransferHandler
 205         putClientProperty("FileChooser.instance", fileChooser);
 206         putClientProperty("FileChooser.supportsFileDrop", TRUE);
 207     }
 208 
 209     @Override
 210     public void propertyChange(PropertyChangeEvent evt) {
 211         if (evt.getPropertyName().equals("transferHandler")) {
 212             setTransferHandler(fileChooser.getTransferHandler());
 213         }
 214         if (evt.getPropertyName().equals((FILE_SYSTEM_VIEW_CHANGED_PROPERTY))) {
 215             repaint();
 216         }
 217     }
 218 
 219     public TreePath getPathForLocation(int x, int y) {
 220         return getClosestPathForLocation(x, y);
 221     }
 222 
 223     @Override
 224     public Dimension getPreferredSize() {
 225         Dimension result = super.getPreferredSize();
 226         result.width = 230;
 227         return result;
 228     }
 229 
 230     private class RootNode implements TreeNode {
 231 
 232         private final List<TreeNode> children = new ArrayList<>();
 233         private FileSystemView.EssentialFolderSection[] prevCategories;
 234 
 235         private FileSystemView.EssentialFolderSection[] getCategories() {
 236             return fileChooser.getFileSystemView().getEssentialFolders();
 237         }
 238 
 239         private boolean isModified() {
 240             FileSystemView.EssentialFolderSection[] categories = getCategories();
 241             if (Arrays.equals(prevCategories, categories)) {
 242                 return false;
 243             }
 244 
 245             prevCategories = categories;
 246             return true;
 247         }
 248 
 249         private void validateChildrenCache() {
 250             if (isModified()) {
 251                 EssentialFolderSection[] categories = getCategories();
 252 
 253                 children.clear();
 254                 children.add(new EmptyNode(this));
 255                 for (EssentialFolderSection category : categories) {
 256                     children.add(new SeparatorNode(this));
 257 
 258                     int firstEmptyNodeLocation = children.size();
 259                     for (File folder : category.getFolders()) {
 260                         children.add(new EmptyNode(this));
 261                         children.add(FOLDER_NODE_CACHE.computeIfAbsent(folder,
 262                                 f -> new FolderTreeNode(this, f, true)));
 263                     }
 264 
 265                     children.remove(firstEmptyNodeLocation);
 266                 }
 267                 children.add(new EmptyNode(this));
 268                 children.remove(1); // remove the first separator
 269             }
 270         }
 271 
 272         @Override
 273         public TreeNode getChildAt(int childIndex) {
 274             validateChildrenCache();
 275             return children.get(childIndex);
 276         }
 277 
 278         @Override
 279         public int getChildCount() {
 280             validateChildrenCache();
 281             return children.size();
 282         }
 283 
 284         @Override
 285         public TreeNode getParent() {
 286             return null;
 287         }
 288 
 289         @Override
 290         public int getIndex(TreeNode node) {
 291             return -1;
 292         }
 293 
 294         @Override
 295         public boolean getAllowsChildren() {
 296             return true;
 297         }
 298 
 299         @Override
 300         public boolean isLeaf() {
 301             return false;
 302         }
 303 
 304         @Override
 305         public Enumeration children() {
 306             validateChildrenCache();
 307             return Collections.enumeration(children);
 308         }
 309 
 310     }
 311     private static final Map<File, FolderTreeNode> FOLDER_NODE_CACHE = new HashMap<>();
 312 
 313     public class FolderTreeNode implements TreeNode {
 314 
 315         public final File folder;
 316         private final TreeNode parent;
 317         private final boolean recursiveChildren;
 318 
 319         private FolderTreeNode(TreeNode parent, File folder, boolean recursiveChildren) {
 320             assert folder != null;
 321 
 322             try {
 323                 while (fileChooser.getFileSystemView().isLink(folder)) {
 324                     folder = fileChooser.getFileSystemView().getLinkLocation(folder);
 325                 }
 326             } catch (FileNotFoundException ex) {
 327                 throw new UncheckedIOException(ex);
 328             }
 329 
 330             this.parent = parent;
 331             this.folder = folder;
 332             this.recursiveChildren = recursiveChildren;
 333         }
 334 
 335         private List<File> childrenCache;
 336 
 337         @SuppressWarnings("ReturnOfCollectionOrArrayField")
 338         private List<File> getChildren() {
 339             if (childrenCache != null) {
 340                 return childrenCache;
 341             }
 342 
 343             if (!recursiveChildren && !(parent instanceof RootNode)) {
 344                 return childrenCache = emptyList();
 345             }
 346 
 347             Object hidingValue = getDefaultToolkit().getDesktopProperty("awt.file.showHiddenFiles");
 348             File[] files = view.getFiles(folder, (boolean) hidingValue);
 349             return childrenCache = Arrays.stream(files).
 350                     filter(view::isTraversable).
 351                     collect(toList());
 352         }
 353 
 354         @Override
 355         public int getChildCount() {
 356             return getChildren().size();
 357         }
 358 
 359         @Override
 360         public TreeNode getChildAt(int childIndex) {
 361             File file = getChildren().get(childIndex);
 362             return FOLDER_NODE_CACHE.computeIfAbsent(file,
 363                     f -> new FolderTreeNode(this, f, recursiveChildren));
 364         }
 365 
 366         @Override
 367         public TreeNode getParent() {
 368             return parent;
 369         }
 370 
 371         @Override
 372         public int getIndex(TreeNode node) {
 373             return ((FolderTreeNode) node).getChildren().indexOf(folder);
 374         }
 375 
 376         @Override
 377         public boolean getAllowsChildren() {
 378             return true;
 379         }
 380 
 381         @Override
 382         public boolean isLeaf() {
 383             if (!recursiveChildren && !(parent instanceof RootNode)) {
 384                 return true;
 385             }
 386 
 387             if (childrenCache == null) {
 388                 return false;
 389             }
 390             return childrenCache.isEmpty();
 391         }
 392 
 393         @Override
 394         public Enumeration children() {
 395             return Collections.enumeration(getChildren());
 396         }
 397 
 398     }
 399 
 400     private class EmptyNode implements TreeNode {
 401 
 402         private final TreeNode parent;
 403 
 404         private EmptyNode(TreeNode parent) {
 405             this.parent = parent;
 406         }
 407 
 408         @Override
 409         public TreeNode getChildAt(int childIndex) {
 410             throw new UnsupportedOperationException("an EmptyNode doesn't have children");
 411         }
 412 
 413         @Override
 414         public int getChildCount() {
 415             return 0;
 416         }
 417 
 418         @Override
 419         public TreeNode getParent() {
 420             return parent;
 421         }
 422 
 423         @Override
 424         public int getIndex(TreeNode node) {
 425             throw new UnsupportedOperationException("an EmptyNode doesn't have children");
 426         }
 427 
 428         @Override
 429         public boolean getAllowsChildren() {
 430             return false;
 431         }
 432 
 433         @Override
 434         public boolean isLeaf() {
 435             return true;
 436         }
 437 
 438         @Override
 439         public Enumeration children() {
 440             return Collections.emptyEnumeration();
 441         }
 442     }
 443 
 444     public class SeparatorNode extends EmptyNode {
 445 
 446         public SeparatorNode(TreeNode parent) {
 447             super(parent);
 448         }
 449 
 450     }
 451 
 452     private class NavTreeUI extends WindowsTreeUI {
 453 
 454         private boolean expandControlHover;
 455 
 456         @Override
 457         protected void installDefaults() {
 458             super.installDefaults();
 459             setLeftChildIndent(0);
 460 
 461             setExpandedIcon(new AnimatableIcon(COLLAPSE_ICON));
 462             setCollapsedIcon(new AnimatableIcon(EXPAND_ICON));
 463         }
 464 
 465         @Override
 466         protected void installListeners() {
 467             super.installListeners();
 468             tree.addMouseListener(new MouseAdapter() {
 469 
 470                 private final Timer animationTimer = new Timer(30, evt -> onAnimFrame());
 471 
 472                 @Override
 473                 public void mouseEntered(MouseEvent e) {
 474                     Instant start = now();
 475                     iconAlphaSupplier = () -> ((float) between(start, now()).toMillis()) / 300;
 476                     animationTimer.start();
 477                 }
 478 
 479                 @Override
 480                 public void mouseExited(MouseEvent e) {
 481                     Instant start = now();
 482                     iconAlphaSupplier = () -> 1 - ((float) between(start, now()).toMillis()) / 1000;
 483                     animationTimer.start();
 484                 }
 485 
 486                 private void onAnimFrame() {
 487                     if (iconAlphaSupplier.get() < 0) {
 488                         animationTimer.stop();
 489                         iconAlphaSupplier = () -> 0f;
 490                     } else if (iconAlphaSupplier.get() > 1) {
 491                         animationTimer.stop();
 492                         iconAlphaSupplier = () -> 1f;
 493                     } else {
 494                         tree.repaint();
 495                     }
 496                 }
 497             });
 498         }
 499 
 500         @Override
 501         protected void updateDepthOffset() {
 502             depthOffset = 1;
 503         }
 504 
 505         @Override
 506         protected boolean shouldPaintExpandControl(TreePath path, int row,
 507                 boolean isExpanded,
 508                 boolean hasBeenExpanded,
 509                 boolean isLeaf) {
 510             return !isLeaf;
 511         }
 512 
 513         public Rectangle getStrictBounds(TreePath path) {
 514             return super.getPathBounds(WindowsNavigationTree.this, path);
 515         }
 516 
 517         @Override
 518         public Rectangle getPathBounds(JTree tree, TreePath path) {
 519             Rectangle result = super.getPathBounds(tree, path);
 520             if (result == null) {
 521                 return null;
 522             }
 523 
 524             result.x = 0;
 525             result.width = getWidth();
 526             return result;
 527         }
 528 
 529         @Override
 530         protected void paintExpandControl(Graphics g, Rectangle clipBounds,
 531                 Insets insets, Rectangle bounds, TreePath path,
 532                 int row, boolean isExpanded,
 533                 boolean hasBeenExpanded, boolean isLeaf) {
 534             paintBackground(g, row, bounds);
 535             expandControlHover = row == hover && isLocationInExpandControl(path,
 536                     mousePoint.x, mousePoint.y);
 537             try {
 538                 super.paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
 539             } finally {
 540                 expandControlHover = false;
 541             }
 542         }
 543 
 544         @Override
 545         public Icon getCollapsedIcon() {
 546             if (expandControlHover) {
 547                 return EXPAND_ICON_HOVER;
 548             }
 549             return super.getCollapsedIcon();
 550         }
 551 
 552         @Override
 553         public Icon getExpandedIcon() {
 554             if (expandControlHover) {
 555                 return COLLAPSE_ICON_HOVER;
 556             }
 557             return super.getExpandedIcon();
 558         }
 559 
 560         @Override
 561         protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
 562             if (!shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) {
 563                 paintBackground(g, row, bounds);
 564             }
 565 
 566             if (path.getLastPathComponent() instanceof SeparatorNode) {
 567                 bounds.y += bounds.height / 2 - 1;
 568 
 569                 g.setColor(UIManager.getColor("Separator.foreground"));
 570                 g.drawLine(0, bounds.y, tree.getWidth(), bounds.y);
 571 
 572                 g.setColor(UIManager.getColor("Separator.background"));
 573                 g.drawLine(0, bounds.y + 1, tree.getWidth(), bounds.y + 1);
 574                 return;
 575             }
 576 
 577             super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
 578         }
 579 
 580         private void paintBackground(Graphics g, int row, Rectangle bounds) {
 581             if (hover == row) {
 582                 g.setColor(ITEM_HOVERED_COLOR);
 583                 g.fillRect(0, bounds.y, tree.getWidth(), bounds.height);
 584             }
 585             if (tree.isRowSelected(row)) {
 586                 g.setColor(ITEM_SELECTED_COLOR);
 587                 g.fillRect(0, bounds.y, tree.getWidth(), bounds.height);
 588                 if (hover == row) {
 589                     g.setColor(WindowsFileListUI.ITEM_SELECTED_BORDER_COLOR);
 590                     g.drawRect(0, bounds.y, tree.getWidth() - 1, tree.getRowHeight());
 591                 }
 592             }
 593         }
 594 
 595         @Override
 596         protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) {
 597         }
 598 
 599         @Override
 600         protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) {
 601         }
 602     }
 603 
 604     private Supplier<Float> iconAlphaSupplier = () -> 0f;
 605 
 606     private class AnimatableIcon implements Icon {
 607 
 608         private final Icon icon;
 609 
 610         public AnimatableIcon(Icon icon) {
 611             this.icon = icon;
 612         }
 613 
 614         @Override
 615         public void paintIcon(Component c, Graphics g, int x, int y) {
 616             if (g instanceof Graphics2D) {
 617                 Graphics2D g2d = (Graphics2D) g;
 618                 Composite prevComposite = g2d.getComposite();
 619 
 620                 float alpha = Math.min(1, Math.max(0, iconAlphaSupplier.get()));
 621                 g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
 622                 icon.paintIcon(c, g, x, y);
 623                 g2d.setComposite(prevComposite);
 624             } else {
 625                 icon.paintIcon(c, g, x, y);
 626             }
 627         }
 628 
 629         @Override
 630         public int getIconWidth() {
 631             return icon.getIconWidth();
 632         }
 633 
 634         @Override
 635         public int getIconHeight() {
 636             return icon.getIconHeight();
 637         }
 638 
 639     }
 640 
 641     private static Image initDownArrowNormalImage() {
 642         BufferedImage img = new BufferedImage(9, 6, 6);
 643         img.setRGB(0, 0, 1610612736);
 644         img.setRGB(0, 1, -2013265920);
 645         img.setRGB(0, 2, 83886080);
 646         img.setRGB(0, 3, 14277081);
 647         img.setRGB(0, 4, 14277081);
 648         img.setRGB(0, 5, 14277081);
 649         img.setRGB(1, 0, -2013265920);
 650         img.setRGB(1, 1, -1073741824);
 651         img.setRGB(1, 2, -2013265920);
 652         img.setRGB(1, 3, 67108864);
 653         img.setRGB(1, 4, 14277081);
 654         img.setRGB(1, 5, 14277081);
 655         img.setRGB(2, 0, 67108864);
 656         img.setRGB(2, 1, -2013265920);
 657         img.setRGB(2, 2, -1073741824);
 658         img.setRGB(2, 3, -2013265920);
 659         img.setRGB(2, 4, 67108864);
 660         img.setRGB(2, 5, 14277081);
 661         img.setRGB(3, 0, 14277081);
 662         img.setRGB(3, 1, 67108864);
 663         img.setRGB(3, 2, -2013265920);
 664         img.setRGB(3, 3, -1073741824);
 665         img.setRGB(3, 4, -2013265920);
 666         img.setRGB(3, 5, 67108864);
 667         img.setRGB(4, 0, 14277081);
 668         img.setRGB(4, 1, 14277081);
 669         img.setRGB(4, 2, 150994944);
 670         img.setRGB(4, 3, -1291845632);
 671         img.setRGB(4, 4, -1073741824);
 672         img.setRGB(4, 5, 1577058304);
 673         img.setRGB(5, 0, 14277081);
 674         img.setRGB(5, 1, 67108864);
 675         img.setRGB(5, 2, -2013265920);
 676         img.setRGB(5, 3, -1073741824);
 677         img.setRGB(5, 4, -2013265920);
 678         img.setRGB(5, 5, 83886080);
 679         img.setRGB(6, 0, 67108864);
 680         img.setRGB(6, 1, -2013265920);
 681         img.setRGB(6, 2, -1073741824);
 682         img.setRGB(6, 3, -2013265920);
 683         img.setRGB(6, 4, 67108864);
 684         img.setRGB(6, 5, 14277081);
 685         img.setRGB(7, 0, -2013265920);
 686         img.setRGB(7, 1, -1073741824);
 687         img.setRGB(7, 2, -2013265920);
 688         img.setRGB(7, 3, 67108864);
 689         img.setRGB(7, 4, 14277081);
 690         img.setRGB(7, 5, 14277081);
 691         img.setRGB(8, 0, 1593835520);
 692         img.setRGB(8, 1, -2013265920);
 693         img.setRGB(8, 2, 67108864);
 694         img.setRGB(8, 3, 14277081);
 695         img.setRGB(8, 4, 14277081);
 696         img.setRGB(8, 5, 14277081);
 697         return img;
 698     }
 699 
 700     private static Image initRightArrowNormalImage() {
 701         BufferedImage img = new BufferedImage(6, 9, 6);
 702         img.setRGB(0, 0, 754974720);
 703         img.setRGB(0, 1, 1073741824);
 704         img.setRGB(0, 2, 33554432);
 705         img.setRGB(0, 6, 33554432);
 706         img.setRGB(0, 7, 1073741824);
 707         img.setRGB(0, 8, 754974720);
 708         img.setRGB(1, 0, 1073741824);
 709         img.setRGB(1, 1, 1493172224);
 710         img.setRGB(1, 2, 1073741824);
 711         img.setRGB(1, 3, 33554432);
 712         img.setRGB(1, 5, 33554432);
 713         img.setRGB(1, 6, 1073741824);
 714         img.setRGB(1, 7, 1493172224);
 715         img.setRGB(1, 8, 1073741824);
 716         img.setRGB(2, 0, 33554432);
 717         img.setRGB(2, 1, 1073741824);
 718         img.setRGB(2, 2, 1493172224);
 719         img.setRGB(2, 3, 1073741824);
 720         img.setRGB(2, 4, 83886080);
 721         img.setRGB(2, 5, 1073741824);
 722         img.setRGB(2, 6, 1493172224);
 723         img.setRGB(2, 7, 1073741824);
 724         img.setRGB(2, 8, 33554432);
 725         img.setRGB(3, 1, 33554432);
 726         img.setRGB(3, 2, 1073741824);
 727         img.setRGB(3, 3, 1493172224);
 728         img.setRGB(3, 4, 1392508928);
 729         img.setRGB(3, 5, 1493172224);
 730         img.setRGB(3, 6, 1073741824);
 731         img.setRGB(3, 7, 33554432);
 732         img.setRGB(4, 2, 33554432);
 733         img.setRGB(4, 3, 1073741824);
 734         img.setRGB(4, 4, 1493172224);
 735         img.setRGB(4, 5, 1073741824);
 736         img.setRGB(4, 6, 33554432);
 737         img.setRGB(5, 3, 33554432);
 738         img.setRGB(5, 4, 721420288);
 739         img.setRGB(5, 5, 33554432);
 740         return img;
 741     }
 742 
 743     private static Image initDownArrowHoverImage() {
 744         BufferedImage img = new BufferedImage(9, 6, 6);
 745         img.setRGB(0, 0, 1879096822);
 746         img.setRGB(0, 1, -1627341067);
 747         img.setRGB(0, 2, 100702207);
 748         img.setRGB(0, 3, 15070207);
 749         img.setRGB(0, 4, 15070207);
 750         img.setRGB(0, 5, 15070207);
 751         img.setRGB(1, 0, -1627341067);
 752         img.setRGB(1, 1, -536822282);
 753         img.setRGB(1, 2, -1627341067);
 754         img.setRGB(1, 3, 67156735);
 755         img.setRGB(1, 4, 15070207);
 756         img.setRGB(1, 5, 15070207);
 757         img.setRGB(2, 0, 67156735);
 758         img.setRGB(2, 1, -1627341067);
 759         img.setRGB(2, 2, -536822282);
 760         img.setRGB(2, 3, -1627341067);
 761         img.setRGB(2, 4, 67156735);
 762         img.setRGB(2, 5, 15070207);
 763         img.setRGB(3, 0, 15070207);
 764         img.setRGB(3, 1, 67156735);
 765         img.setRGB(3, 2, -1627341067);
 766         img.setRGB(3, 3, -536822282);
 767         img.setRGB(3, 4, -1627341067);
 768         img.setRGB(3, 5, 67156735);
 769         img.setRGB(4, 0, 15070207);
 770         img.setRGB(4, 1, 15070207);
 771         img.setRGB(4, 2, 184600063);
 772         img.setRGB(4, 3, -771703049);
 773         img.setRGB(4, 4, -536822282);
 774         img.setRGB(4, 5, 1828765174);
 775         img.setRGB(5, 0, 15070207);
 776         img.setRGB(5, 1, 67156735);
 777         img.setRGB(5, 2, -1627341067);
 778         img.setRGB(5, 3, -536822282);
 779         img.setRGB(5, 4, -1627341067);
 780         img.setRGB(5, 5, 100702207);
 781         img.setRGB(6, 0, 67156735);
 782         img.setRGB(6, 1, -1627341067);
 783         img.setRGB(6, 2, -536822282);
 784         img.setRGB(6, 3, -1627341067);
 785         img.setRGB(6, 4, 67156735);
 786         img.setRGB(6, 5, 15070207);
 787         img.setRGB(7, 0, -1627341067);
 788         img.setRGB(7, 1, -536822282);
 789         img.setRGB(7, 2, -1627341067);
 790         img.setRGB(7, 3, 67156735);
 791         img.setRGB(7, 4, 15070207);
 792         img.setRGB(7, 5, 15070207);
 793         img.setRGB(8, 0, 1862319862);
 794         img.setRGB(8, 1, -1627341067);
 795         img.setRGB(8, 2, 67156735);
 796         img.setRGB(8, 3, 15070207);
 797         img.setRGB(8, 4, 15070207);
 798         img.setRGB(8, 5, 15070207);
 799         return img;
 800     }
 801 
 802     private static Image initRightArrowHoverImage() {
 803         BufferedImage img = new BufferedImage(6, 9, 6);
 804         img.setRGB(0, 0, 1409335286);
 805         img.setRGB(0, 1, 1996537334);
 806         img.setRGB(0, 2, 50374655);
 807         img.setRGB(0, 3, 15070207);
 808         img.setRGB(0, 4, 15070207);
 809         img.setRGB(0, 5, 15070207);
 810         img.setRGB(0, 6, 67156735);
 811         img.setRGB(0, 7, 1996537334);
 812         img.setRGB(0, 8, 1409335286);
 813         img.setRGB(1, 0, 1996537334);
 814         img.setRGB(1, 1, -1476346122);
 815         img.setRGB(1, 2, 1996537334);
 816         img.setRGB(1, 3, 50374655);
 817         img.setRGB(1, 4, 15070207);
 818         img.setRGB(1, 5, 67156735);
 819         img.setRGB(1, 6, 1996537334);
 820         img.setRGB(1, 7, -1476346122);
 821         img.setRGB(1, 8, 1996537334);
 822         img.setRGB(2, 0, 67156735);
 823         img.setRGB(2, 1, 1996537334);
 824         img.setRGB(2, 2, -1476346122);
 825         img.setRGB(2, 3, 1996537334);
 826         img.setRGB(2, 4, 151042815);
 827         img.setRGB(2, 5, 1996537334);
 828         img.setRGB(2, 6, -1476346122);
 829         img.setRGB(2, 7, 1996537334);
 830         img.setRGB(2, 8, 67156735);
 831         img.setRGB(3, 0, 15070207);
 832         img.setRGB(3, 1, 67156735);
 833         img.setRGB(3, 2, 1996537334);
 834         img.setRGB(3, 3, -1476346122);
 835         img.setRGB(3, 4, -1660895755);
 836         img.setRGB(3, 5, -1476346122);
 837         img.setRGB(3, 6, 1996537334);
 838         img.setRGB(3, 7, 67156735);
 839         img.setRGB(3, 8, 15070207);
 840         img.setRGB(4, 0, 15070207);
 841         img.setRGB(4, 1, 15070207);
 842         img.setRGB(4, 2, 67156735);
 843         img.setRGB(4, 3, 1996537334);
 844         img.setRGB(4, 4, -1476346122);
 845         img.setRGB(4, 5, 1996537334);
 846         img.setRGB(4, 6, 67156735);
 847         img.setRGB(4, 7, 15070207);
 848         img.setRGB(4, 8, 15070207);
 849         img.setRGB(5, 0, 15070207);
 850         img.setRGB(5, 1, 15070207);
 851         img.setRGB(5, 2, 15070207);
 852         img.setRGB(5, 3, 67156735);
 853         img.setRGB(5, 4, 1375779830);
 854         img.setRGB(5, 5, 67156735);
 855         img.setRGB(5, 6, 15070207);
 856         img.setRGB(5, 7, 15070207);
 857         img.setRGB(5, 8, 15070207);
 858         return img;
 859     }
 860 }