/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.ui.dialog.bookmark;
import com.mucommander.bookmark.Bookmark;
import com.mucommander.bookmark.BookmarkManager;
import com.mucommander.commons.collections.AlteredVector;
import com.mucommander.text.Translator;
import com.mucommander.ui.action.ActionProperties;
import com.mucommander.ui.action.impl.EditBookmarksAction;
import com.mucommander.ui.dialog.FocusDialog;
import com.mucommander.ui.helper.MnemonicHelper;
import com.mucommander.ui.layout.XAlignedComponentPanel;
import com.mucommander.ui.layout.XBoxPanel;
import com.mucommander.ui.layout.YBoxPanel;
import com.mucommander.ui.list.DynamicList;
import com.mucommander.ui.list.SortableListPanel;
import com.mucommander.ui.main.MainFrame;
import com.mucommander.ui.text.FilePathField;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* This dialog contains a list of all bookmarks and allows the user to edit, remove, duplicate, go to and reorder them.
*
* <p>If the contents of this list is modified, bookmarks will be saved to disk when this dialog is disposed.</p>
*
* @author Maxence Bernard
*/
public class EditBookmarksDialog extends FocusDialog implements ActionListener, ListSelectionListener, DocumentListener {
private MainFrame mainFrame;
private JButton newButton;
private JButton duplicateButton;
private JButton removeButton;
private JButton goToButton;
private JButton closeButton;
private JTextField nameField;
private JTextField locationField;
private AlteredVector<Bookmark> bookmarks;
private DynamicList<Bookmark> bookmarkList;
private int currentListIndex;
private Bookmark currentBookmarkSave;
private boolean ignoreDocumentListenerEvents;
// Dialog's size has to be at least 400x300
private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(440,330);
// Dialog's size has to be at most 600x400
private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(600,400);
public EditBookmarksDialog(MainFrame mainFrame) {
super(mainFrame, ActionProperties.getActionLabel(EditBookmarksAction.Descriptor.ACTION_ID), mainFrame);
this.mainFrame = mainFrame;
Container contentPane = getContentPane();
// Retrieve bookmarks list
this.bookmarks = BookmarkManager.getBookmarks();
// Temporarily suspend bookmark change events, otherwise an event would be fired for each character
// typed in the name / location fields. Events will be resumed when this dialog is disposed
BookmarkManager.setFireEvents(false);
// Create the sortable bookmarks list panel
SortableListPanel<Bookmark> listPanel = new SortableListPanel<Bookmark>(bookmarks);
this.bookmarkList = listPanel.getDynamicList();
contentPane.add(listPanel, BorderLayout.CENTER);
// Text fields panel
XAlignedComponentPanel compPanel = new XAlignedComponentPanel();
// Add bookmark name field
this.nameField = new JTextField();
nameField.getDocument().addDocumentListener(this);
compPanel.addRow(Translator.get("name")+":", nameField, 5);
// Create a path field with auto-completion capabilities
this.locationField = new FilePathField();
locationField.getDocument().addDocumentListener(this);
compPanel.addRow(Translator.get("location")+":", locationField, 10);
YBoxPanel yPanel = new YBoxPanel(10);
yPanel.add(compPanel);
// Add buttons: 'remove', 'move up' and 'move down' buttons are enabled
// only if there is at least one bookmark in the table
XBoxPanel buttonsPanel = new XBoxPanel();
JPanel buttonGroupPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
MnemonicHelper mnemonicHelper = new MnemonicHelper();
// New bookmark button
newButton = new JButton(Translator.get("edit_bookmarks_dialog.new"));
newButton.setMnemonic(mnemonicHelper.getMnemonic(newButton));
newButton.addActionListener(this);
buttonGroupPanel.add(newButton);
// Duplicate bookmark button
duplicateButton = new JButton(Translator.get("duplicate"));
duplicateButton.setMnemonic(mnemonicHelper.getMnemonic(duplicateButton));
duplicateButton.addActionListener(this);
buttonGroupPanel.add(duplicateButton);
// Remove bookmark button
removeButton = new JButton(bookmarkList.getRemoveAction());
removeButton.setMnemonic(mnemonicHelper.getMnemonic(removeButton));
buttonGroupPanel.add(removeButton);
// Go to bookmark button
goToButton = new JButton(Translator.get("go_to"));
goToButton.setMnemonic(mnemonicHelper.getMnemonic(goToButton));
goToButton.addActionListener(this);
buttonGroupPanel.add(goToButton);
buttonsPanel.add(buttonGroupPanel);
// Button that closes the window
closeButton = new JButton(Translator.get("close"));
closeButton.setMnemonic(mnemonicHelper.getMnemonic(closeButton));
closeButton.addActionListener(this);
buttonsPanel.add(Box.createHorizontalGlue());
buttonsPanel.add(closeButton);
yPanel.add(buttonsPanel);
contentPane.add(yPanel, BorderLayout.SOUTH);
// Set initial text components and buttons' enabled state
updateComponents();
// Listen to selection changes to reflect the change
bookmarkList.addListSelectionListener(this);
// table will receive initial focus
setInitialFocusComponent(bookmarkList);
// Selects OK when enter is pressed
getRootPane().setDefaultButton(closeButton);
// Packs dialog
setMinimumSize(MINIMUM_DIALOG_DIMENSION);
setMaximumSize(MAXIMUM_DIALOG_DIMENSION);
// Call dispose() on close and write bookmarks file
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
showDialog();
}
/**
* Updates text fields and buttons' enabled state based on the current selection. Should be called
* whenever the list selection has changed.
*/
private void updateComponents() {
String nameValue = null;
String locationValue = null;
boolean componentsEnabled = false;
if(!bookmarkList.isSelectionEmpty() && bookmarks.size()>0) {
componentsEnabled = true;
Bookmark b = (Bookmark)bookmarkList.getSelectedValue();
nameValue = b.getName();
locationValue = b.getLocation();
}
// Ignore text field events while setting values
ignoreDocumentListenerEvents = true;
nameField.setText(nameValue);
nameField.setEnabled(componentsEnabled);
locationField.setText(locationValue);
locationField.setEnabled(componentsEnabled);
ignoreDocumentListenerEvents = false;
goToButton.setEnabled(componentsEnabled);
duplicateButton.setEnabled(componentsEnabled);
removeButton.setEnabled(componentsEnabled);
}
/**
* Called whenever a value in one of the text fields has been modified, and updates the current Bookmark instance to
* use the new value.
*
* @param sourceDocument the javax.swing.text.Document of the JTextField that was modified
*/
private void modifyBookmark(Document sourceDocument) {
if(ignoreDocumentListenerEvents || bookmarks.size()==0)
return;
int selectedIndex = bookmarkList.getSelectedIndex();
// Make sure that the selected index is not out of bounds
if(!bookmarkList.isIndexValid(selectedIndex))
return;
Bookmark selectedBookmark = bookmarks.elementAt(selectedIndex);
if(currentBookmarkSave==null) {
// Create a clone of the current bookmark in order to cancel any modifications made to it if the dialog
// is cancelled.
try { currentBookmarkSave = (Bookmark)selectedBookmark.clone(); }
catch(CloneNotSupportedException ex) {}
this.currentListIndex = selectedIndex;
}
// Update name
if(sourceDocument==nameField.getDocument()) {
String name = nameField.getText();
if(name.trim().equals(""))
name = getFreeNameVariation(Translator.get("untitled"));
selectedBookmark.setName(name);
bookmarkList.itemModified(selectedIndex, false);
}
// Update location
else {
selectedBookmark.setLocation(locationField.getText());
}
}
/**
* Returns the first variation of the given name that is not already used by another bookmark, e.g. :
* <br>"music" -> "music (2)" if there already is bookmark with the "music" name
* <br>"music (2)" -> "music (3)" and so on...
*/
private String getFreeNameVariation(String name) {
if(!containsName(name))
return name;
int len = name.length();
char c;
int num = 2;
if(len>4 && name.charAt(len-1)==')'
&& (c=name.charAt(len-2))>='0' && c<='9'
&& name.charAt(len-3)=='('
&& name.charAt(len-4)==' ')
{
num = (c-'0')+1;
name = name.substring(0, len-4);
}
String newName;
while(containsName(newName=(name+" ("+num+++")")));
return newName;
}
/**
* Returns true if the bookmarks list contains a bookmark that has the specified name.
*/
private boolean containsName(String name) {
int nbBookmarks = bookmarks.size();
for(int i=0; i<nbBookmarks; i++) {
if(bookmarks.elementAt(i).getName().equals(name))
return true;
}
return false;
}
/**
* Overrides dispose() to write bookmarks to disk (if needed).
*/
@Override
public void dispose() {
super.dispose();
// Rollback current bookmark's modifications if the dialog was cancelled
if(currentBookmarkSave!=null) {
bookmarks.setElementAt(currentBookmarkSave, currentListIndex);
currentBookmarkSave = null;
}
// Resume bookmark change events
BookmarkManager.setFireEvents(true);
// Write bookmarks file to disk, only if changes were made to bookmarks
try {BookmarkManager.writeBookmarks(false);}
// We should probably pop an error here.
catch(Exception e) {}
}
///////////////////////////
// ActionListener method //
///////////////////////////
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
// Dispose the dialog (bookmarks save is performed in dispose())
if (source== closeButton) {
// Do not rollback current bookmark's modifications on dispose()
currentBookmarkSave = null;
dispose();
}
// Create a new empty bookmark / duplicate the currently selected bookmark
else if (source==newButton || source==duplicateButton) {
Bookmark newBookmark;
if(source==newButton) {
newBookmark = new Bookmark(getFreeNameVariation(Translator.get("untitled")), "");
}
else { // Duplicate button
try {
Bookmark currentBookmark = (Bookmark)bookmarkList.getSelectedValue();
newBookmark = (Bookmark)currentBookmark.clone();
newBookmark.setName(getFreeNameVariation(currentBookmark.getName()));
}
catch(CloneNotSupportedException ex) { return; }
}
bookmarks.add(newBookmark);
int newBookmarkIndex = bookmarks.size()-1;
bookmarkList.selectAndScroll(newBookmarkIndex);
updateComponents();
nameField.selectAll();
nameField.requestFocus();
}
else if(source==goToButton) {
// Dispose dialog first
dispose();
// Change active panel's folder
mainFrame.getActivePanel().tryChangeCurrentFolder(((Bookmark)bookmarkList.getSelectedValue()).getLocation());
}
}
///////////////////////////////////
// ListSelectionListener methods //
///////////////////////////////////
public void valueChanged(ListSelectionEvent e) {
if(e.getValueIsAdjusting())
return;
// Reset current bookmark's save
currentBookmarkSave = null;
// currentListIndex = bookmarkList.getSelectedIndex();
// Update components to reflect the new selection
updateComponents();
}
//////////////////////////////
// DocumentListener methods //
//////////////////////////////
public void changedUpdate(DocumentEvent e) {
modifyBookmark(e.getDocument());
}
public void insertUpdate(DocumentEvent e) {
modifyBookmark(e.getDocument());
}
public void removeUpdate(DocumentEvent e) {
modifyBookmark(e.getDocument());
}
}