/* GanttProject is an opensource project management tool. Copyright (C) 2011 GanttProject team This program 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. This program 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject.wizard; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.text.MessageFormat; import java.util.Timer; import java.util.TimerTask; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; import net.sourceforge.ganttproject.document.Document; import net.sourceforge.ganttproject.gui.TextFieldAndFileChooserComponent; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.gui.UIUtil; import net.sourceforge.ganttproject.gui.options.GPOptionChoicePanel; import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder; import net.sourceforge.ganttproject.language.GanttLanguage; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.osgi.service.prefs.Preferences; import com.google.common.base.Objects; import biz.ganttproject.core.option.DefaultBooleanOption; import biz.ganttproject.core.option.GPOptionGroup; public abstract class AbstractFileChooserPage implements WizardPage { public static final int FILE_SOURCE = 0; public static final int URL_SOURCE = 1; protected static final String PREF_SELECTED_FILE = "selected_file"; private static final String PREF_SELECTED_URL = "selected_url"; private static final FileFilter ACCEPT_ALL = new FileFilter() { @Override public String getDescription() { return null; } @Override public boolean accept(File f) { return true; } }; private JPanel myComponent; private TextFieldAndFileChooserComponent myChooser; private JTextField myUrlField; private final OptionsPageBuilder myOptionsBuilder; private final JPanel mySecondaryOptionsComponent; private int ourSelectedSource = AbstractFileChooserPage.FILE_SOURCE; private final boolean isUrlChooserEnabled; private final JLabel myFileLabel = new JLabel(" "); private final JLabel myUrlLabel = new JLabel(" "); private final Preferences myPreferences; private final UIFacade myUiFacade; private final FileFilter myFileFilter; private final GPOptionGroup[] myOptions; private final String myTitle; private AbstractWizard myWizard; protected AbstractFileChooserPage(UIFacade uiFacade, Preferences prefs, String title, FileFilter fileFilter, GPOptionGroup[] options, boolean enableUrlChooser) { myUiFacade = uiFacade; myPreferences = prefs; myTitle = title; myFileFilter = Objects.firstNonNull(fileFilter, ACCEPT_ALL); myOptions = Objects.firstNonNull(options, new GPOptionGroup[0]); isUrlChooserEnabled = enableUrlChooser; myOptionsBuilder = new OptionsPageBuilder(); mySecondaryOptionsComponent = new JPanel(new BorderLayout()); mySecondaryOptionsComponent.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); } protected int getFileChooserSelectionMode() { return JFileChooser.FILES_AND_DIRECTORIES; } @Override public JComponent getComponent() { myComponent = new JPanel(new BorderLayout()); myChooser = new TextFieldAndFileChooserComponent(myUiFacade, myTitle) { @Override protected void onFileChosen(File file) { AbstractFileChooserPage.this.onSelectedFileChange(file); } }; myChooser.setFileSelectionMode(getFileChooserSelectionMode()); JComponent contentPanel = new JPanel(new BorderLayout()); if (!isUrlChooserEnabled) { contentPanel.add(myChooser, BorderLayout.NORTH); } else { final UrlFetcher urlFetcher = new UrlFetcher() { @Override protected void onFetchComplete(File file) { super.onFetchComplete(file); onSelectedFileChange(file); } }; myUrlField = new JTextField(); Box urlBox = Box.createVerticalBox(); urlBox.add(myUrlField); urlBox.add(myUrlLabel); myUrlField.getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { onChange(); } @Override public void insertUpdate(DocumentEvent e) { onChange(); } @Override public void changedUpdate(DocumentEvent e) { onChange(); } private void onChange() { urlFetcher.setUrl(getSelectedUrl()); } }); Box fileBox = Box.createVerticalBox(); fileBox.add(myChooser); fileBox.add(myFileLabel); Action fileSourceAction = new AbstractAction(GanttLanguage.getInstance().getText("file")) { @Override public void actionPerformed(ActionEvent e) { ourSelectedSource = AbstractFileChooserPage.FILE_SOURCE; myChooser.tryFile(); } }; Action urlSourceAction = new AbstractAction(GanttLanguage.getInstance().getText("url")) { @Override public void actionPerformed(ActionEvent e) { ourSelectedSource = AbstractFileChooserPage.URL_SOURCE; urlFetcher.setStatusLabel(myUrlLabel); urlFetcher.setUrl(getSelectedUrl()); } }; Action[] importSourceActions = new Action[] { fileSourceAction, urlSourceAction }; JComponent[] importSourceComponents = new JComponent[] { fileBox, urlBox }; GPOptionChoicePanel sourceChoicePanel = new GPOptionChoicePanel(); Box sourceBox = Box.createVerticalBox(); sourceBox.add(sourceChoicePanel.getComponent(importSourceActions, importSourceComponents, ourSelectedSource)); sourceBox.add(Box.createVerticalStrut(5)); sourceBox.add(urlFetcher.getComponent()); contentPanel.add(sourceBox, BorderLayout.NORTH); } contentPanel.add(mySecondaryOptionsComponent, BorderLayout.CENTER); myComponent.add(contentPanel, BorderLayout.NORTH); return myComponent; } protected void loadPreferences() { String oldFile = myPreferences.get(AbstractFileChooserPage.PREF_SELECTED_FILE, null); if (oldFile != null) { // Use the previously used path with the current filename for the default // name // The implementing classes can modify the file extension when desired File f = new File(oldFile); onSelectedFileChange(f); myChooser.setFile(f); } if (myUrlField != null && myPreferences.get(AbstractFileChooserPage.PREF_SELECTED_URL, null) != null) { myUrlField.setText(myPreferences.get(AbstractFileChooserPage.PREF_SELECTED_URL, null)); } } @Override public void setActive(AbstractWizard wizard) { GPOptionGroup[] optionGroups = getOptionGroups(); myWizard = wizard; if (wizard == null) { // Means that user switched to either previous or next page if (myChooser.getFile() != null) { myPreferences.put(AbstractFileChooserPage.PREF_SELECTED_FILE, myChooser.getFile().getAbsolutePath()); } if (myUrlField != null) { myPreferences.put(AbstractFileChooserPage.PREF_SELECTED_URL, myUrlField.getText()); } } else { if (mySecondaryOptionsComponent != null) { mySecondaryOptionsComponent.removeAll(); } mySecondaryOptionsComponent.add(createSecondaryOptionsPanel(), BorderLayout.NORTH); myChooser.setFileFilter(myFileFilter); loadPreferences(); wizard.getDialog().layout(); } } protected Component createSecondaryOptionsPanel() { return myOptionsBuilder.buildPlanePage(getOptionGroups()); } protected final Preferences getPreferences() { return myPreferences; } protected final TextFieldAndFileChooserComponent getChooser() { return myChooser; } protected AbstractWizard getWizard() { return myWizard; } private URL getSelectedUrl() { try { switch (ourSelectedSource) { case FILE_SOURCE: return myChooser.getSelectedURL(); case URL_SOURCE: return new URL(myUrlField.getText()); default: assert false : "Should not be here"; return null; } } catch (MalformedURLException e) { reportMalformedUrl(e); return null; } } private void reportMalformedUrl(Exception e) { } protected void setOptionsEnabled(boolean enabled) { if (mySecondaryOptionsComponent != null) { setEnabledTree(mySecondaryOptionsComponent, enabled); } } private void setEnabledTree(JComponent root, boolean isEnabled) { UIUtil.setEnabledTree(root, isEnabled); } private GPOptionGroup[] getOptionGroups() { return myOptions; } protected abstract void setFile(File file); protected void onSelectedFileChange(File file) { IStatus status = getFileStatus(file); if (status.isOK()) { setFile(file); } else { setFile(null); } showStatus(myFileLabel, status); } private static IStatus getFileStatus(File file) { if (file == null) { return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File does not exist", null); } if (!file.exists()) { return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File does not exist", null); } if (!file.canRead()) { return new Status(IStatus.ERROR, "foo", IStatus.ERROR, "File read error", null); } return Status.OK_STATUS; } private static void showStatus(JLabel label, IStatus status) { label.setOpaque(true); if (status.isOK()) { label.setForeground(Color.BLACK); } else { label.setForeground(Color.RED); } label.setText(status.getMessage()); } class UrlFetcher { private final DefaultBooleanOption myProgressOption = new DefaultBooleanOption(""); private JLabel myStatusLabel; private final Timer myTimer = new Timer(); private boolean isFetching; private URL myUrl; private File myFetchedFile = null; public UrlFetcher() { } public void setStatusLabel(JLabel label) { myStatusLabel = label; } Component getComponent() { OptionsPageBuilder builder = new OptionsPageBuilder(); return builder.createWaitIndicatorComponent(myProgressOption); } void setUrl(final URL url) { synchronized (myTimer) { myUrl = url; if (isFetching) { return; } reschedule(); } } private void fetch() { myProgressOption.lock(); myProgressOption.toggle(); myProgressOption.commit(); try { URLConnection connection = myUrl.openConnection(); connection.connect(); File tempFile = File.createTempFile("gp-import-", ""); tempFile.deleteOnExit(); InputStream from = myUrl.openStream(); try { OutputStream to = new FileOutputStream(tempFile); try { byte[] buf = new byte[1024]; while (true) { int r = from.read(buf); if (r == -1) { break; } to.write(buf, 0, r); } myFetchedFile = tempFile; setStatus(new Status(IStatus.OK, "foo", IStatus.OK, MessageFormat.format("Successfully fetched from {0}", new Object[] { myUrl }), null)); } finally { to.flush(); to.close(); } } finally { from.close(); } } catch (IOException e) { setStatus(new Status(IStatus.ERROR, "foo", IStatus.ERROR, MessageFormat.format("Failed to fetch from {0}\n{1}", new Object[] { myUrl, e.getMessage() }), e)); } finally { isFetching = false; myProgressOption.lock(); myProgressOption.toggle(); myProgressOption.commit(); onFetchComplete(myFetchedFile); } } private void setStatus(IStatus status) { AbstractFileChooserPage.showStatus(myStatusLabel, status); } protected void onFetchComplete(File file) { onSelectedFileChange(file); } private void reschedule() { if (myUrl == null || myUrl.getHost() == null || myUrl.getHost().length() == 0) { onFetchComplete(null); return; } myFetchedFile = null; onFetchComplete(null); isFetching = true; myTimer.schedule(new TimerTask() { final URL myUrlAtStart = myUrl; @Override public void run() { synchronized (myTimer) { if (!myUrlAtStart.equals(myUrl)) { reschedule(); } else { fetch(); } } } }, 3000); } } }