1   /**
2    * JHylaFax - A java client for HylaFAX.
3    *
4    * Copyright (C) 2005 by Steffen Pingel <steffenp@gmx.de>
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; either version 2 of the License, or
9    * (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with this program; if not, write to the Free Software
18   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19   */
20  package net.sf.jhylafax.addressbook;
21  
22  import static net.sf.jhylafax.JHylaFAX.i18n;
23  import java.awt.BorderLayout;
24  import java.awt.Component;
25  import java.awt.Insets;
26  import java.awt.datatransfer.Transferable;
27  import java.awt.event.ActionEvent;
28  import java.awt.event.WindowAdapter;
29  import java.awt.event.WindowEvent;
30  import java.io.BufferedReader;
31  import java.io.ByteArrayInputStream;
32  import java.io.ByteArrayOutputStream;
33  import java.io.File;
34  import java.io.FileInputStream;
35  import java.io.FileOutputStream;
36  import java.io.FileReader;
37  import java.io.IOException;
38  import java.io.ObjectInputStream;
39  import java.io.ObjectOutputStream;
40  import java.util.ArrayList;
41  import java.util.List;
42  import java.util.StringTokenizer;
43  import javax.swing.Action;
44  import javax.swing.Icon;
45  import javax.swing.JComponent;
46  import javax.swing.JFileChooser;
47  import javax.swing.JFrame;
48  import javax.swing.JLabel;
49  import javax.swing.JMenu;
50  import javax.swing.JMenuBar;
51  import javax.swing.JOptionPane;
52  import javax.swing.JPanel;
53  import javax.swing.JPopupMenu;
54  import javax.swing.JScrollPane;
55  import javax.swing.JSplitPane;
56  import javax.swing.JTable;
57  import javax.swing.JTextField;
58  import javax.swing.JToolBar;
59  import javax.swing.JTree;
60  import javax.swing.ListSelectionModel;
61  import javax.swing.WindowConstants;
62  import javax.swing.event.ListSelectionEvent;
63  import javax.swing.event.ListSelectionListener;
64  import javax.swing.table.AbstractTableModel;
65  import javax.swing.tree.DefaultMutableTreeNode;
66  import javax.swing.tree.DefaultTreeCellRenderer;
67  import javax.swing.tree.DefaultTreeModel;
68  import javax.swing.tree.TreePath;
69  import net.sf.jhylafax.JHylaFAX;
70  import net.sf.jhylafax.LocaleChangeListener;
71  import net.sf.jhylafax.Settings;
72  import net.wimpi.pim.Pim;
73  import net.wimpi.pim.contact.db.ContactDatabase;
74  import net.wimpi.pim.contact.db.ContactGroup;
75  import net.wimpi.pim.contact.facades.SimpleContact;
76  import net.wimpi.pim.contact.io.ContactMarshaller;
77  import net.wimpi.pim.contact.io.ContactUnmarshaller;
78  import net.wimpi.pim.contact.model.Contact;
79  import net.wimpi.pim.factory.ContactIOFactory;
80  import net.wimpi.pim.factory.ContactModelFactory;
81  import org.apache.commons.logging.Log;
82  import org.apache.commons.logging.LogFactory;
83  import org.xnap.commons.gui.Builder;
84  import org.xnap.commons.gui.ColoredTable;
85  import org.xnap.commons.gui.Dialogs;
86  import org.xnap.commons.gui.EraseTextFieldAction;
87  import org.xnap.commons.gui.ErrorDialog;
88  import org.xnap.commons.gui.ToolBarButton;
89  import org.xnap.commons.gui.action.AbstractXNapAction;
90  import org.xnap.commons.gui.table.StringCellRenderer;
91  import org.xnap.commons.gui.table.TableLayoutManager;
92  import org.xnap.commons.gui.table.TableSorter;
93  import org.xnap.commons.gui.util.DoubleClickListener;
94  import org.xnap.commons.gui.util.GUIHelper;
95  import org.xnap.commons.gui.util.IconHelper;
96  import org.xnap.commons.gui.util.PopupListener;
97  import org.xnap.commons.io.FileExtensionFilter;
98  import org.xnap.commons.settings.SettingStore;
99  import com.jgoodies.forms.builder.DefaultFormBuilder;
100 import com.jgoodies.forms.layout.FormLayout;
101 
102 public class AddressBook extends JFrame implements ListSelectionListener,
103 	LocaleChangeListener {
104 
105 	private final static Log logger = LogFactory.getLog(AddressBook.class);
106 	private static final String[] DEFAULT_CONTACT_TABLE_COLUMNS 
107 		= new String[] { "displayName", "company", "faxNumber" };
108 
109 	/**
110 	 * @param args
111 	 */
112 	public static void main(String[] args)
113 	{
114 		final AddressBook app = new AddressBook();
115 		app.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
116 		app.restoreLayout(new SettingStore(Settings.backstore));
117 		
118 		try {
119 			File file = JHylaFAX.getAddressBookFile();
120 			if (file.exists()) {
121 				app.load(file);
122 			}
123 		}
124 		catch (Exception e) {
125 			ErrorDialog.showError(null, 
126 					i18n.tr("Could not load address book"), 
127 					i18n.tr("JHylaFAX Error"), e);					
128 		}
129 
130 		app.addWindowListener(new WindowAdapter() {
131 			public void windowClosing(WindowEvent event) {
132 				try {
133 					File file = JHylaFAX.getAddressBookFile();
134 					app.store(file);
135 				}
136 				catch (Exception e) {
137 					ErrorDialog.showError(null, 
138 							i18n.tr("Could not store address book"), 
139 							i18n.tr("JHylaFAX Error"), e);					
140 				}
141 			}
142 		});
143 
144 		app.setVisible(true);
145 	}
146 	
147 	private JMenu addressBookMenu;
148 	private JTree addressBookTree;
149 	private DefaultTreeModel addressBookTreeModel;
150 	private CloseAction closeAction;
151 	private JTable contactTable;
152 	private AddressTableModel contactTableModel;
153 	private DeleteAction deleteAction;
154 	private Action doubleClickAction;
155 	private EditAction editAction;
156 	private ExportAction exportAction;
157 	private FilterAction filterAction;
158 	private ImportAction importAction;
159 	private ContactCollection localAddressBook;
160 	private JToolBar mainToolBar;
161 	private NewAction newAction;
162 	private DefaultMutableTreeNode rootNode;
163 	private JTextField searchTextField;
164 	private JSplitPane horizontalSplitPane;
165 	private JLabel filterLabel;
166 	private TableLayoutManager contactTableLayoutManager;
167 	
168 	public AddressBook() {
169 		ContactModelFactory cmf = Pim.getContactModelFactory();
170 
171 		JHylaFAX.initializeToolkit();
172 		initialize();
173 		
174 		ContactDatabase contactDatabase = Pim.getContactDBFactory().createContactDatabase();
175 		
176 		// initialize tree content
177 		DefaultMutableTreeNode localAddressBookTreeNode = new DefaultMutableTreeNode();
178 		localAddressBook = new ContactCollection(contactDatabase, i18n.tr("Local Address Book"));
179 		localAddressBookTreeNode.setUserObject(localAddressBook);
180 		rootNode.add(localAddressBookTreeNode);
181 		addressBookTree.expandPath(new TreePath(rootNode));
182 		addressBookTree.setSelectionRow(0);
183 		
184 		updateActions();
185 	}
186 
187 	public void exportContacts(File file, SimpleContact[] contacts) throws IOException
188 	{
189 		FileOutputStream out = new FileOutputStream(file);
190 		try {
191 			ContactIOFactory factory = Pim.getContactIOFactory();
192 		    ContactMarshaller marshaller = factory.createContactMarshaller();
193 		    marshaller.setEncoding("UTF-8");
194 		    for (SimpleContact contact : contacts) {
195 			    marshaller.marshallContact(out, contact.getContact());	
196 			}
197 		}
198 		finally {
199 			out.close();
200 		}
201 	}
202 
203 	public SimpleContact[] getAlllContacts()
204 	{
205 		TableSorter sorter = (TableSorter)contactTable.getModel();
206 		SimpleContact[] result = new SimpleContact[sorter.getRowCount()];
207 		for (int i = 0; i < result.length; i++) {
208 			int row = sorter.mapToIndex(i);
209 			result[i] = contactTableModel.getContact(row); 
210 		}
211 		return result;
212 	}
213 
214 	public ContactCollection getSelectedContactCollection()
215 	{
216 		DefaultMutableTreeNode node = (DefaultMutableTreeNode)addressBookTree.getSelectionPath().getLastPathComponent();
217 		return (ContactCollection)node.getUserObject();
218 	}
219 
220 	public SimpleContact[] getSelectedContacts()
221 	{
222 		int[] rows = contactTable.getSelectedRows();
223 		if (rows.length == 0) {
224 			return new SimpleContact[0];
225 		}
226 		SimpleContact[] result = new SimpleContact[rows.length];
227 		for (int i = 0; i < rows.length; i++) {
228 			int row = ((TableSorter)contactTable.getModel()).mapToIndex(rows[i]);
229 			result[i] = contactTableModel.getContact(row); 
230 		}
231 		return result;
232 	}
233 
234 	public SimpleContact[] importVCardContacts(File file) throws IOException
235 	{
236 		FileInputStream in = new FileInputStream(file);
237 		try {
238 			ContactIOFactory factory = Pim.getContactIOFactory();
239 		    ContactUnmarshaller unmarshaller = factory.createContactUnmarshaller();
240 		    unmarshaller.setEncoding("UTF-8");
241 		    Contact[] contacts = unmarshaller.unmarshallContacts(in);
242 		    if (contacts != null) {
243 			    SimpleContact[] result = new SimpleContact[contacts.length];
244 			    for (int i = 0; i < contacts.length; i++) {
245 			    	SimpleContact contact = new SimpleContact(contacts[i]);
246 				    getSelectedContactCollection().add(contact);
247 				    result[i] = contact;
248 				}
249 			    return result;
250 		    }
251 		    return new SimpleContact[0];
252 		}
253 		finally {
254 			in.close();
255 		}
256 	}
257 
258 	public SimpleContact[] importCSVContacts(File file, String separator) throws IOException
259 	{
260 		BufferedReader in = new BufferedReader(new FileReader(file));
261 		try {
262 			List<SimpleContact> contacts = new ArrayList<SimpleContact>();
263 			String line;
264 			while ((line = in.readLine()) != null) {
265 				SimpleContact contact = new SimpleContact();
266 				StringTokenizer t = new StringTokenizer(line, separator);
267 				if (t.hasMoreTokens()) contact.setFirstname(t.nextToken());
268 				if (t.hasMoreTokens()) contact.setLastname(t.nextToken());
269 				if (t.hasMoreTokens()) contact.setFaxNumber(t.nextToken());
270 				if (t.hasMoreTokens()) contact.setCompany(t.nextToken());
271 			    getSelectedContactCollection().add(contact);
272 			    contacts.add(contact);
273 			}
274 			return contacts.toArray(new SimpleContact[0]);
275 		}
276 		finally {
277 			in.close();
278 		}
279 	}
280 
281 	private void initialize() {
282 		initializeActions();
283 		initializeShortCuts();
284 		initializeMenuBar();
285 		initializeContent();
286 		initializeToolBar();
287 		
288 		updateLabels();		
289 	}
290 
291 	private void initializeActions()
292 	{
293 		newAction = new NewAction();
294 		editAction = new EditAction();
295 		deleteAction = new DeleteAction();
296 		importAction = new ImportAction();
297 		exportAction = new ExportAction();
298 		closeAction = new CloseAction();
299 		filterAction = new FilterAction();
300 	}
301 
302 	private void initializeContent()
303 	{
304 		horizontalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
305 		horizontalSplitPane.setBorder(GUIHelper.createEmptyBorder(5));
306 		
307 		rootNode = new DefaultMutableTreeNode();
308 		addressBookTreeModel = new DefaultTreeModel(rootNode);
309 		addressBookTree = new JTree(addressBookTreeModel);
310 		addressBookTree.setRootVisible(false);
311 		addressBookTree.setCellRenderer(new ContactCollectionCellRenderer());
312 		horizontalSplitPane.add(new JScrollPane(addressBookTree));
313 		
314 		JPanel contactPanel = new JPanel();
315 		contactPanel.setLayout(new BorderLayout(0, 10));
316 		horizontalSplitPane.add(contactPanel);
317 		
318 		DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("min, 3dlu, min, 3dlu, pref:grow, 3dlu, min", ""));
319 		contactPanel.add(builder.getPanel(), BorderLayout.NORTH);
320 		
321 		searchTextField = new JTextField(10);
322 		EraseTextFieldAction eraseAction = new EraseTextFieldAction(searchTextField) {
323 			public void actionPerformed(ActionEvent event) {
324 				super.actionPerformed(event);
325 				filterAction.actionPerformed(event);
326 			};
327 		};
328 		builder.append(new TabTitleButton(eraseAction));
329 		filterLabel = new JLabel();
330 		builder.append(filterLabel);
331 		builder.append(searchTextField);
332 		GUIHelper.bindEnterKey(searchTextField, filterAction);
333 		
334 		builder.append(Builder.createButton(filterAction));
335 		
336 		JPopupMenu tablePopupMenu = new JPopupMenu();
337 		tablePopupMenu.add(Builder.createMenuItem(newAction));
338 		tablePopupMenu.addSeparator();		
339 		tablePopupMenu.add(Builder.createMenuItem(editAction));
340 		tablePopupMenu.addSeparator();		
341 		tablePopupMenu.add(Builder.createMenuItem(deleteAction));
342 
343 		contactTableModel = new AddressTableModel();
344 		TableSorter sorter = new TableSorter(contactTableModel);
345 		contactTable = new ColoredTable(sorter);
346 		contactTableLayoutManager = new TableLayoutManager(contactTable);
347 		contactTableLayoutManager.addColumnProperties("displayName", "", 180, true);
348 		contactTableLayoutManager.addColumnProperties("company", "", 80, true);
349 		contactTableLayoutManager.addColumnProperties("faxNumber", "", 60, true);
350 		contactTableLayoutManager.initializeTableLayout();
351 		contactPanel.add(new JScrollPane(contactTable), BorderLayout.CENTER);
352 
353 		contactTable.setShowVerticalLines(true);
354 		contactTable.setShowHorizontalLines(false);
355 		contactTable.setAutoCreateColumnsFromModel(true);
356 		contactTable.setIntercellSpacing(new java.awt.Dimension(2, 1));
357 		contactTable.setBounds(0, 0, 50, 50);
358 		contactTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
359 		contactTable.getSelectionModel().addListSelectionListener(this);
360 		contactTable.addMouseListener(new PopupListener(tablePopupMenu));
361 		contactTable.addMouseListener(new DoubleClickListener(new TableDoubleClickAction()));
362 		contactTable.setTransferHandler(new ContactTransferHandler());
363 		contactTable.setDragEnabled(true);
364 		
365 		contactTable.setDefaultRenderer(String.class, new StringCellRenderer());
366 		
367 		getContentPane().setLayout(new BorderLayout());
368 		getContentPane().add(horizontalSplitPane, BorderLayout.CENTER);
369 	}
370 	
371 	private void initializeMenuBar()
372 	{
373 		JMenuBar menuBar = new JMenuBar();
374 		setJMenuBar(menuBar);
375 		
376 		addressBookMenu = new JMenu();
377 		menuBar.add(addressBookMenu);
378 		addressBookMenu.add(Builder.createMenuItem(newAction));
379 		addressBookMenu.addSeparator();
380 		addressBookMenu.add(Builder.createMenuItem(editAction));
381 		addressBookMenu.addSeparator();
382 		addressBookMenu.add(Builder.createMenuItem(deleteAction));
383 		addressBookMenu.addSeparator();
384 		addressBookMenu.add(Builder.createMenuItem(importAction));
385 		addressBookMenu.add(Builder.createMenuItem(exportAction));
386 		addressBookMenu.addSeparator();
387 		addressBookMenu.add(Builder.createMenuItem(closeAction));
388 	}
389 
390 	private void initializeShortCuts()
391 	{
392 	}
393 
394 	private void initializeToolBar()
395 	{
396 		mainToolBar = new JToolBar();
397 		//mainToolBar.setBorderPainted(false);
398 		//mainToolBar.setRollover(true);
399 		getContentPane().add(mainToolBar, BorderLayout.NORTH);
400 		
401 		mainToolBar.add(Builder.createToolBarButton(newAction));
402 		mainToolBar.addSeparator();
403 		mainToolBar.add(Builder.createToolBarButton(editAction));
404 		mainToolBar.add(Builder.createToolBarButton(deleteAction));
405 	}
406 	
407 	public void load(File file) throws IOException, ClassNotFoundException
408 	{
409 		ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
410 		try {
411 			ContactDatabase contactDatabase = (ContactDatabase)in.readObject();
412 			localAddressBook.setDatabase(contactDatabase);
413 		}
414 		finally {
415 			in.close();
416 		}
417 	}
418 
419 	public void saveLayout(SettingStore store) {
420 		contactTableLayoutManager.saveLayout(store, "addressbook.contact");
421 		store.saveWindow("addressbook.window", this);
422 		store.saveSplitPane("addressbook.horizontalSplit", horizontalSplitPane);
423 	}
424 	
425 	public void restoreLayout(SettingStore store) {
426 		contactTableLayoutManager.restoreLayout(store, "addressbook.contact");
427 		store.restoreWindow("addressbook.window", 40, 40, 550, 300, this);
428 		store.restoreSplitPane("addressbook.horizontalSplit", 150, horizontalSplitPane);
429 	}
430 
431 	public void setDoubleClickAction(Action doubleClickAction)
432 	{
433 		this.doubleClickAction = doubleClickAction;
434 	}
435 	
436     public void store(File file) throws IOException
437 	{
438     	if (!localAddressBook.isChanged()) {
439     		logger.info("Address book unchanged");
440     		return;
441     	}
442 
443     	ObjectOutputStream in = new ObjectOutputStream(new FileOutputStream(file));
444     	try {
445     		in.writeObject(localAddressBook.getDatabase());
446     		logger.info("Stored address book in " + file.getAbsolutePath());
447     	}
448     	finally {
449     		in.close();
450     	}
451 	}
452 
453 	public void updateLabels() {
454 		setTitle(i18n.tr("JHylaFAX Address Book"));
455 		
456 		addressBookMenu.setText(i18n.tr("Address Book"));
457 		
458 		newAction.putValue(Action.NAME, i18n.tr("New Contact..."));
459 		editAction.putValue(Action.NAME, i18n.tr("Edit Contact..."));
460 		deleteAction.putValue(Action.NAME, i18n.tr("Delete Contact"));
461 		
462 		importAction.putValue(Action.NAME, i18n.tr("Import..."));
463 		exportAction.putValue(Action.NAME, i18n.tr("Export..."));
464 		
465 		closeAction.putValue(Action.NAME, i18n.tr("Close"));
466 		
467 		filterLabel.setText(i18n.tr("Search for"));
468 		filterAction.putValue(Action.NAME, i18n.tr("Go"));
469 		
470 		contactTableLayoutManager.getTableLayout().setColumnNames(new String[] {
471 				i18n.tr("Name"),
472 				i18n.tr("Company"), 
473 				i18n.tr("Fax"),});
474 		
475 		GUIHelper.setMnemonics(getJMenuBar());
476 	}
477 
478 	public void valueChanged(ListSelectionEvent e) {
479 		updateActions();
480 	}
481 
482 	public void updateActions() {
483 		boolean selected = (contactTable.getSelectedRow() != -1);
484 		editAction.setEnabled(selected);
485 		deleteAction.setEnabled(selected);
486 	}
487 	
488 	private static class AddressTableModel extends AbstractTableModel {
489 
490 		private static final Class[] columnClasses= {
491 			String.class, 
492 			String.class,
493 			String.class,
494 		};
495 		
496 		private List<SimpleContact> data = new ArrayList<SimpleContact>();
497 		
498 		public AddressTableModel() 
499 		{
500 		}
501 		
502 		public void add(SimpleContact contact)
503 		{
504 			data.add(contact);
505 			fireTableRowsInserted(data.size() - 1, data.size() - 1);
506 		}
507 		
508 		public Class<?> getColumnClass(int column) {
509 	        return columnClasses[column];
510 	    }
511 	
512 		public int getColumnCount()
513 		{
514 			return columnClasses.length;
515 		}
516 
517 		public SimpleContact getContact(int row) {
518 			return data.get(row);
519 		}
520 
521 		public int getRowCount()
522 		{
523 			return data.size();
524 		}
525 		
526 	    public Object getValueAt(int row, int column)
527 		{
528 			SimpleContact contact = data.get(row);
529 			switch (column) {
530 			case 0:
531 				return contact.getFirstname() + " " + contact.getLastname();
532 			case 1:
533 				return contact.getCompany();
534 			case 2:
535 				return contact.getFaxNumber();
536 			default:
537 				return null;
538 			}
539 		}
540 		
541 		public int indexOf(SimpleContact contact)
542 		{
543 			for (int i = 0; i < data.size(); i++) {
544 				if (data.get(i).getContact().equals(contact.getContact())) {
545 					return i;
546 				}
547 			}
548 			return -1;
549 		}
550 
551 		public void remove(SimpleContact contact)
552 		{
553 			// XXX work around broken SimpleContant.equals() method
554 			int i = indexOf(contact);
555 			if (i != -1) {
556 				data.remove(i);
557 				fireTableRowsDeleted(i, i);
558 			}
559 		}
560 		
561 		public void setData(List<SimpleContact> data)
562 		{
563 			this.data = data;
564 			fireTableDataChanged();
565 		}
566 
567 		public void update(SimpleContact contact)
568 		{
569 			// XXX work around broken SimpleContant.equals() method
570 			int i = indexOf(contact);
571 			if (i != -1) {
572 				fireTableRowsUpdated(i, i);
573 			}
574 		}
575 
576 	}
577 
578 	private class CloseAction extends AbstractXNapAction {
579 		
580 		public CloseAction() {
581 		}
582 
583 		public void actionPerformed(ActionEvent event)
584 		{
585 			AddressBook.this.setVisible(false);
586 		}		
587 	}
588 
589 	private class ContactCollection
590 	{
591 		
592 		private ContactDatabase database;
593 		private String filterText;
594 		private ContactGroup group;
595 		private String name;
596 		private ContactCollection parent;
597 		private boolean changed;
598 		
599 		public ContactCollection(ContactCollection parent, ContactGroup group)
600 		{
601 			this.parent = parent;
602 			this.group = group;
603 		}
604 		
605 		public ContactCollection(ContactDatabase database, String name)
606 		{
607 			this.database = database;
608 			this.name = name;
609 		}
610 
611 		public void add(SimpleContact contact)
612 		{
613 			getDatabase().getContactCollection().add(contact.getContact());
614 			if (group != null) {
615 				group.addContact(contact.getContact());
616 			}
617 			if (matches(contact)) {
618 				contactTableModel.add(contact);
619 			}
620 			changed = true;
621 		}
622 		
623 		public void changed(SimpleContact contact)
624 		{
625 			if (!matches(contact)) {
626 				contactTableModel.remove(contact);
627 			}
628 			else {
629 				contactTableModel.update(contact);
630 			}
631 			changed = true;
632 		}
633 		
634 		public ContactDatabase getDatabase()
635 		{
636 			return (parent != null) ? parent.getDatabase() : database;
637 		}
638 
639 		public ContactGroup getGroup()
640 		{
641 			return group;
642 		}
643 		
644 		public boolean isChanged()
645 		{
646 			return changed;
647 		}
648 		
649 		private boolean matches(SimpleContact contact)
650 		{
651 			if (filterText == null || filterText.length() == 0) {
652 				return true;
653 			}
654 			return contact.getFirstname().toLowerCase().contains(filterText)
655 				|| contact.getLastname().toLowerCase().contains(filterText)
656 				|| contact.getCompany().toLowerCase().contains(filterText)
657 				|| contact.getFaxNumber().toLowerCase().contains(filterText);
658 		}
659 		
660 		public void remove(SimpleContact contact)
661 		{
662 			getDatabase().getContactCollection().remove(contact.getContact());
663 			if (group != null) {
664 				group.removeContact(contact.getContact());
665 			}
666 			if (matches(contact)) {
667 				contactTableModel.remove(contact);
668 			}
669 			changed = true;
670 		}
671 		
672 		public void resync()
673 		{
674 			Contact[] contacts = (group != null) 
675 				? group.listContacts()
676 				: database.getContactCollection().toArray();
677 			List<SimpleContact> data = new ArrayList<SimpleContact>(contacts.length);
678 			for (int i = 0; i < contacts.length; i++) {
679 				SimpleContact contact = new SimpleContact(contacts[i]);
680 				if (matches(contact)) {
681 					data.add(contact);
682 				}
683 			}
684 			contactTableModel.setData(data);
685 			valueChanged(null);
686 		}
687 	
688 		public void setDatabase(ContactDatabase database)
689 		{
690 			this.database = database;
691 			changed = false;
692 			resync();
693 		}
694 		
695 		public void setFilterText(String filterText)
696 		{
697 			this.filterText = filterText.toLowerCase();
698 			resync();
699 		}
700 
701 		@Override
702 		public String toString()
703 		{
704 			return (group != null) ? group.getName() : name;
705 		}
706 		
707 	}
708 
709 	private static class ContactCollectionCellRenderer extends DefaultTreeCellRenderer {
710 		Icon bookIcon = IconHelper.getTreeIcon("contents.png");
711 		Icon groupIcon = IconHelper.getTreeIcon("kdmconfig.png");
712 		
713 		@Override
714 		public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus)
715 		{
716 			super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
717 					row, hasFocus);
718 
719 			DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
720 			if (node.getUserObject() instanceof ContactCollection) {
721 				ContactCollection collection = (ContactCollection)node.getUserObject();
722 				setIcon((collection.getGroup() != null) ? groupIcon : bookIcon);
723 			}
724 			
725 			return this;
726 		}
727 	}
728 
729 	private class DeleteAction extends AbstractXNapAction {
730 		
731 		public DeleteAction() {
732 			putValue(ICON_FILENAME, "editdelete.png");
733 		}
734 
735 		public void actionPerformed(ActionEvent e)
736 		{
737 			SimpleContact[] contacts = getSelectedContacts();
738 			if (Dialogs.showConfirmDialog(AddressBook.this, 
739 					i18n.tr("Do you really want to delete the selected contacts?"),
740 					i18n.tr("Delete Contacts"), 
741 					JOptionPane.YES_NO_OPTION, 
742 					Settings.CONFIRM_DELETE) == JOptionPane.YES_OPTION) {
743 				for (int i = 0; i < contacts.length; i++) {
744 					getSelectedContactCollection().remove(contacts[i]);
745 				}
746 			}
747 		}
748 		
749 	}
750 
751 	private class EditAction extends AbstractXNapAction {
752 		
753 		public EditAction() {
754 			putValue(ICON_FILENAME, "edit.png");
755 		}
756 
757 		public void actionPerformed(ActionEvent e)
758 		{
759 			SimpleContact[] contact = getSelectedContacts();
760 			EditContactDialog dialog = new EditContactDialog(AddressBook.this, contact[0]);
761 			dialog.setModal(true);
762 			dialog.setLocationRelativeTo(AddressBook.this);
763 			dialog.setVisible(true);
764 			if (dialog.isOkay()) {
765 				getSelectedContactCollection().changed(contact[0]);
766 			}
767 		}
768 		
769 	}
770 	
771 	private class ExportAction extends AbstractXNapAction {
772 		
773 		public ExportAction() {
774 			putValue(ICON_FILENAME, "fileexport.png");
775 		}
776 
777 		public void actionPerformed(ActionEvent event)
778 		{
779 			if (contactTableModel.getRowCount() == 0) {
780 				Dialogs.showInfo(AddressBook.this, i18n.tr("No data to export"), i18n.tr("JHylaFAX Addressbook Error"));
781 			}
782 			
783 			JFileChooser fileChooser = new JFileChooser();
784 			fileChooser.addChoosableFileFilter(new FileExtensionFilter(i18n.tr("vCards (*.vcf)"), ".vcf"));
785 			if (fileChooser.showSaveDialog(AddressBook.this) == JFileChooser.APPROVE_OPTION) {
786 				try {
787 					SimpleContact[] contacts = getSelectedContacts();
788 					if (contacts.length == 0) {
789 						contacts = getAlllContacts();
790 					}
791 					exportContacts(fileChooser.getSelectedFile(), contacts);
792 					Dialogs.showInfo(AddressBook.this, 
793 							i18n.tr("Exported {0} contacts", contacts.length), 
794 							i18n.tr("JHylaFAX Address Book"));
795 				}
796 				catch (Exception e) {
797 					ErrorDialog.showError(AddressBook.this, 
798 							i18n.tr("Could not export to file \"{0}\""), 
799 							i18n.tr("JHylaFAX Error"), e);					
800 				}
801 			}
802 		}		
803 	}
804 
805 	private class FilterAction extends AbstractXNapAction {
806 		
807 		public FilterAction() {
808 			putValue(ICON_FILENAME, "filter.png");
809 		}
810 
811 		public void actionPerformed(ActionEvent e)
812 		{
813 			getSelectedContactCollection().setFilterText(searchTextField.getText());
814 		}
815 		
816 	}
817 
818 	private class ImportAction extends AbstractXNapAction {
819 		
820 		public ImportAction() {
821 			putValue(ICON_FILENAME, "fileimport.png");
822 		}
823 /*
824 		public void actionPerformed1(ActionEvent event)
825 		{
826 			JFileChooser fileChooser = new JFileChooser();
827 			fileChooser.addChoosableFileFilter(new FileExtensionFilter(I18n.tr("vCards (*.vcf)"), ".vcf"));
828 			if (fileChooser.showOpenDialog(AddressBook.this) == JFileChooser.APPROVE_OPTION) {
829 				try {
830 					SimpleContact[] contacts = importContacts(fileChooser.getSelectedFile());
831 					Dialogs.showInfo(AddressBook.this, 
832 							I18n.tr("Imported {0} contacts", contacts.length), 
833 							I18n.tr("JHylaFAX Address Book"));
834 
835 				}
836 				catch (Exception e) {
837 					ErrorDialog.showError(AddressBook.this, 
838 							I18n.tr("Could not import from file \"{0}\""), 
839 							I18n.tr("JHylaFAX Addressbook Error"), e);					
840 				}
841 			}
842 		}
843 */
844 		public void actionPerformed(ActionEvent e)
845 		{
846 			ImportWizard wizard = new ImportWizard(AddressBook.this);
847 			wizard.setLocationRelativeTo(AddressBook.this);
848 			wizard.setVisible(true);
849 		}
850 		
851 	}
852 
853 	private class NewAction extends AbstractXNapAction {
854 		
855 		public NewAction() {
856 			putValue(ICON_FILENAME, "filenew.png");
857 		}
858 
859 		public void actionPerformed(ActionEvent e)
860 		{
861 			SimpleContact contact = new SimpleContact();
862 			EditContactDialog dialog = new EditContactDialog(AddressBook.this, contact);
863 			dialog.setModal(true);
864 			dialog.setLocationRelativeTo(AddressBook.this);
865 			dialog.setVisible(true);
866 			if (dialog.isOkay()) {
867 				getSelectedContactCollection().add(contact);
868 			}
869 		}
870 		
871 	}
872 
873 	private class TableDoubleClickAction extends AbstractXNapAction {
874 		
875 		public TableDoubleClickAction() {
876 		}
877 
878 		public void actionPerformed(ActionEvent event)
879 		{
880 			if (doubleClickAction == null) {
881 				if (editAction.isEnabled()) {
882 					editAction.actionPerformed(event);
883 				}
884 			}
885 			else if (doubleClickAction.isEnabled()){
886 				doubleClickAction.actionPerformed(event);
887 			}
888 		}
889 		
890 	}
891 	
892 	private class TabTitleButton extends ToolBarButton
893     {
894     	public TabTitleButton(Action action)
895     	{
896     		super(action);
897     		String iconName = (String)action.getValue(AbstractXNapAction.ICON_FILENAME);
898     		setIcon(IconHelper.getTabTitleIcon(iconName));
899     		setMargin(new Insets(0, 0, 0, 0));
900     	}
901     }
902 
903 	private class ContactTransferHandler extends AbstractContactTransferHandler {
904 
905 		@Override
906 		protected Transferable createTransferable(JComponent c)
907 		{
908 			SimpleContact[] contacts = getSelectedContacts();
909 			if (contacts.length == 0) {
910 				return super.createTransferable(c);
911 			}
912 			
913 			ByteArrayOutputStream out = new ByteArrayOutputStream();
914 			ContactIOFactory factory = Pim.getContactIOFactory();
915 			ContactMarshaller marshaller = factory.createContactMarshaller();
916 			marshaller.setEncoding("UTF-8");
917 			for (SimpleContact contact : contacts) {
918 				marshaller.marshallContact(out, contact.getContact());	
919 			}
920 			return new ContactTransferable(new ByteArrayInputStream(out.toByteArray()));
921 		}
922 
923 		@Override
924 		public void importData(Contact[] contacts)
925 		{
926 		    for (int i = 0; i < contacts.length; i++) {
927 		    	SimpleContact contact = new SimpleContact(contacts[i]);
928 			    getSelectedContactCollection().add(contact);
929 			}
930 		}
931 
932 	}
933 
934 }