View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.gui;
21  
22  import java.awt.BorderLayout;
23  import java.awt.Color;
24  import java.awt.Insets;
25  import java.awt.event.ActionEvent;
26  import java.awt.event.ActionListener;
27  import java.awt.event.KeyAdapter;
28  import java.awt.event.KeyEvent;
29  import java.io.File;
30  import java.io.IOException;
31  import java.util.Arrays;
32  import java.util.List;
33  import java.util.Vector;
34  import javax.swing.AbstractAction;
35  import javax.swing.Action;
36  import javax.swing.Box;
37  import javax.swing.BoxLayout;
38  import javax.swing.DefaultCellEditor;
39  import javax.swing.JButton;
40  import javax.swing.JFileChooser;
41  import javax.swing.JLabel;
42  import javax.swing.JPanel;
43  import javax.swing.JPopupMenu;
44  import javax.swing.JScrollBar;
45  import javax.swing.JScrollPane;
46  import javax.swing.JSplitPane;
47  import javax.swing.JTable;
48  import javax.swing.JTextField;
49  import javax.swing.KeyStroke;
50  import javax.swing.border.EmptyBorder;
51  import javax.swing.event.ListSelectionEvent;
52  import javax.swing.event.ListSelectionListener;
53  import javax.swing.event.TreeSelectionEvent;
54  import javax.swing.event.TreeSelectionListener;
55  import org.apache.log4j.Logger;
56  import org.xnap.XNap;
57  import org.xnap.action.AbstractTogglePrefAction;
58  import org.xnap.action.AbstractXNapAction;
59  import org.xnap.gui.action.CopyFileAction;
60  import org.xnap.gui.action.CutFileAction;
61  import org.xnap.gui.action.EnqueueFileAction;
62  import org.xnap.gui.action.EraseAction;
63  import org.xnap.gui.action.OpenFileAction;
64  import org.xnap.gui.action.PasteFileAction;
65  import org.xnap.gui.action.PlayFileAction;
66  import org.xnap.gui.action.StopPlayerAction;
67  import org.xnap.gui.component.FileCompletionModel;
68  import org.xnap.gui.component.HistoryComboBox;
69  import org.xnap.gui.component.XNapButton;
70  import org.xnap.gui.component.XNapMenuItem;
71  import org.xnap.gui.component.XNapTextField;
72  import org.xnap.gui.event.DoubleClickListener;
73  import org.xnap.gui.event.PopupListener;
74  import org.xnap.gui.menu.OpenFileWithMenu;
75  import org.xnap.gui.menu.ViewerMenu;
76  import org.xnap.gui.shortcut.ShortcutManager;
77  import org.xnap.gui.table.LibraryTableModel;
78  import org.xnap.gui.tree.LibraryTree;
79  import org.xnap.gui.util.DragFilesSupport;
80  import org.xnap.gui.util.FocusHandler;
81  import org.xnap.gui.util.GUIHelper;
82  import org.xnap.gui.util.IconHelper;
83  import org.xnap.gui.util.SwingSynchronizedTask;
84  import org.xnap.gui.viewer.Viewer;
85  import org.xnap.gui.viewer.ViewerFrontend;
86  import org.xnap.gui.viewer.ViewerManager;
87  import org.xnap.io.Library;
88  import org.xnap.util.FileHelper;
89  import org.xnap.util.Formatter;
90  import org.xnap.util.Preferences;
91  import org.xnap.util.Scheduler;
92  
93  public class LibraryPanel extends JPanel 
94      implements ActionProvider, TreeSelectionListener, ListSelectionListener,
95  			   DirectoryProvider, FileProvider, ViewerFrontend
96  {
97      
98      //--- Constant(s) ---
99      
100     public static final String HISTORY_FILENAME 
101 		= FileHelper.getHomeDir() + "repository_search_history";
102 
103     //--- Data field(s) ---
104 
105     private Preferences prefs = Preferences.getInstance();
106 
107     private LibraryTree lTree;
108     private JTable jta;
109     private JFileChooser jfc = null;
110     private LibraryTableModel ltm;
111     private DragFilesSupport dfs;
112 
113     private JSplitPane jspH;
114     private JSplitPane jspV;
115 
116 	private JPanel jpURL;
117 	private XNapTextField jtfURL;
118 	private JPanel jpSearch;
119     private HistoryComboBox jcSearch;
120 
121     // preview
122     private JPanel jpPreview;
123     private JScrollPane jspPreview;
124     private JLabel jlStatus;
125 
126     private PlayFileAction playAction = new PlayFileAction(this);
127     private RefreshAction refreshAction = new RefreshAction();
128     private StopPlayerAction stopAction = new StopPlayerAction();
129     private EnqueueFileAction enqueueAction = new EnqueueFileAction(this);
130     
131     private OpenFileAction acOpenFile = new OpenFileAction(this);
132     private DeleteAction deleteAction = new DeleteAction();
133     private RenameAction renameAction = new RenameAction();
134 
135     private CopyFileAction acCopyFile = new CopyFileAction(this);
136     private CutFileAction acCutFile = new CutFileAction(this);
137     private PasteFileAction acPasteFile = new PasteFileAction(this);
138 
139 	private ShowURLAction acShowURL = new ShowURLAction();
140 	private ShowSearchAction acShowSearch = new ShowSearchAction();
141 
142     private File currentDir;
143 	private boolean delayNextViewer;
144 
145 	private Viewer currentViewer;
146 	
147 	private static Logger logger = Logger.getLogger(LibraryPanel.class);
148 
149     //--- Constructor(s) ---
150 
151     public LibraryPanel() 
152     {
153 		initialize();
154     }
155 
156     //--- Method(s) ---
157 
158     private void initialize() 
159     {
160 		// url panel
161 		Box bxURL = new Box(BoxLayout.X_AXIS);
162 
163 		jtfURL = new XNapTextField(20);
164 		jtfURL.setCompletionModel(new FileCompletionModel());
165 		jtfURL.setPreferences("libraryURL");
166 
167 		Action acEraseURL = new EraseAction(jtfURL);
168 		JButton jbEraseURL = new JButton(acEraseURL);
169 		jbEraseURL.setAlignmentY(0.5f);
170 		jbEraseURL.setMargin(new Insets(1, 1, 1, 1));
171 		bxURL.add(jbEraseURL);
172 	
173 		bxURL.add(new JLabel(XNap.tr("Location", 1)));
174 		//Action acQueryURL = new QueryURLAction();
175 		//GUIHelper.bindEnterKey(jtfURL, acQueryURL);
176 		bxURL.add(jtfURL);
177 
178 		jpURL = new JPanel(new BorderLayout());
179 		jpURL.setVisible(prefs.getBoolean("showURLInLibraryPanel"));
180 		jpURL.setBorder(new EmptyBorder(2, 2, 2, 2));
181 		jpURL.add(bxURL, BorderLayout.CENTER);
182 		acShowURL.setPanel(jpURL);
183 
184 		// search panel
185 		Box bxSearch = new Box(BoxLayout.X_AXIS);
186 		Action acQuery = new QueryAction();
187 
188 		EraseAction acErase = new EraseAction();
189 		ShortcutManager.getInstance().add(acErase, this);
190 		JButton jbErase = new JButton(acErase);
191 		jbErase.setMargin(new Insets(1, 1, 1, 1));
192 		jbErase.setAlignmentY(0.5f);
193 		bxSearch.add(jbErase);
194 	
195 		JLabel searchLabel = new JLabel(XNap.tr("Library Search", 1));
196 		bxSearch.add(searchLabel);
197 
198 		jcSearch = new HistoryComboBox(readHistoryFile(), 20);
199 		searchLabel.setLabelFor(jcSearch.getTextField());
200 		jcSearch.setPreferences("libararySearch");
201 		GUIHelper.bindEnterKeyLocally(jcSearch.getTextField(), acQuery);
202 
203 		bxSearch.add(jcSearch);
204 		acErase.setTextField(jcSearch.getTextField());
205 		    
206 		XNapButton jbQuery = new XNapButton(acQuery);
207 		bxSearch.add(jbQuery);
208 
209 		jpSearch = new JPanel(new BorderLayout());
210 		jpSearch.setVisible(prefs.getBoolean("showSearchInLibraryPanel"));
211 		//jpSearch.setBorder(GUIHelper.createDefaultBorder(XNap.tr("Library Search")));
212 		jpSearch.setBorder(new EmptyBorder(2, 2, 2, 2));
213 		jpSearch.add(bxSearch, BorderLayout.CENTER);
214 		acShowSearch.setPanel(jpSearch);
215 
216 		// top
217 		Box bxTop = new Box(BoxLayout.Y_AXIS);
218 		bxTop.add(jpURL);
219 		bxTop.add(jpSearch);
220 
221 		// tree
222 		lTree = new LibraryTree(this);
223 		lTree.addTreeSelectionListener(this);
224 	
225         JScrollPane jspTree = new JScrollPane();
226         jspTree.setViewportView(lTree);
227 	
228 		// table context menu
229 		JPopupMenu popup = new JPopupMenu();
230 		popup.add(new XNapMenuItem(playAction));
231 		popup.add(new XNapMenuItem(enqueueAction));
232 		popup.addSeparator();
233 		popup.add(new XNapMenuItem(acOpenFile));
234 		popup.add(new OpenFileWithMenu(this));
235 		popup.addSeparator();
236 		popup.add(new ViewerMenu(this, this));
237 		popup.addSeparator();
238 		popup.add(new XNapMenuItem(acCopyFile));
239 		popup.add(new XNapMenuItem(acCutFile));
240 		popup.add(new XNapMenuItem(acPasteFile));
241 		popup.addSeparator();
242 		popup.add(new XNapMenuItem(renameAction));
243 		popup.add(new XNapMenuItem(deleteAction));
244 
245 		// table
246         ltm = new LibraryTableModel();
247 		jta = ltm.createTable(Preferences.getInstance(), "library");
248 		dfs = new DragFilesSupport(jta, this);
249 		jta.getSelectionModel().addListSelectionListener(this);
250 		jta.setShowGrid(false);
251 		jta.addKeyListener(new ArrowKeyListener());
252 
253 		jta.addMouseListener(new PopupListener(popup));
254 		DoubleClickListener.install(jta, acOpenFile);
255 
256 		GUIHelper.bindEmacsKeysToTable(jta);
257 
258 		// new shortcuts stuff
259 		ShortcutManager.getInstance().add(renameAction, jta);
260 		ShortcutManager.getInstance().add(deleteAction, jta);
261 		ShortcutManager.getInstance().add(refreshAction, this);
262 
263 		ShortcutManager.getInstance().add(enqueueAction, jta);
264 		ShortcutManager.getInstance().add(playAction, jta);
265 		ShortcutManager.getInstance().add(acOpenFile, jta);
266 	
267 		//  jta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
268 //  							  acOpenFile);
269 //  		jta.getActionMap().put(acOpenFile, acOpenFile);
270 	
271 //  		jta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0),
272 //  							  enqueueAction);
273 //  		jta.getActionMap().put(enqueueAction, enqueueAction);
274 	
275 		JScrollPane jspTable = new JScrollPane();
276 		jspTable.setViewportView(jta);
277 	
278 		// preview panel
279 		jpPreview = new JPanel(new BorderLayout());
280 		jpPreview.setBackground(Color.gray);
281 	
282 		jspPreview = new JScrollPane(jpPreview);
283 
284 		ActionListener al = jspPreview.getActionForKeyStroke
285 			(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0));
286 		jspPreview.registerKeyboardAction
287 			(al, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.CTRL_MASK),
288 			 WHEN_IN_FOCUSED_WINDOW);
289 	
290 		al = jspPreview.getActionForKeyStroke
291 			(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0));
292 		jspPreview.registerKeyboardAction
293 			(al, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.CTRL_MASK 
294 										| KeyEvent.SHIFT_MASK),
295 			 WHEN_IN_FOCUSED_WINDOW);
296 	
297 		JScrollBar jsb = jspPreview.getVerticalScrollBar();
298 		jsb.setToolTipText(GUIHelper.tt(XNap.tr("Ctrl+Space to scroll down<br>Ctrl+Shift+Space to scroll up")));
299 	
300 		// split panels
301 		jspV = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
302         jspV.add(jspTable, JSplitPane.TOP);
303         jspV.add(jspPreview, JSplitPane.BOTTOM);
304         jspV.setDividerLocation(prefs.getLibraryVerticalDividerLocation());
305 		jspV.setOneTouchExpandable(true);
306 		jspV.setBorder(GUIHelper.createEmptyBorder());
307 
308 		jlStatus = new JLabel(" ");
309 	
310 		JPanel jpV = new JPanel(new BorderLayout());
311 		jpV.add(jspV, "Center");
312 		jpV.add(jlStatus, "South");
313 	
314 		jspH = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
315         jspH.add(jspTree, JSplitPane.LEFT);
316         jspH.add(jpV, JSplitPane.RIGHT);
317 		jspH.setDividerLocation(prefs.getLibraryHorizontalDividerLocation());
318 		jspH.setOneTouchExpandable(true);
319 		jspH.setBorder(GUIHelper.createEmptyBorder());
320 		
321 		// focus listener
322 		addComponentListener(new FocusHandler(jcSearch.getTextField()));
323 
324 		// content 
325 		setLayout(new BorderLayout());
326 		add(bxTop, BorderLayout.NORTH);
327 		add(jspH, BorderLayout.CENTER);
328 
329 		GUIHelper.setMnemonics(this);
330     }
331 	
332 	private void initializeShortcuts()
333 	{
334 		ShortcutManager sm = ShortcutManager.getInstance();
335 	}
336 
337 	public Action[] getActions()
338 	{
339 		return new Action[] { playAction, enqueueAction, stopAction, null,
340 							  refreshAction, null, 
341 							  acShowURL, acShowSearch, };
342 	}
343     
344     public void savePrefs()
345     {	
346 		prefs.setLibraryHorizontalDividerLocation(jspH.getDividerLocation());
347 		prefs.setLibraryVerticalDividerLocation(jspV.getDividerLocation());
348 		writeHistoryFile();
349     }
350 
351 	private void writeHistoryFile()
352 	{
353 		List l = Arrays.asList(jcSearch.getHistoryItems());
354 		try {
355 			FileHelper.writeBinary(new File(HISTORY_FILENAME), l);
356 		}
357 		catch (IOException ie) {
358 			logger.debug("Error writing history file", ie);
359 		}
360 	}
361 
362 	private Vector readHistoryFile()
363 	{
364 		Vector v = new Vector();
365 		try {
366 			FileHelper.readBinary(new File(HISTORY_FILENAME), v);
367 		}
368 		catch (IOException ie) {
369 			logger.debug("Error reading history file" , ie);
370 		}
371 		return v;
372 	}
373 
374     private void showDirectory(File f)
375     {
376 		jtfURL.setText(f.getAbsolutePath());
377 
378 		currentDir = f;
379 		ltm.clear();
380 		// check if directory is readable by xnap user
381 		if (f.canRead()) {
382 			ltm.add(f);
383 			ltm.resort();
384 		}
385 		resetPanel();
386     }
387 
388     private void showFiles(File[] files) 
389     {
390 		ltm.clear();
391 		ltm.add(files);
392 		ltm.resort();
393 		resetPanel();
394     }
395 
396     /***
397      * Called when a directory has been selected.
398      */
399     public void valueChanged(TreeSelectionEvent e)
400     {
401 		Object target = e.getPath().getLastPathComponent();
402 	
403 		if (target instanceof File) {
404 			File f = (File)target;
405 			showDirectory(f);
406 		}
407 		else {
408 			jtfURL.setText(null);
409 		}
410     }
411 
412     /*** 
413      * Called when the table's selection changes, we check what kind of
414      * file was selected and call the respective viewer plugin if available.
415      */
416     public void valueChanged(ListSelectionEvent e)
417     {
418 		/* user is about to select some files, don't show anything */
419 		if (jta.getSelectedRowCount() > 1) {
420 			resetPanel();
421 			return;
422 		}
423 
424 		int row = jta.getSelectedRow();
425 		if (row != -1) {
426 			if (delayNextViewer) {
427 				delayNextViewer = false;
428 				Scheduler.run(500, new DelayedViewerTask(row));
429 			}
430 			else {
431 				display(ltm.get(row));
432 			}
433 		}
434     }
435 
436     /***
437      * Implements the {@link DirectoryProvider} interface delegating the
438      * request to the {@link LibraryTree}.
439      */
440     public File getDirectory()
441     {
442 		return lTree.getDirectory();
443     }
444     
445     /***
446      * Implements the {@link DirectoryProvider#hasChanged}.
447      */
448     public void hasChanged(File directory)
449     {
450     	if (currentDir != null) {
451     		showDirectory(currentDir);
452 		}
453     }
454 
455     private File[] getFiles(boolean showMessage)
456     {
457 		int rowC = jta.getSelectedRowCount();
458 		int rows[] = jta.getSelectedRows();
459 	
460 		if (showMessage && rowC == 0) {
461 			StatusBar.setText(XNap.tr("Please select something first"));
462 			return null;
463 		}
464 	
465 		File[] files = new File[rowC];
466 	
467 		for (int i = 0; i < rowC; i++) {
468 			files[i] = ltm.get(rows[i]);
469 		}
470 
471 		return files;
472     }
473 
474     /***
475      * Implements {@link FileProvider}.
476      */
477     public File[] getFiles()
478     {
479 		return getFiles(true);
480     }    
481 
482 	private void display(File file)
483 	{
484 		jtfURL.setText(file.getAbsolutePath());
485 
486 		if (currentViewer != null) {
487 			currentViewer.close();
488 		}
489 
490 		Viewer v = ViewerManager.getInstance().getDefaultViewer(file);
491 		if (v != null && file.canRead()) {
492 			display(v, file);
493 		}
494 		else {
495 			if (v != null) {
496 				StatusBar.setText
497 					(XNap.tr("You do not have permission to read file"));
498 			}
499 			jspPreview.setViewportView(jpPreview);
500 		}
501 	}
502 
503     /***
504      * Implements the {@link ViewerFrontend} interface.
505      */
506     public void display(Viewer viewer, File file)
507     {
508 		jspPreview.setViewportView(viewer.getComponent());
509 		currentViewer = viewer;
510 		viewer.open(file);
511     }
512     
513     private void resetPanel()
514     {
515 		jspPreview.setViewportView(jpPreview);
516 		updateStatus();
517     }
518 
519     /***
520      * Needed for properties serialization.
521      */
522     public int getHorizontalDividerLocation()
523     {
524 		return jspH.getDividerLocation();
525     }
526 
527     /***
528      * Needed for properties deserialization.
529      */
530     public void setHorizontalDividerLocation(int newValue)
531     {
532 		if (newValue == -1) {
533 			jspH.setDividerLocation(200);
534 		}
535 		else {
536 			jspH.setDividerLocation(newValue);
537 		}
538     }
539 
540     /***
541      * Needed for properties serialization.
542      */
543     public int getVerticalDividerLocation()
544     {
545 		return jspV.getDividerLocation();
546     }
547 
548     /***
549      * Needed for properties deserialization.
550      */
551     public void setVerticalDividerLocation(int newValue)
552     {
553 		if (newValue == -1) {
554 			jspV.setDividerLocation(200);
555 		}
556 		else {
557 			jspV.setDividerLocation(newValue);
558 		}
559     }
560 
561     /***
562      * Updates the status line below the file table.
563      */
564     private void updateStatus()
565     {
566 		StringBuffer sb = new StringBuffer();
567 		File[] files = getFiles(false);
568 		if (files.length == 0) {
569 			// nothing is selected
570 			sb.append(ltm.getRowCount());
571 			sb.append(" files - ");   
572 			sb.append(Formatter.formatSize(ltm.getTotalSize()));
573 		}
574 		else if (files.length == 1) {
575 			sb.append("1 file - ");
576 			sb.append(Formatter.formatNumber(files[0].length()) + " bytes");
577 		}
578 		else {
579 			long totalSize = 0;
580 			for (int i = 0; i < files.length; i++) {
581 				totalSize += files[i].length();
582 			}
583 			sb.append("selected - ");
584 			sb.append(files.length);
585 			sb.append(" files - ");   
586 			sb.append(Formatter.formatSize(totalSize));
587 		}
588 
589 		// FIX: we need some kind of padding
590 		sb.insert(0, " ");
591 		jlStatus.setText(sb.toString());
592     }
593 
594     /* inner classes */
595 
596     /***
597      * Refresh file and directory listings
598      */
599     private class RefreshAction extends AbstractAction 
600 	{
601 
602         public RefreshAction() 
603 		{
604             putValue(Action.NAME, XNap.tr("Refresh"));
605             putValue(Action.SHORT_DESCRIPTION,
606 					 XNap.tr("Refresh list of files"));
607 			putValue(IconHelper.XNAP_ICON, "reload.png");
608 			putValue(Action.ACTION_COMMAND_KEY, "libraryRefresh");
609 			putValue(AbstractXNapAction.DEFAULT_KEYSTROKE,
610 					 KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
611 			putValue(AbstractXNapAction.SHORTCUT_CATEGORY,
612 					 XNap.tr("Library"));
613         }
614 
615         public void actionPerformed(ActionEvent event) 
616 		{
617 			if (currentDir != null) {
618 				showDirectory(currentDir);
619 			}
620 		}
621     }
622 
623     private class DeleteAction extends AbstractAction
624     {
625 		public DeleteAction() 
626 		{
627             putValue(Action.NAME, XNap.tr("Delete"));
628             putValue(Action.SHORT_DESCRIPTION,
629 					 XNap.tr("Deletes selected file(s)."));
630 			putValue(IconHelper.XNAP_ICON, "editdelete.png");
631 			putValue(Action.ACTION_COMMAND_KEY, "libraryFileDelete");
632 			putValue(AbstractXNapAction.DEFAULT_KEYSTROKE,
633 					 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
634 			putValue(AbstractXNapAction.SHORTCUT_CATEGORY,
635 					 XNap.tr("Library"));
636         }
637 
638         public void actionPerformed(ActionEvent event) 
639 		{
640 			File[] files = getFiles();
641 			if (files == null)
642 				return;
643 	    
644 			files = Dialogs.showDeleteFilesDialog(LibraryPanel.this, files);
645 
646 			if (files != null) {
647 				for (int i = 0; i < files.length; i++) {
648 					if (!ltm.delete(files[i])) {
649 						StatusBar.setText(XNap.tr("Could not delete {0}",
650 												  files[i].getName()));
651 					}
652 				}
653 			}
654 		}
655     }
656 
657     private class RenameAction extends AbstractAction
658     {
659 		public RenameAction() 
660 		{
661             putValue(Action.NAME, XNap.tr("Rename"));
662             putValue(Action.SHORT_DESCRIPTION,
663 					 XNap.tr("Renames the selected file."));
664 			putValue(Action.ACTION_COMMAND_KEY, "libraryFileRename");
665 			putValue(AbstractXNapAction.DEFAULT_KEYSTROKE, 
666 					 KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
667 			putValue(AbstractXNapAction.SHORTCUT_CATEGORY,
668 					 XNap.tr("Library"));
669         }
670         
671 		public void actionPerformed(ActionEvent event) 
672 		{
673 			int i = jta.getSelectedRow();
674 			if (i != -1) {
675 				ltm.setCellEditable(i, LibraryTableModel.NAME);
676 				jta.editCellAt(i, LibraryTableModel.NAME);
677 				DefaultCellEditor dce = (DefaultCellEditor)jta.getCellEditor();
678 				JTextField jtf = (JTextField)dce.getComponent();
679 				//  jtf.setSelectionStart(0);
680 				//  jtf.setSelectionEnd(jtf.getText().length());
681 				jtf.grabFocus();
682 				jtf.setCaretPosition(0);
683 				ltm.setCellEditable(-1, -1);
684 			}
685 		}
686     }
687 
688     private class ShowSearchAction extends AbstractTogglePrefAction
689     {
690 		private JPanel jp;
691 
692 		public ShowSearchAction() 
693 		{
694 			super("showSearchInLibraryPanel");
695             putValue(Action.NAME, XNap.tr("Show Search Input"));
696 			putValue(IconHelper.XNAP_ICON, "filefind.png");
697             putValue(Action.SHORT_DESCRIPTION,
698 					 XNap.tr("Shows or hides the search field."));
699         }
700 
701 		public void setPanel(JPanel jp)
702 		{
703 			this.jp = jp;
704 		}
705         
706 		public void toggled(boolean visible) 
707 		{
708 			if (jp != null) {
709 				jp.setVisible(visible);
710 			}
711 		}
712     }
713 
714     private class ShowURLAction extends AbstractTogglePrefAction
715     {
716 		private JPanel jp;
717 
718 		public ShowURLAction() 
719 		{
720 			super("showURLInLibraryPanel");
721 
722             putValue(Action.NAME, XNap.tr("Show URL Input"));
723 			putValue(IconHelper.XNAP_ICON, "editclear.png");
724             putValue(Action.SHORT_DESCRIPTION,
725 					 XNap.tr("Shows or hides the URL field."));
726         }
727 
728 		public void setPanel(JPanel jp)
729 		{
730 			this.jp = jp;
731 		}
732         
733 		public void toggled(boolean visible) 
734 		{
735 			if (jp != null) {
736 				jp.setVisible(visible);
737 			}
738 		}
739     }
740 
741     /***
742      * Performs a search.
743      */
744     private class QueryAction extends AbstractAction
745     {
746 
747         public QueryAction() 
748 		{
749             putValue(Action.NAME, XNap.tr("Query"));
750             putValue(IconHelper.XNAP_ICON, "filefind.png");
751             putValue(Action.SHORT_DESCRIPTION, XNap.tr("Performs search."));
752         }
753 
754         public void actionPerformed(ActionEvent event) 
755 		{
756 			String s = jcSearch.getText().trim();
757 			if (s.length() > 0) {
758 				File[] files = Library.getInstance().search(s);
759 				if (files != null && files.length > 0) {
760 					lTree.clearSelection();
761 					showFiles(files);
762 					StatusBar.setText(XNap.tr("Found {0} files", 
763 											  new Integer(files.length)));
764 				}
765 				else {
766 					StatusBar.setText(XNap.tr("{0} not found in library", s));
767 				}
768 				jcSearch.addDistinctItemAtTop(s);
769 			}
770 			else {
771 				StatusBar.setText(XNap.tr("What are you trying to search for?"));
772 			}
773         }
774 
775     }
776 
777     private class DelayedViewerTask extends SwingSynchronizedTask
778     {
779 		private int showRow;
780 
781 		public DelayedViewerTask(int showRow)
782 		{
783 			this.showRow = showRow;
784 		}
785 
786 		public void runSynchronized()
787 		{
788 			if (jta.getSelectedRowCount() == 1 
789 				&& jta.getSelectedRow() == showRow) {
790 				display(ltm.get(showRow));
791 			}
792 		}
793     }
794 
795 	private class ArrowKeyListener extends KeyAdapter {
796 
797 		public void keyPressed(KeyEvent e) 
798 		{
799 			int c = e.getKeyCode();
800 			if (c == KeyEvent.VK_PAGE_DOWN || c == KeyEvent.VK_PAGE_UP 
801 				|| c == KeyEvent.VK_DOWN || c == KeyEvent.VK_UP) {
802 				delayNextViewer = true;
803 			}
804 		}
805 
806 	}
807 	
808 }