package net.sourceforge.fidocadj.dialogs; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import javax.swing.plaf.metal.*; import net.sourceforge.fidocadj.globals.*; import java.awt.*; import java.io.*; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.util.*; /** * JFileChooser accessory panel for listing libraries description. * * @author Kohta Ozaki */ public class LibraryPanel extends JPanel implements PropertyChangeListener { final private static int PREFERRED_PANEL_WIDTH = 250; final private JFileChooser fc; final private LibraryListModel listModel; /** * Creates UI. * This LibraryPanel register as accessory panel to JFileChooser. * And adds as listener for receiving selection change event. * @param fc JFileChooser instance. */ public LibraryPanel(JFileChooser fc) { this.fc = fc; fc.addPropertyChangeListener(this); fc.setAccessory(this); listModel = new LibraryListModel(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { initGUI(); } } ); listModel.setDirectory(fc.getCurrentDirectory()); } /** * Creates UI. */ private void initGUI() { JList<LibraryDesc> fileList; JScrollPane sp; setLayout(new BorderLayout()); setPreferredSize(new Dimension(PREFERRED_PANEL_WIDTH,1)); // If this class is run as a standalone program, the Globals.messages // resource handler might not be initizalized. In this case, // an english tag will do the job. if(Globals.messages==null) add(BorderLayout.NORTH, new JLabel("Libraries in directory:")); else add(BorderLayout.NORTH,new JLabel( Globals.messages.getString("lib_in_dir"))); fileList = new JList<LibraryDesc>(listModel); fileList.setCellRenderer(new ListCellRenderer<LibraryDesc>() { @Override public Component getListCellRendererComponent(JList<? extends LibraryPanel.LibraryDesc> list, LibraryPanel.LibraryDesc value, int index, boolean isSelected, boolean cellHasFocus) { LibraryDesc desc = (LibraryDesc) value; String libraryName; JPanel p; Icon icon = MetalIconFactory.getTreeFloppyDriveIcon(); Icon spaceIcon = new SpaceIcon(icon.getIconWidth(), icon.getIconHeight()); if (desc.libraryName == null) { libraryName = "---"; } else { libraryName = "(" + desc.libraryName + ")"; } p = new JPanel(); p.setBorder(new EmptyBorder(2,0,3,0)); p.setOpaque(false); p.setLayout(new BorderLayout()); p.add(BorderLayout.NORTH, new JLabel(desc.filename, icon, SwingConstants.LEFT)); p.add(BorderLayout.SOUTH, new JLabel(libraryName, spaceIcon, SwingConstants.LEFT)); return p; } }); sp = new JScrollPane(fileList); sp.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); add(BorderLayout.CENTER, sp); // Disable focus. // The list is never selected. fileList.setFocusable(false); } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals( JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) { listModel.setDirectory(fc.getSelectedFile()); } if (evt.getPropertyName().equals( JFileChooser.DIRECTORY_CHANGED_PROPERTY)) { listModel.setDirectory(fc.getCurrentDirectory()); } } /** For test method. Shows only JFileChooser with this LibraryPanel. @param args the input parameters on the command line. */ public static void main(String... args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JFileChooser fc; fc = new JFileChooser(); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fc.setPreferredSize(new Dimension(800,400)); new LibraryPanel(fc); fc.showOpenDialog(null); } }); } /** * ListModel to provide libraries list. * This model searches libraries in selected directory. * And provide library name and filename to JList component. */ public class LibraryListModel implements ListModel<LibraryDesc> { final private ArrayList<ListDataListener> listeners; final private ArrayList<LibraryDesc> libraryList; private File currentDir; /** Constructs model. */ LibraryListModel() { listeners = new ArrayList<ListDataListener>(); libraryList = new ArrayList<LibraryDesc>(); } /** * Sets directory. * And updates libraries list in directory. * @param dir selected directory as File */ public void setDirectory(File dir) { currentDir = dir; clearList(); if (currentDir != null && currentDir.canRead() && currentDir.isDirectory()) { // Permission check refreshList(); } // DB -> KO check if it is correct. I removed a "}" fireChanged(); } private void refreshList() { File[] files; LibraryDesc desc; files = currentDir.listFiles(new FileFilter() { @Override public boolean accept(File f) { if (f.isFile() && f.getName().toLowerCase().matches("^.*\\.fcl$")) { return true; } // DB -> KO check if it is correct. I removed a "}" return false; } }); if(files==null) return; for (File f : files) { desc = new LibraryDesc(); desc.filename = f.getName(); desc.libraryName = getLibraryName(f); libraryList.add(desc); } // Sort list by filename. Collections.sort(libraryList, new Comparator<LibraryDesc>() { @Override public int compare(LibraryDesc ld1, LibraryDesc ld2) { // Sort with case sensitive. // This is usually made in UNIX file systems. // If case sensitive is not needed, use // String.compareToIgnoreCase return ld1.filename.compareTo(ld2.filename); } /*@Override public boolean equals(Object obj) { // DB. FindBugs complains that this methods always // returns "false". It considers it a quite high priority // issue to be solved. Is there any particular reason why // this must return false? // return false; return this == obj; }*/ }); } private void clearList() { libraryList.clear(); } private void fireChanged() { for (ListDataListener l : listeners) { l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, 0)); } } private String getLibraryName(File f) { // if there is a public api for reading library name direct, // rewrite this section. // maxReadLine = -1 : reads to EOF. // = num : reads to num line. int maxReadline = -1; int pt = 0; FileReader fr = null; BufferedReader br = null; String buf; String libraryName = null; try { fr = new FileReader(f); br = new BufferedReader(fr); while (true) { buf = br.readLine(); // check EOF if (buf == null) { break; } if (buf.matches("^\\[FIDOLIB .*\\]\\s*")) { buf = buf.trim(); libraryName = buf.substring(9, buf.length() - 1); break; } if (maxReadline != -1 && maxReadline <= pt) { break; } pt++; } // DB: it seems to me that catching an Exception is a // little bit too general. Which kind of reasonable // problems have we got to handle here? } catch (Exception e) { // return null for libraryName libraryName=null; } finally { try { if (br != null) { br.close(); } if (fr != null) { fr.close(); } // DB: it seems to me that catching an Exception is a // little bit too general. Which kind of reasonable // problems have we got to handle here? } catch (Exception e) { System.out.println("Problems while closing streams."); } } return libraryName; } @Override public void addListDataListener(ListDataListener l) { listeners.add(l); } @Override public void removeListDataListener(ListDataListener l) { listeners.remove(l); } @Override public int getSize() { return libraryList.size(); } @Override public LibraryDesc getElementAt(int index) { return libraryList.get(index); } } /** * Library description class. */ private class LibraryDesc { public String filename; public String libraryName; @Override public String toString() { return String.format("%s (%s)", filename, libraryName); } } /** * Dummy icon class for spacing. */ private class SpaceIcon implements Icon { final private int width; final private int height; /** Constructor. Creates a dummy icon with the given size. */ SpaceIcon(int width, int height) { this.width = width; this.height = height; } @Override public int getIconHeight() { return height; } @Override public int getIconWidth() { return width; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { // NOP } } }