Java tutorial
/* Copyright (c) 2006-2007 Harri Kaimio This file is part of Photovault. Photovault 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 2 of the License, or (at your option) any later version. Photovault 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 Photovault; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.photovault.swingui; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import javax.swing.*; import java.io.*; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.photovault.command.CommandException; import org.photovault.command.PhotovaultCommandHandler; import org.photovault.imginfo.*; import org.photovault.imginfo.PhotoInfo; import org.photovault.imginfo.ShootingDateComparator; import org.photovault.imginfo.ShootingPlaceComparator; import org.photovault.folder.*; import org.photovault.swingui.framework.AbstractController; import org.photovault.swingui.framework.DefaultAction; import org.photovault.swingui.framework.DefaultEvent; import org.photovault.swingui.framework.DefaultEventListener; import org.photovault.swingui.indexer.BackgroundIndexer; import org.photovault.swingui.indexer.CurrentFolderIndexer; import org.photovault.swingui.indexer.IndexerFileChooser; import org.photovault.swingui.indexer.UpdateIndexAction; import org.photovault.swingui.taskscheduler.BackgroundTaskListener; import org.photovault.swingui.taskscheduler.SwingWorkerTaskScheduler; import org.photovault.swingui.taskscheduler.TaskPriority; import org.photovault.swingui.volumetree.ExtDirPhotos; import org.photovault.swingui.volumetree.VolumeTreeController; import org.photovault.taskscheduler.BackgroundTask; import org.photovault.taskscheduler.TaskProducer; public class BrowserWindow extends AbstractController { static Log log = LogFactory.getLog(BrowserWindow.class.getName()); JFrame window; PhotoViewController viewCtrl = null; /** Indexer job that updates the currently displayed external folder to match file system state. */ CurrentFolderIndexer folderIndexer = null; public BrowserWindow(AbstractController parent) { this(parent, null); } /** Constructor */ public BrowserWindow(AbstractController parent, final List<PhotoInfo> initialPhotos) { super(parent); window = new JFrame("Photovault Browser"); SwingWorkerTaskScheduler taskScheduler = (SwingWorkerTaskScheduler) Photovault.getInstance() .getTaskScheduler(); folderIndexer = new CurrentFolderIndexer(taskScheduler, (PhotovaultCommandHandler) getCommandHandler()); createUI(null); taskScheduler.addTaskListener(folderIndexer, new BackgroundTaskListener() { public void taskExecuted(TaskProducer producer, BackgroundTask task) { StringBuffer msgBuf = new StringBuffer(); CurrentFolderIndexer indexer = (CurrentFolderIndexer) producer; if (indexer.getState() == CurrentFolderIndexer.IndexingPhase.FILE_INDEX) { File f = indexer.getCurrentDir(); msgBuf.append("Indexing ").append(f.getName()).append("... "); msgBuf.append(indexer.getPercentComplete()).append("% complete"); viewCtrl.setIndexingPercentComplete(indexer.getPercentComplete()); } statusBar.statusChanged(new StatusChangeEvent(indexer, msgBuf.toString())); } public void taskProducerFinished(TaskProducer producer) { StringBuffer msgBuf = new StringBuffer(); CurrentFolderIndexer indexer = (CurrentFolderIndexer) producer; if (indexer.getState() == CurrentFolderIndexer.IndexingPhase.FILE_INDEX) { File f = indexer.getCurrentDir(); msgBuf.append("Indexed ").append(f.getName()); } statusBar.statusChanged(new StatusChangeEvent(indexer, msgBuf.toString())); viewCtrl.setIndexingOngoing(false); } }); if (initialPhotos != null) { viewPane.setPhotos(initialPhotos); SwingUtilities.invokeLater(new java.lang.Runnable() { public void run() { viewPane.selectFirstPhoto(); } }); } } /** Show a specific photo collection in this window. Note! This must not be called before UI is created & visible!!! @param c The photo collection to show */ public void setPhotoCollection(PhotoCollection c) { viewCtrl.setCollection(c); } protected void createUI(PhotoCollection collection) { window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); tabPane = new JTabbedPane(); queryPane = new QueryPane(); PhotoFolderTreeController treeCtrl = new PhotoFolderTreeController(window, this); viewCtrl = new PhotoViewController(window, this); treePane = treeCtrl.folderTree; viewPane = viewCtrl.getThumbPane(); previewPane = viewCtrl.getPreviewPane(); tabPane.addTab("Query", queryPane); tabPane.addTab("Folders", treePane); VolumeTreeController volTreeCtrl = new VolumeTreeController(this); JTree volumeTree = volTreeCtrl.getView(); JScrollPane voltreeScrollPane = new JScrollPane(volumeTree); voltreeScrollPane.setPreferredSize(new Dimension(200, 500)); tabPane.add("Volumes", voltreeScrollPane); // TODO: get rid of this!!!! EditSelectionColorsAction colorAction = (EditSelectionColorsAction) viewPane.getEditSelectionColorsAction(); colorAction.setPreviewCtrl(previewPane); // Set listeners to both query and folder tree panes /* If an actionEvent comes from query pane, swich to query results. */ queryPane.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { viewCtrl.setCollection(queryPane.getResultCollection()); } }); /* If the selected folder is changed in treePane, switch to that immediately */ treeCtrl.registerEventListener(PhotoFolderTreeEvent.class, new DefaultEventListener<PhotoCollection>() { public void handleEvent(DefaultEvent<PhotoCollection> event) { PhotoCollection c = event.getPayload(); if (c != null) { viewCtrl.setCollection(c); if (c instanceof PhotoFolder) { PhotoFolder f = (PhotoFolder) c; if (f.getExternalDir() != null) { ExternalDir ed = f.getExternalDir(); folderIndexer.updateDir(ed.getVolume(), ed.getPath()); viewCtrl.setIndexingOngoing(true); } } } } }); volTreeCtrl.registerEventListener(PhotoFolderTreeEvent.class, new DefaultEventListener<PhotoCollection>() { public void handleEvent(DefaultEvent<PhotoCollection> event) { PhotoCollection c = event.getPayload(); if (c != null) { viewCtrl.setCollection(c); if (c instanceof ExtDirPhotos) { ExtDirPhotos photos = (ExtDirPhotos) c; ExternalVolume vol = (ExternalVolume) viewCtrl.getDAOFactory().getVolumeDAO() .getVolume(photos.getVolId()); folderIndexer.updateDir(vol, photos.getDirPath()); viewCtrl.setIndexingOngoing(true); } } } }); collectionPane = viewCtrl.getCollectionPane(); JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tabPane, collectionPane); split.putClientProperty(JSplitPane.ONE_TOUCH_EXPANDABLE_PROPERTY, new Boolean(true)); Container cp = window.getContentPane(); cp.setLayout(new BorderLayout()); cp.add(split, BorderLayout.CENTER); statusBar = new StatusBar(); cp.add(statusBar, BorderLayout.SOUTH); // Create actions for BrowserWindow UI ImageIcon indexDirIcon = getIcon("index_dir.png"); DefaultAction indexDirAction = new DefaultAction("Index directory...", indexDirIcon) { public void actionPerformed(ActionEvent e) { indexDir(); } }; indexDirAction.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_D); indexDirAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Index all images in a directory"); this.registerAction("new_ext_vol", indexDirAction); ImageIcon importIcon = getIcon("import.png"); importAction = new AbstractAction("Import image...", importIcon) { public void actionPerformed(ActionEvent e) { importFile(); } }; importAction.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_O); importAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); importAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Import new image into database"); ImageIcon updateIcon = getIcon("update_indexed_dirs.png"); UpdateIndexAction updateIndexAction = new UpdateIndexAction(viewCtrl, "Update indexed dirs", updateIcon, "Check for changes in previously indexed directories", KeyEvent.VK_U); this.registerAction("update_indexed_dirs", updateIndexAction); updateIndexAction.addStatusChangeListener(statusBar); ImageIcon previewTopIcon = getIcon("view_preview_top.png"); DefaultAction previewTopAction = new DefaultAction("Preview on top", previewTopIcon) { public void actionPerformed(ActionEvent e) { viewCtrl.setLayout(PhotoViewController.Layout.PREVIEW_HORIZONTAL_THUMBS); } }; previewTopAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Show preview on top of thumbnails"); previewTopAction.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_T); this.registerAction("view_preview_top", previewTopAction); ImageIcon previewRightIcon = getIcon("view_preview_right.png"); DefaultAction previewRightAction = new DefaultAction("Preview on right", previewRightIcon) { public void actionPerformed(ActionEvent e) { viewCtrl.setLayout(PhotoViewController.Layout.PREVIEW_VERTICAL_THUMBS); } }; previewRightAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Show preview on right of thumbnails"); previewRightAction.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_R); this.registerAction("view_preview_right", previewRightAction); ImageIcon previewNoneIcon = getIcon("view_no_preview.png"); DefaultAction previewNoneAction = new DefaultAction("No preview", previewNoneIcon) { public void actionPerformed(ActionEvent e) { viewCtrl.setLayout(PhotoViewController.Layout.ONLY_THUMBS); } }; previewNoneAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Show no preview image"); previewNoneAction.putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_O); this.registerAction("view_no_preview", previewNoneAction); DefaultAction previewLargeAction = new DefaultAction("Large", null) { public void actionPerformed(ActionEvent e) { viewCtrl.setPreviewSize(200); } }; DefaultAction previewSmallAction = new DefaultAction("Small", null) { public void actionPerformed(ActionEvent e) { viewCtrl.setPreviewSize(100); } }; JToolBar tb = createToolbar(); cp.add(tb, BorderLayout.NORTH); // Create the menu bar & menus JMenuBar menuBar = new JMenuBar(); window.setJMenuBar(menuBar); // File menu JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F); menuBar.add(fileMenu); JMenuItem newWindowItem = new JMenuItem("New window", KeyEvent.VK_N); newWindowItem.setIcon(getIcon("window_new.png")); newWindowItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BrowserWindow br = new BrowserWindow(getParentController(), null); // br.setVisible( true ); } }); fileMenu.add(newWindowItem); JMenuItem importItem = new JMenuItem(importAction); fileMenu.add(importItem); JMenuItem indexDirItem = new JMenuItem(indexDirAction); fileMenu.add(indexDirItem); fileMenu.add(new JMenuItem(viewCtrl.getActionAdapter("update_indexed_dirs"))); ExportSelectedAction exportAction = (ExportSelectedAction) viewPane.getExportSelectedAction(); JMenuItem exportItem = new JMenuItem(exportAction); exportAction.addStatusChangeListener(statusBar); fileMenu.add(exportItem); JMenu dbMenu = new JMenu("Database"); dbMenu.setMnemonic(KeyEvent.VK_B); dbMenu.setIcon(getIcon("empty_icon.png")); ExportMetadataAction exportMetadata = new ExportMetadataAction("Export as XML...", null, "Export whole database as XML file", KeyEvent.VK_E); dbMenu.add(new JMenuItem(exportMetadata)); ImportXMLAction importMetadata = new ImportXMLAction("Import XML data...", null, "Import data from other Photovault database as XML", KeyEvent.VK_I); importMetadata.addStatusChangeListener(statusBar); dbMenu.add(new JMenuItem(importMetadata)); fileMenu.add(dbMenu); JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_X); exitItem.setIcon(getIcon("exit.png")); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); fileMenu.add(exitItem); JMenu viewMenu = new JMenu("View"); viewMenu.setMnemonic(KeyEvent.VK_V); menuBar.add(viewMenu); JMenuItem vertIconsItem = new JMenuItem(previewRightAction); viewMenu.add(vertIconsItem); JMenuItem horzIconsItem = new JMenuItem(previewTopAction); viewMenu.add(horzIconsItem); JMenuItem noPreviewItem = new JMenuItem(previewNoneAction); viewMenu.add(noPreviewItem); JMenuItem nextPhotoItem = new JMenuItem(viewPane.getSelectNextAction()); viewMenu.add(nextPhotoItem); JMenuItem prevPhotoItem = new JMenuItem(viewPane.getSelectPreviousAction()); viewMenu.add(prevPhotoItem); viewMenu.add(new JMenuItem(previewSmallAction)); viewMenu.add(new JMenuItem(previewLargeAction)); JMenu sortMenu = new JMenu("Sort by"); sortMenu.setMnemonic(KeyEvent.VK_S); sortMenu.setIcon(getIcon("empty_icon.png")); JMenuItem byDateItem = new JMenuItem(new SetPhotoOrderAction(viewCtrl, new ShootingDateComparator(), "Date", null, "Order photos by date", KeyEvent.VK_D)); sortMenu.add(byDateItem); JMenuItem byPlaceItem = new JMenuItem(new SetPhotoOrderAction(viewCtrl, new ShootingPlaceComparator(), "Place", null, "Order photos by shooting place", KeyEvent.VK_P)); sortMenu.add(byPlaceItem); JMenuItem byQualityItem = new JMenuItem(new SetPhotoOrderAction(viewCtrl, new QualityComparator(), "Quality", null, "Order photos by quality", KeyEvent.VK_Q)); sortMenu.add(byQualityItem); viewMenu.add(sortMenu); // Set default ordering by date byDateItem.getAction().actionPerformed(new ActionEvent(this, 0, "Setting default")); JMenu imageMenu = new JMenu("Image"); imageMenu.setMnemonic(KeyEvent.VK_I); menuBar.add(imageMenu); imageMenu.add(new JMenuItem(viewPane.getEditSelectionPropsAction())); imageMenu.add(new JMenuItem(viewPane.getShowSelectedPhotoAction())); imageMenu.add(new JMenuItem(viewCtrl.getActionAdapter("rotate_cw"))); imageMenu.add(new JMenuItem(viewCtrl.getActionAdapter("rotate_ccw"))); imageMenu.add(new JMenuItem(viewCtrl.getActionAdapter("rotate_180"))); imageMenu.add(new JMenuItem(previewPane.getCropAction())); imageMenu.add(new JMenuItem(viewPane.getEditSelectionColorsAction())); imageMenu.add(new JMenuItem(viewPane.getDeleteSelectedAction())); // Create the Quality submenu JMenu qualityMenu = new JMenu("Quality"); qualityMenu.setIcon(getIcon("empty_icon.png")); for (int n = 0; n < 6; n++) { Action qualityAction = viewCtrl.getActionAdapter("quality_" + n); JMenuItem qualityMenuItem = new JMenuItem(qualityAction); qualityMenu.add(qualityMenuItem); } imageMenu.add(qualityMenu); JMenu aboutMenu = new JMenu("About"); aboutMenu.setMnemonic(KeyEvent.VK_A); aboutMenu.add(new JMenuItem(new ShowAboutDlgAction("About Photovault...", null, "", null))); menuBar.add(Box.createHorizontalGlue()); menuBar.add(aboutMenu); window.pack(); window.setVisible(true); } /** Create the toolbar for this browser window. */ protected JToolBar createToolbar() { JToolBar tb = new JToolBar(); JButton importBtn = new JButton(importAction); importBtn.setText(""); JButton indexBtn = new JButton(viewCtrl.getActionAdapter("new_ext_vol")); indexBtn.setText(""); JButton updateBtn = new JButton(viewCtrl.getActionAdapter("update_indexed_dirs")); updateBtn.setText(""); JButton exportBtn = new JButton(viewPane.getExportSelectedAction()); exportBtn.setText(""); JButton deleteBtn = new JButton(viewPane.getDeleteSelectedAction()); deleteBtn.setText(""); JButton rotCWBtn = new JButton(viewCtrl.getActionAdapter("rotate_cw")); rotCWBtn.setText(""); JButton rotCCWBtn = new JButton(viewCtrl.getActionAdapter("rotate_ccw")); rotCCWBtn.setText(""); JButton rot180Btn = new JButton(viewCtrl.getActionAdapter("rotate_180")); rot180Btn.setText(""); JButton cropBtn = new JButton(previewPane.getCropAction()); cropBtn.setText(""); JButton colorsBtn = new JButton(viewPane.getEditSelectionColorsAction()); colorsBtn.setText(""); JButton nextBtn = new JButton(viewPane.getSelectNextAction()); nextBtn.setText(""); JButton prevBtn = new JButton(viewPane.getSelectPreviousAction()); prevBtn.setText(""); JButton previewRightBtn = new JButton(getActionAdapter("view_preview_right")); previewRightBtn.setText(""); JButton previewTopBtn = new JButton(getActionAdapter("view_preview_top")); previewTopBtn.setText(""); JButton previewNoneBtn = new JButton(getActionAdapter("view_no_preview")); previewNoneBtn.setText(""); ZoomComboBox zoomCombo = new ZoomComboBox(previewPane); tb.add(importBtn); tb.add(indexBtn); tb.add(updateBtn); tb.add(exportBtn); tb.add(deleteBtn); tb.addSeparator(); tb.add(prevBtn); tb.add(nextBtn); tb.add(previewRightBtn); tb.add(previewTopBtn); tb.add(previewNoneBtn); tb.add(zoomCombo); tb.addSeparator(); tb.add(rotCWBtn); tb.add(rotCCWBtn); tb.add(rot180Btn); tb.add(cropBtn); tb.add(colorsBtn); return tb; } /** Loads an icon using class loader of this class @param resouceName Name of the icon reosurce to load @return The icon or <code>null</code> if no image was found using the given resource name. */ private ImageIcon getIcon(String resourceName) { ImageIcon icon = null; java.net.URL iconURL = JAIPhotoViewer.class.getClassLoader().getResource(resourceName); if (iconURL != null) { icon = new ImageIcon(iconURL); } return icon; } /** Set whether to show the query/tree view pane @param b if true, panel is shown, if false it is not shown. */ public void setShowCollectionPane(boolean b) { tabPane.setVisible(b); } /** Shows an file selection dialog that allows user to select a file to import. After that shows the PhotoInfo dialog to allow the user to edit the eriginal information about the file. */ protected void importFile() { // Show the file chooser dialog JFileChooser fc = new JFileChooser(); fc.addChoosableFileFilter(new ImageFilter()); fc.setAccessory(new ImagePreview(fc)); fc.setMultiSelectionEnabled(true); int retval = fc.showDialog(window, "Import"); if (retval == JFileChooser.APPROVE_OPTION) { // Add the selected file to the database and allow user to edit its attributes final File[] files = fc.getSelectedFiles(); final ProgressDlg pdlg = new ProgressDlg(window, true); // Add all the selected files to DB final PhotoInfo[] photos = new PhotoInfo[files.length]; Thread importThread = new Thread() { @Override public void run() { for (int n = 0; n < files.length; n++) { try { photos[n] = PhotoInfo.addToDB(files[n]); pdlg.setProgressPercent((n * 100) / files.length); pdlg.setStatus("" + (n + 1) + " of " + files.length + " files imported."); } catch (Exception e) { log.error("Unexpected exception: " + e.getMessage()); } } pdlg.completed(); } }; importThread.start(); pdlg.setVisible(true); // Show editor dialog for the added photos // PhotoInfoDlg dlg = new PhotoInfoDlg( window, false, photos ); PhotoInfoDlg dlg = null; dlg.showDialog(); } } /** Asks user to select a directory, creates an external volume from it and indexes all images in it. The actual indexing is done asynchronously in a separate thread. */ protected void indexDir() { IndexerFileChooser fc = new IndexerFileChooser(); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int retval = fc.showDialog(window, "Select directory to index"); if (retval == JFileChooser.APPROVE_OPTION) { File dir = fc.getSelectedFile(); // Generate a unique name for the new volume String extVolName = "extvol_" + dir.getName(); // if ( VolumeBase.getVolume( extVolName ) != null ) { // int n = 2; // while ( VolumeBase.getVolume( extVolName + n ) != null ) { // n++; // } // extVolName += n; // } String rootFolderName = "extvol_" + dir.getName(); CreateExternalVolume volCmd = null; try { volCmd = new CreateExternalVolume(dir, rootFolderName); viewCtrl.getCommandHandler().executeCommand(volCmd); // Start indexing BackgroundIndexer indexer = new BackgroundIndexer(dir, volCmd.getCreatedVolume(), true); SwingWorkerTaskScheduler sched = (SwingWorkerTaskScheduler) Photovault.getInstance() .getTaskScheduler(); sched.registerTaskProducer(indexer, TaskPriority.INDEX_EXTVOL); } catch (CommandException ex) { JOptionPane.showMessageDialog(window, ex.getMessage(), "Error creating volume", JOptionPane.ERROR_MESSAGE); } } } /** Set the title of this window. @param title The new title. */ void setTitle(String title) { window.setTitle(title); } protected JTabbedPane tabPane = null; protected JTabbedPane collectionTabPane = null; protected JScrollPane viewScroll = null; protected JPanel collectionPane = null; protected QueryPane queryPane = null; protected PhotoFolderTree treePane = null; // protected TableCollectionView viewPane = null; protected PhotoCollectionThumbView viewPane = null; protected JAIPhotoViewer previewPane = null; /** Action that imports a new image to Photovault archive */ AbstractAction importAction; /** *Status bar for this window */ private StatusBar statusBar; }