Libraryless. Click here for Pure Java version (3175L/22K/74K).
1 | !7 |
2 | |
3 | import javax.swing.*; |
4 | import javax.swing.border.*; |
5 | import javax.swing.event.*; |
6 | import javax.swing.tree.*; |
7 | import javax.swing.table.*; |
8 | import javax.swing.filechooser.FileSystemView; |
9 | |
10 | import javax.imageio.ImageIO; |
11 | |
12 | import java.util.Date; |
13 | import java.util.List; |
14 | import java.util.ArrayList; |
15 | |
16 | import java.io.*; |
17 | import java.nio.channels.FileChannel; |
18 | |
19 | import java.net.URL; |
20 | |
21 | /** |
22 | A basic File Browser. Requires 1.6+ for the Desktop & SwingWorker |
23 | classes, amongst other minor things. |
24 | |
25 | Includes support classes FileTableModel & FileTreeCellRenderer. |
26 | |
27 | @TODO Bugs |
28 | <li>Fix keyboard focus issues - especially when functions like |
29 | rename/delete etc. are called that update nodes & file lists. |
30 | <li>Needs more testing in general. |
31 | |
32 | @TODO Functionality |
33 | <li>Double clicking a directory in the table, should update the tree |
34 | <li>Move progress bar? |
35 | <li>Add other file display modes (besides table) in CardLayout? |
36 | <li>Menus + other cruft? |
37 | <li>Implement history/back |
38 | <li>Allow multiple selection |
39 | <li>Add file search |
40 | |
41 | @author Andrew Thompson |
42 | @version 2011-06-08 |
43 | @see http://codereview.stackexchange.com/q/4446/7784 |
44 | @license LGPL |
45 | */ |
46 | |
47 | p-substance { |
48 | FileBrowser.main(args); |
49 | } |
50 | |
51 | sclass FileBrowser { |
52 | |
53 | /** Title of the application */ |
54 | public static final String APP_TITLE = "FileBro"; |
55 | /** Used to open/edit/print files. */ |
56 | private Desktop desktop; |
57 | /** Provides nice icons and names for files. */ |
58 | private FileSystemView fileSystemView; |
59 | |
60 | /** currently selected File. */ |
61 | private File currentFile; |
62 | |
63 | /** Main GUI container */ |
64 | private JPanel gui; |
65 | |
66 | /** File-system tree. Built Lazily */ |
67 | private JTree tree; |
68 | private DefaultTreeModel treeModel; |
69 | |
70 | /** Directory listing */ |
71 | private JTable table; |
72 | private JProgressBar progressBar; |
73 | /** Table model for File[]. */ |
74 | private FileTableModel fileTableModel; |
75 | private ListSelectionListener listSelectionListener; |
76 | private boolean cellSizesSet = false; |
77 | private int rowIconPadding = 6; |
78 | |
79 | /* File controls. */ |
80 | private JButton openFile; |
81 | private JButton printFile; |
82 | private JButton editFile; |
83 | |
84 | /* File details. */ |
85 | private JLabel fileName; |
86 | private JTextField path; |
87 | private JLabel date; |
88 | private JLabel size; |
89 | private JCheckBox readable; |
90 | private JCheckBox writable; |
91 | private JCheckBox executable; |
92 | private JRadioButton isDirectory; |
93 | private JRadioButton isFile; |
94 | |
95 | /* GUI options/containers for new File/Directory creation. Created lazily. */ |
96 | private JPanel newFilePanel; |
97 | private JRadioButton newTypeFile; |
98 | private JTextField name; |
99 | |
100 | public Container getGui() { |
101 | if (gui==null) { |
102 | gui = new JPanel(new BorderLayout(3,3)); |
103 | gui.setBorder(new EmptyBorder(5,5,5,5)); |
104 | |
105 | fileSystemView = FileSystemView.getFileSystemView(); |
106 | desktop = Desktop.getDesktop(); |
107 | |
108 | JPanel detailView = new JPanel(new BorderLayout(3,3)); |
109 | |
110 | table = new JTable(); |
111 | table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
112 | table.setAutoCreateRowSorter(true); |
113 | table.setShowVerticalLines(false); |
114 | |
115 | listSelectionListener = new ListSelectionListener() { |
116 | @Override |
117 | public void valueChanged(ListSelectionEvent lse) { |
118 | int row = table.getSelectionModel().getLeadSelectionIndex(); |
119 | setFileDetails( ((FileTableModel)table.getModel()).getFile(row) ); |
120 | } |
121 | }; |
122 | table.getSelectionModel().addListSelectionListener(listSelectionListener); |
123 | JScrollPane tableScroll = new JScrollPane(table); |
124 | Dimension d = tableScroll.getPreferredSize(); |
125 | tableScroll.setPreferredSize(new Dimension((int)d.getWidth(), (int)d.getHeight()/2)); |
126 | detailView.add(tableScroll, BorderLayout.CENTER); |
127 | |
128 | // the File tree |
129 | DefaultMutableTreeNode root = new DefaultMutableTreeNode(); |
130 | treeModel = new DefaultTreeModel(root); |
131 | |
132 | TreeSelectionListener treeSelectionListener = new TreeSelectionListener() { |
133 | public void valueChanged(TreeSelectionEvent tse){ |
134 | DefaultMutableTreeNode node = |
135 | (DefaultMutableTreeNode)tse.getPath().getLastPathComponent(); |
136 | showChildren(node); |
137 | setFileDetails((File)node.getUserObject()); |
138 | } |
139 | }; |
140 | |
141 | // show the file system roots. |
142 | File[] roots = fileSystemView.getRoots(); |
143 | for (File fileSystemRoot : roots) { |
144 | DefaultMutableTreeNode node = new DefaultMutableTreeNode(fileSystemRoot); |
145 | root.add( node ); |
146 | File[] files = fileSystemView.getFiles(fileSystemRoot, true); |
147 | for (File file : files) { |
148 | if (file.isDirectory()) { |
149 | node.add(new DefaultMutableTreeNode(file)); |
150 | } |
151 | } |
152 | // |
153 | } |
154 | |
155 | tree = new JTree(treeModel); |
156 | tree.setRootVisible(false); |
157 | tree.addTreeSelectionListener(treeSelectionListener); |
158 | tree.setCellRenderer(new FileTreeCellRenderer()); |
159 | tree.expandRow(0); |
160 | JScrollPane treeScroll = new JScrollPane(tree); |
161 | |
162 | // as per trashgod tip |
163 | tree.setVisibleRowCount(15); |
164 | |
165 | Dimension preferredSize = treeScroll.getPreferredSize(); |
166 | Dimension widePreferred = new Dimension( |
167 | 200, |
168 | (int)preferredSize.getHeight()); |
169 | treeScroll.setPreferredSize( widePreferred ); |
170 | |
171 | // details for a File |
172 | JPanel fileMainDetails = new JPanel(new BorderLayout(4,2)); |
173 | fileMainDetails.setBorder(new EmptyBorder(0,6,0,6)); |
174 | |
175 | JPanel fileDetailsLabels = new JPanel(new GridLayout(0,1,2,2)); |
176 | fileMainDetails.add(fileDetailsLabels, BorderLayout.WEST); |
177 | |
178 | JPanel fileDetailsValues = new JPanel(new GridLayout(0,1,2,2)); |
179 | fileMainDetails.add(fileDetailsValues, BorderLayout.CENTER); |
180 | |
181 | fileDetailsLabels.add(new JLabel("File", JLabel.TRAILING)); |
182 | fileName = new JLabel(); |
183 | fileDetailsValues.add(fileName); |
184 | fileDetailsLabels.add(new JLabel("Path/name", JLabel.TRAILING)); |
185 | path = new JTextField(5); |
186 | path.setEditable(false); |
187 | fileDetailsValues.add(path); |
188 | fileDetailsLabels.add(new JLabel("Last Modified", JLabel.TRAILING)); |
189 | date = new JLabel(); |
190 | fileDetailsValues.add(date); |
191 | fileDetailsLabels.add(new JLabel("File size", JLabel.TRAILING)); |
192 | size = new JLabel(); |
193 | fileDetailsValues.add(size); |
194 | fileDetailsLabels.add(new JLabel("Type", JLabel.TRAILING)); |
195 | |
196 | JPanel flags = new JPanel(new FlowLayout(FlowLayout.LEADING,4,0)); |
197 | |
198 | isDirectory = new JRadioButton("Directory"); |
199 | flags.add(isDirectory); |
200 | |
201 | isFile = new JRadioButton("File"); |
202 | flags.add(isFile); |
203 | fileDetailsValues.add(flags); |
204 | |
205 | JToolBar toolBar = new JToolBar(); |
206 | // mnemonics stop working in a floated toolbar |
207 | toolBar.setFloatable(false); |
208 | |
209 | JButton locateFile = new JButton("Locate"); |
210 | locateFile.setMnemonic('l'); |
211 | |
212 | locateFile.addActionListener(new ActionListener(){ |
213 | public void actionPerformed(ActionEvent ae) { |
214 | try { |
215 | System.out.println("Locate: " + currentFile.getParentFile()); |
216 | desktop.open(currentFile.getParentFile()); |
217 | } catch(Throwable t) { |
218 | showThrowable(t); |
219 | } |
220 | gui.repaint(); |
221 | } |
222 | }); |
223 | toolBar.add(locateFile); |
224 | |
225 | openFile = new JButton("Open"); |
226 | openFile.setMnemonic('o'); |
227 | |
228 | openFile.addActionListener(new ActionListener(){ |
229 | public void actionPerformed(ActionEvent ae) { |
230 | try { |
231 | System.out.println("Open: " + currentFile); |
232 | desktop.open(currentFile); |
233 | } catch(Throwable t) { |
234 | showThrowable(t); |
235 | } |
236 | gui.repaint(); |
237 | } |
238 | }); |
239 | toolBar.add(openFile); |
240 | |
241 | editFile = new JButton("Edit"); |
242 | editFile.setMnemonic('e'); |
243 | editFile.addActionListener(new ActionListener(){ |
244 | public void actionPerformed(ActionEvent ae) { |
245 | try { |
246 | desktop.edit(currentFile); |
247 | } catch(Throwable t) { |
248 | showThrowable(t); |
249 | } |
250 | } |
251 | }); |
252 | toolBar.add(editFile); |
253 | |
254 | printFile = new JButton("Print"); |
255 | printFile.setMnemonic('p'); |
256 | printFile.addActionListener(new ActionListener(){ |
257 | public void actionPerformed(ActionEvent ae) { |
258 | try { |
259 | desktop.print(currentFile); |
260 | } catch(Throwable t) { |
261 | showThrowable(t); |
262 | } |
263 | } |
264 | }); |
265 | toolBar.add(printFile); |
266 | |
267 | // Check the actions are supported on this platform! |
268 | openFile.setEnabled(desktop.isSupported(Desktop.Action.OPEN)); |
269 | editFile.setEnabled(desktop.isSupported(Desktop.Action.EDIT)); |
270 | printFile.setEnabled(desktop.isSupported(Desktop.Action.PRINT)); |
271 | |
272 | flags.add(new JLabel(":: Flags")); |
273 | readable = new JCheckBox("Read "); |
274 | readable.setMnemonic('a'); |
275 | flags.add(readable); |
276 | |
277 | writable = new JCheckBox("Write "); |
278 | writable.setMnemonic('w'); |
279 | flags.add(writable); |
280 | |
281 | executable = new JCheckBox("Execute"); |
282 | executable.setMnemonic('x'); |
283 | flags.add(executable); |
284 | |
285 | int count = fileDetailsLabels.getComponentCount(); |
286 | for (int ii=0; ii<count; ii++) { |
287 | fileDetailsLabels.getComponent(ii).setEnabled(false); |
288 | } |
289 | |
290 | count = flags.getComponentCount(); |
291 | for (int ii=0; ii<count; ii++) { |
292 | flags.getComponent(ii).setEnabled(false); |
293 | } |
294 | |
295 | JPanel fileView = new JPanel(new BorderLayout(3,3)); |
296 | |
297 | fileView.add(toolBar,BorderLayout.NORTH); |
298 | fileView.add(fileMainDetails,BorderLayout.CENTER); |
299 | |
300 | detailView.add(fileView, BorderLayout.SOUTH); |
301 | |
302 | JSplitPane splitPane = new JSplitPane( |
303 | JSplitPane.HORIZONTAL_SPLIT, |
304 | treeScroll, |
305 | detailView); |
306 | gui.add(splitPane, BorderLayout.CENTER); |
307 | |
308 | JPanel simpleOutput = new JPanel(new BorderLayout(3,3)); |
309 | progressBar = new JProgressBar(); |
310 | simpleOutput.add(progressBar, BorderLayout.EAST); |
311 | progressBar.setVisible(false); |
312 | |
313 | gui.add(simpleOutput, BorderLayout.SOUTH); |
314 | |
315 | } |
316 | return gui; |
317 | } |
318 | |
319 | public void showRootFile() { |
320 | // ensure the main files are displayed |
321 | tree.setSelectionInterval(0,0); |
322 | } |
323 | |
324 | private TreePath findTreePath(File find) { |
325 | for (int ii=0; ii<tree.getRowCount(); ii++) { |
326 | TreePath treePath = tree.getPathForRow(ii); |
327 | Object object = treePath.getLastPathComponent(); |
328 | DefaultMutableTreeNode node = (DefaultMutableTreeNode)object; |
329 | File nodeFile = (File)node.getUserObject(); |
330 | |
331 | if (nodeFile==find) { |
332 | return treePath; |
333 | } |
334 | } |
335 | // not found! |
336 | return null; |
337 | } |
338 | |
339 | private void showErrorMessage(String errorMessage, String errorTitle) { |
340 | JOptionPane.showMessageDialog( |
341 | gui, |
342 | errorMessage, |
343 | errorTitle, |
344 | JOptionPane.ERROR_MESSAGE |
345 | ); |
346 | } |
347 | |
348 | private void showThrowable(Throwable t) { |
349 | t.printStackTrace(); |
350 | JOptionPane.showMessageDialog( |
351 | gui, |
352 | t.toString(), |
353 | t.getMessage(), |
354 | JOptionPane.ERROR_MESSAGE |
355 | ); |
356 | gui.repaint(); |
357 | } |
358 | |
359 | /** Update the table on the EDT */ |
360 | private void setTableData(final File[] files) { |
361 | SwingUtilities.invokeLater(new Runnable() { |
362 | public void run() { |
363 | if (fileTableModel==null) { |
364 | fileTableModel = new FileTableModel(); |
365 | table.setModel(fileTableModel); |
366 | } |
367 | table.getSelectionModel().removeListSelectionListener(listSelectionListener); |
368 | fileTableModel.setFiles(files); |
369 | table.getSelectionModel().addListSelectionListener(listSelectionListener); |
370 | if (!cellSizesSet) { |
371 | Icon icon = fileSystemView.getSystemIcon(files[0]); |
372 | |
373 | // size adjustment to better account for icons |
374 | table.setRowHeight( icon.getIconHeight()+rowIconPadding ); |
375 | |
376 | setColumnWidth(0,-1); |
377 | setColumnWidth(3,60); |
378 | table.getColumnModel().getColumn(3).setMaxWidth(120); |
379 | setColumnWidth(4,-1); |
380 | setColumnWidth(5,-1); |
381 | setColumnWidth(6,-1); |
382 | setColumnWidth(7,-1); |
383 | setColumnWidth(8,-1); |
384 | setColumnWidth(9,-1); |
385 | |
386 | cellSizesSet = true; |
387 | } |
388 | } |
389 | }); |
390 | } |
391 | |
392 | private void setColumnWidth(int column, int width) { |
393 | TableColumn tableColumn = table.getColumnModel().getColumn(column); |
394 | if (width<0) { |
395 | // use the preferred width of the header.. |
396 | JLabel label = new JLabel( (String)tableColumn.getHeaderValue() ); |
397 | Dimension preferred = label.getPreferredSize(); |
398 | // altered 10->14 as per camickr comment. |
399 | width = (int)preferred.getWidth()+14; |
400 | } |
401 | tableColumn.setPreferredWidth(width); |
402 | tableColumn.setMaxWidth(width); |
403 | tableColumn.setMinWidth(width); |
404 | } |
405 | |
406 | /** Add the files that are contained within the directory of this node. |
407 | Thanks to Hovercraft Full Of Eels for the SwingWorker fix. */ |
408 | private void showChildren(final DefaultMutableTreeNode node) { |
409 | tree.setEnabled(false); |
410 | progressBar.setVisible(true); |
411 | progressBar.setIndeterminate(true); |
412 | |
413 | SwingWorker<Void, File> worker = new SwingWorker<Void, File>() { |
414 | @Override |
415 | public Void doInBackground() { |
416 | File file = (File) node.getUserObject(); |
417 | if (file.isDirectory()) { |
418 | File[] files = fileSystemView.getFiles(file, true); //!! |
419 | if (node.isLeaf()) { |
420 | for (File child : files) { |
421 | if (child.isDirectory()) { |
422 | publish(child); |
423 | } |
424 | } |
425 | } |
426 | setTableData(files); |
427 | } |
428 | return null; |
429 | } |
430 | |
431 | @Override |
432 | protected void process(List<File> chunks) { |
433 | for (File child : chunks) { |
434 | node.add(new DefaultMutableTreeNode(child)); |
435 | } |
436 | } |
437 | |
438 | @Override |
439 | protected void done() { |
440 | progressBar.setIndeterminate(false); |
441 | progressBar.setVisible(false); |
442 | tree.setEnabled(true); |
443 | } |
444 | }; |
445 | worker.execute(); |
446 | } |
447 | |
448 | /** Update the File details view with the details of this File. */ |
449 | private void setFileDetails(File file) { |
450 | currentFile = file; |
451 | Icon icon = fileSystemView.getSystemIcon(file); |
452 | fileName.setIcon(icon); |
453 | fileName.setText(fileSystemView.getSystemDisplayName(file)); |
454 | path.setText(file.getPath()); |
455 | date.setText(new Date(file.lastModified()).toString()); |
456 | size.setText(file.length() + " bytes"); |
457 | readable.setSelected(file.canRead()); |
458 | writable.setSelected(file.canWrite()); |
459 | executable.setSelected(file.canExecute()); |
460 | isDirectory.setSelected(file.isDirectory()); |
461 | |
462 | isFile.setSelected(file.isFile()); |
463 | |
464 | JFrame f = (JFrame)gui.getTopLevelAncestor(); |
465 | if (f!=null) { |
466 | f.setTitle( |
467 | APP_TITLE + |
468 | " :: " + |
469 | fileSystemView.getSystemDisplayName(file) ); |
470 | } |
471 | |
472 | gui.repaint(); |
473 | } |
474 | |
475 | public static void main(String[] args) { |
476 | SwingUtilities.invokeLater(new Runnable() { |
477 | public void run() { |
478 | /*try { |
479 | // Significantly improves the look of the output in |
480 | // terms of the file names returned by FileSystemView! |
481 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); |
482 | } catch(Exception weTried) { |
483 | }*/ |
484 | JFrame f = new JFrame(APP_TITLE); |
485 | f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
486 | |
487 | FileBrowser FileBrowser = new FileBrowser(); |
488 | f.setContentPane(FileBrowser.getGui()); |
489 | |
490 | try { |
491 | URL urlBig = FileBrowser.getClass().getResource("fb-icon-32x32.png"); |
492 | URL urlSmall = FileBrowser.getClass().getResource("fb-icon-16x16.png"); |
493 | ArrayList<Image> images = new ArrayList<Image>(); |
494 | images.add( ImageIO.read(urlBig) ); |
495 | images.add( ImageIO.read(urlSmall) ); |
496 | f.setIconImages(images); |
497 | } catch(Exception weTried) {} |
498 | |
499 | f.pack(); |
500 | f.setLocationByPlatform(true); |
501 | f.setMinimumSize(f.getSize()); |
502 | f.setVisible(true); |
503 | |
504 | FileBrowser.showRootFile(); |
505 | } |
506 | }); |
507 | } |
508 | } |
509 | |
510 | /** A TableModel to hold File[]. */ |
511 | sclass FileTableModel extends AbstractTableModel { |
512 | |
513 | private File[] files; |
514 | private FileSystemView fileSystemView = FileSystemView.getFileSystemView(); |
515 | private String[] columns = { |
516 | "Icon", |
517 | "File", |
518 | "Path/name", |
519 | "Size", |
520 | "Last Modified", |
521 | "R", |
522 | "W", |
523 | "E", |
524 | "D", |
525 | "F", |
526 | }; |
527 | |
528 | FileTableModel() { |
529 | this(new File[0]); |
530 | } |
531 | |
532 | FileTableModel(File[] files) { |
533 | this.files = files; |
534 | } |
535 | |
536 | public Object getValueAt(int row, int column) { |
537 | File file = files[row]; |
538 | switch (column) { |
539 | case 0: |
540 | return fileSystemView.getSystemIcon(file); |
541 | case 1: |
542 | return fileSystemView.getSystemDisplayName(file); |
543 | case 2: |
544 | return file.getPath(); |
545 | case 3: |
546 | return file.length(); |
547 | case 4: |
548 | return file.lastModified(); |
549 | case 5: |
550 | return file.canRead(); |
551 | case 6: |
552 | return file.canWrite(); |
553 | case 7: |
554 | return file.canExecute(); |
555 | case 8: |
556 | return file.isDirectory(); |
557 | case 9: |
558 | return file.isFile(); |
559 | default: |
560 | System.err.println("Logic Error"); |
561 | } |
562 | return ""; |
563 | } |
564 | |
565 | public int getColumnCount() { |
566 | return columns.length; |
567 | } |
568 | |
569 | public Class<?> getColumnClass(int column) { |
570 | switch (column) { |
571 | case 0: |
572 | return ImageIcon.class; |
573 | case 3: |
574 | return Long.class; |
575 | case 4: |
576 | return Date.class; |
577 | case 5: |
578 | case 6: |
579 | case 7: |
580 | case 8: |
581 | case 9: |
582 | return Boolean.class; |
583 | } |
584 | return String.class; |
585 | } |
586 | |
587 | public String getColumnName(int column) { |
588 | return columns[column]; |
589 | } |
590 | |
591 | public int getRowCount() { |
592 | return files.length; |
593 | } |
594 | |
595 | public File getFile(int row) { |
596 | return files[row]; |
597 | } |
598 | |
599 | public void setFiles(File[] files) { |
600 | this.files = files; |
601 | fireTableDataChanged(); |
602 | } |
603 | } |
604 | |
605 | /** A TreeCellRenderer for a File. */ |
606 | sclass FileTreeCellRenderer extends DefaultTreeCellRenderer { |
607 | |
608 | private FileSystemView fileSystemView; |
609 | |
610 | private JLabel label; |
611 | |
612 | FileTreeCellRenderer() { |
613 | label = new JLabel(); |
614 | label.setOpaque(true); |
615 | fileSystemView = FileSystemView.getFileSystemView(); |
616 | } |
617 | |
618 | @Override |
619 | public Component getTreeCellRendererComponent( |
620 | JTree tree, |
621 | Object value, |
622 | boolean selected, |
623 | boolean expanded, |
624 | boolean leaf, |
625 | int row, |
626 | boolean hasFocus) { |
627 | |
628 | DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; |
629 | File file = (File)node.getUserObject(); |
630 | label.setIcon(fileSystemView.getSystemIcon(file)); |
631 | label.setText(fileSystemView.getSystemDisplayName(file)); |
632 | label.setToolTipText(file.getPath()); |
633 | |
634 | if (selected) { |
635 | label.setBackground(backgroundSelectionColor); |
636 | label.setForeground(textSelectionColor); |
637 | } else { |
638 | label.setBackground(backgroundNonSelectionColor); |
639 | label.setForeground(textNonSelectionColor); |
640 | } |
641 | |
642 | return label; |
643 | } |
644 | } |
download show line numbers debug dex old transpilations
Travelled to 14 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1006655 |
Snippet name: | FileBro [works, but interface has little quirks] |
Eternal ID of this version: | #1006655/1 |
Text MD5: | 24d2b76fe5c159b8eabd567a8322b743 |
Transpilation MD5: | 00949d6518c9cb43b557cd53d048184f |
Author: | stefan |
Category: | javax / gui |
Type: | JavaX source code |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2017-01-31 05:56:21 |
Source code size: | 22138 bytes / 644 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 485 / 621 |
Referenced in: | [show references] |