package net.sf.jpluck.apps.jpluckx;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import net.sf.jpluck.ClientConfiguration;
import net.sf.jpluck.VersionInfo;
import net.sf.jpluck.apps.ExitCodes;
import net.sf.jpluck.apps.OptionsUtil;
import net.sf.jpluck.apps.jpluckx.ui.AutoUpdateFrame;
import net.sf.jpluck.apps.jpluckx.ui.ListFrame;
import net.sf.jpluck.apps.jpluckx.ui.PreferencesDialog;
import net.sf.jpluck.apps.jpluckx.ui.SplashScreen;
import net.sf.jpluck.conversion.Conversion;
import net.sf.jpluck.conversion.Destination;
import net.sf.jpluck.http.HttpClient;
import net.sf.jpluck.jxl.Document;
import net.sf.jpluck.jxl.JXL;
import net.sf.jpluck.spider.Spider;
import net.sf.jpluck.swing.GUIUtil;
import net.sf.jpluck.swing.TableListSelection;
import net.sf.jpluck.ui.ConversionDialog;
import net.sf.jpluck.ui.ConversionPane;
import net.sf.jpluck.ui.ExceptionDialog;
import net.sf.jpluck.util.Platform;
import net.sf.jpluck.util.StringTemplate;
import net.sf.jpluck.util.URIUtil;
import net.sf.jpluck.util.win32.Shell;
import net.sf.jpluck.web.Command;
import net.sf.jpluck.web.CommandHandler;
import net.sf.jpluck.web.CommandServer;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import com.jeans.trayicon.TrayIconPopup;
import com.jeans.trayicon.TrayIconPopupSeparator;
import com.jeans.trayicon.TrayIconPopupSimpleItem;
import com.jeans.trayicon.WindowsTrayIcon;
import edu.stanford.ejalbert.BrowserLauncher;
public class JPluckX implements ClipboardOwner, CommandHandler, PreferenceChangeListener {
private static JPluckX instance;
private static SplashScreen splashScreen;
private ActivationListener activationListener = new ActivationListener();
private AutoUpdateFrame autoUpdateFrame = new AutoUpdateFrame();
private Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
private CommandServer commandServer = new CommandServer(9126, this);
private Conversion autoUpdateConversion;
private List listFrameList = new ArrayList();
private ListFrame activeListFrame;
private Preferences prefs = Preferences.userRoot().node("Software/JPluck 2");
private StringTemplate backTemplate = StringTemplate.fromClassPath("net/sf/jpluck/res/pluckthis/back.html");
private StringTemplate conversionActiveTemplate = StringTemplate.fromClassPath("net/sf/jpluck/res/pluckthis/conversionActive.html");
private StringTemplate dialogActiveTemplate = StringTemplate.fromClassPath("net/sf/jpluck/res/pluckthis/dialogActive.html");
private StringTemplate popupTemplate = StringTemplate.fromClassPath("net/sf/jpluck/res/pluckthis/popup.html");
private StringTemplate summaryTemplate = StringTemplate.fromClassPath("net/sf/jpluck/res/pluckthis/summary.html");
private StringTemplate switchJXLTemplate = StringTemplate.fromClassPath("net/sf/jpluck/res/pluckthis/switchJXL.html");
private Timer autoUpdateTimer;
private WindowsTrayIcon trayIcon;
private boolean autoUpdateRunning;
private boolean clipboardOwner = false;
private boolean dialogActive;
private boolean minimizedToTrayIcon;
private int autoUpdateInterval;
private boolean webStart;
private JPluckX() {
if (Platform.isWindows()) {
try {
WindowsTrayIcon.initTrayIcon("JPluckX");
Image icon = ImageIO.read(JPluckX.class.getClassLoader().getResource("net/sf/jpluck/res/jpluck16.gif"));
trayIcon = new WindowsTrayIcon(icon, 16, 16);
trayIcon.setToolTipText("JPluck X (click to activate)");
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
trayIcon.setVisible(false);
activeListFrame.show();
}
});
TrayIconPopup popup = new TrayIconPopup("Menu");
TrayIconPopupSimpleItem showPopup = new TrayIconPopupSimpleItem("Show");
showPopup.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
trayIcon.setVisible(false);
activeListFrame.show();
}
});
showPopup.setDefault(true);
popup.addMenuItem(showPopup);
TrayIconPopupSimpleItem autoUpdatePopup = new TrayIconPopupSimpleItem("AutoUpdate");
autoUpdatePopup.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
autoUpdateFrame.show();
autoUpdateFrame.toFront();
}
});
popup.addMenuItem(autoUpdatePopup);
popup.addMenuItem(new TrayIconPopupSeparator());
TrayIconPopupSimpleItem exitPopup = new TrayIconPopupSimpleItem("Exit");
exitPopup.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
exit();
}
});
popup.addMenuItem(exitPopup);
trayIcon.setPopup(popup);
} catch (Exception e) {
}
}
ClientConfiguration conf = ClientConfiguration.getDefault();
conf.restoreAutoUpdateFramePrefs(autoUpdateFrame);
autoUpdateInterval = conf.getAutoUpdateInterval();
Preferences prefs = ClientConfiguration.getDefaultPreferencesNode();
prefs.node("autoupdate").addPreferenceChangeListener(this);
}
public static JPluckX getInstance() {
if (instance == null) {
instance = new JPluckX();
}
return instance;
}
public static void main(String[] arguments) {
try {
File homeDir = new File(System.getProperty("user.home") + File.separator + ".jpluck2");
if (!homeDir.exists()) {
homeDir.mkdirs();
}
Options options = OptionsUtil.createGeneric();
options.addOption("offline", false, "Run in offline mode. Retrieve resources from HTTP cache only.");
options.addOption("webstart", false, "Run from WebStart.");
if (Platform.isWindows()) {
options.addOption("trayicon", false, "Start as tray icon.");
}
CommandLine cl = new GnuParser().parse(options, arguments);
String[] args = cl.getArgs();
if (cl.hasOption("help")) {
OptionsUtil.printHelp("java -jar jpluckx.jar [options] [jxl file]", "GUI for managing JXLs.", options);
System.exit(ExitCodes.OK);
}
if (cl.hasOption("version")) {
OptionsUtil.printVersion("JPluckX", VersionInfo.VERSION, VersionInfo.RELEASE_DATE);
System.exit(ExitCodes.OK);
}
Spider.setOffline(cl.hasOption("offline"));
if (ClientConfiguration.getDefault().isShowSplashScreen()) {
splashScreen = new SplashScreen();
splashScreen.show();
}
GUIUtil.configureTextFieldKeyBindings();
UIManager.installLookAndFeel("Motif (JGoodies)", "com.jgoodies.plaf.motif.ExtMotifLookAndFeel");
UIManager.installLookAndFeel("Plastic", "com.jgoodies.plaf.plastic.PlasticLookAndFeel");
UIManager.installLookAndFeel("Plastic3D", "com.jgoodies.plaf.plastic.Plastic3DLookAndFeel");
UIManager.installLookAndFeel("PlasticXP", "com.jgoodies.plaf.plastic.PlasticXPLookAndFeel");
if (Platform.isWindows()) {
UIManager.installLookAndFeel("Windows (JGoodies)", "com.jgoodies.plaf.windows.ExtWindowsLookAndFeel");
}
try {
ClientConfiguration conf = ClientConfiguration.getDefault();
conf.applyPlasticTheme();
UIManager.setLookAndFeel(conf.getLookAndFeel());
} catch (Exception e) {
e.printStackTrace();
}
JPluckX jpluckx = JPluckX.getInstance();
jpluckx.webStart = cl.hasOption("webstart");
try {
jpluckx.commandServer.start();
} catch (IOException e) {
// JPluck X already active
HttpClient httpClient = new HttpClient();
try {
String jxls = "";
for (int i = 0; i < args.length; i++) {
File file = new File(args[i]);
jxls += ("jxl" + i + "=" + URLEncoder.encode(file.getAbsolutePath(), "ISO-8859-1"));
if (i < (args.length - 1)) {
jxls += "&";
}
}
httpClient.doGet("http://127.0.0.1:9126/activate?" + jxls);
} catch (Exception e2) {
}
System.exit(ExitCodes.OK);
}
ListFrame listFrame = null;
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
try {
JXL jxl = new JXL(new File(args[i]).getAbsoluteFile());
listFrame = jpluckx.newListFrame(jxl);
} catch (Exception e) {
ExceptionDialog.show(null, e);
}
}
} else {
String s = ClientConfiguration.getDefault().getLastJXL();
if (s != null) {
File f = new File(s);
if (f.exists()) {
try {
JXL jxl = new JXL(f);
listFrame = jpluckx.newListFrame(jxl);
} catch (Exception e) {
ExceptionDialog.show(jpluckx.activeListFrame, e);
}
}
}
}
if (listFrame == null) {
listFrame = jpluckx.newListFrame();
}
if (cl.hasOption("trayicon") && (jpluckx.trayIcon != null)) {
jpluckx.trayIcon.setVisible(true);
} else {
listFrame.show();
jpluckx.checkDestination();
}
if (ClientConfiguration.getDefault().isAutoUpdateEnabled()) {
jpluckx.startAutoUpdate();
}
} catch (ParseException e) {
System.err.println(e.getMessage());
System.exit(ExitCodes.ERROR_INVALID_OPTION);
}
}
public Document[] getClipboardContents() {
Document[] documents = null;
if (clipboardOwner) {
try {
Object[] items = (Object[]) clipboard.getContents(null).getTransferData(TableListSelection.FLAVOR);
documents = new Document[items.length];
for (int i = 0; i < items.length; i++) {
documents[i] = (Document) items[i];
}
} catch (Exception e) {
// Should not occur
e.printStackTrace();
}
} else {
documents = new Document[0];
}
return documents;
}
public boolean isClipboardOwner() {
return clipboardOwner;
}
public void setDialogActive(boolean dialogActive) {
this.dialogActive = dialogActive;
}
public Preferences getPreferences() {
return prefs;
}
public boolean checkDestination() {
Destination destination = ClientConfiguration.getDefault().getDestination();
if (destination == null) {
int result = JOptionPane.showConfirmDialog(activeListFrame,
"You have not yet configured a destination for the Plucker documents.\n" +
"The destination must be configured before documents can be converted.\n\n" +
"Do you want to configure the destination now?",
"Configure Destination", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
PreferencesDialog dlg = new PreferencesDialog(activeListFrame);
dlg.show();
return !dlg.isCancelled();
} else {
return false;
}
} else {
return true;
}
}
public void closeAllListFrames(boolean exit) {
for (int i = 0, n = listFrameList.size(); i < n; i++) {
if (!closeListFrame((ListFrame) listFrameList.get(0), exit)) {
break;
}
}
}
public boolean closeListFrame(ListFrame listFrame, boolean exit) {
if (listFrame.canClose()) {
listFrame.dispose();
if ((listFrameList.size() == 1) && ClientConfiguration.getDefault().isMinimizeToTrayIcon() &&
(trayIcon != null) && !exit) {
trayIcon.setVisible(true);
return true;
}
listFrame.removeWindowListener(activationListener);
listFrameList.remove(listFrame);
if (listFrameList.size() == 0) {
ClientConfiguration conf = ClientConfiguration.getDefault();
conf.storeListFramePrefs(listFrame);
exit();
}
return true;
} else {
return false;
}
}
public void copyToClipboard(Transferable selection) {
clipboardOwner = true;
clipboard.setContents(selection, this);
for (int i = 0, n = listFrameList.size(); i < n; i++) {
ListFrame listFrame = (ListFrame) listFrameList.get(i);
listFrame.enablePasteAction();
}
}
// This method is synchronized to avoid two requests being processed at the same time.
public synchronized void handle(Command command) {
String path = command.getUri().getPath();
if (path.equals("/addSite")) {
if (dialogActive) {
if (Boolean.valueOf(command.getParameter("plugin")).booleanValue()) {
command.respond("A dialog is currently active.");
} else {
command.respond(dialogActiveTemplate.process());
}
} else if (Conversion.isConversionActive()) {
if (Boolean.valueOf(command.getParameter("plugin")).booleanValue()) {
command.respond("A conversion is currently active.");
} else {
command.respond(conversionActiveTemplate.process());
}
} else {
String name = command.getParameter("name");
String url = command.getParameter("url");
if ((name == null) || (url == null)) {
throw new IllegalArgumentException("Name or URL not specified.");
}
name=URIUtil.unescape(name);
url = URIUtil.unescape(url);
String jxl = ClientConfiguration.getDefault().getPluckThisJXL();
if (trayIcon != null) {
minimizedToTrayIcon = trayIcon.isVisible();
trayIcon.setVisible(false);
}
activeListFrame.setVisible(true);
activeListFrame.toFront();
if (jxl.length() > 0) {
File jxlFile = new File(jxl);
if (!jxlFile.equals(activeListFrame.getJXL().getFile())) {
if (activeListFrame.canClose()) {
activeListFrame.openJXL(jxlFile);
} else {
if (Boolean.valueOf(command.getParameter("plugin")).booleanValue()) {
command.respond("Cannot switch to PluckThis JXL.");
} else {
command.respond(switchJXLTemplate.process());
}
return;
}
}
}
activeListFrame.addSite(name, url);
if (Boolean.valueOf(command.getParameter("back")).booleanValue()) {
command.respond(backTemplate.process());
} else if (Boolean.valueOf(command.getParameter("plugin")).booleanValue()) {
command.respond("OK");
} else if (Boolean.valueOf(command.getParameter("popup")).booleanValue()) {
popupTemplate.put("name", name);
popupTemplate.put("url", url);
command.respond(popupTemplate.process());
} else {
summaryTemplate.put("name", name);
summaryTemplate.put("url", url);
command.respond(summaryTemplate.process());
}
}
} else if (path.equals("/activate")) {
activeListFrame.show();
int i = 0;
while (true) {
String filename = command.getParameter("jxl" + i);
if (filename == null) {
break;
}
filename = filename.replace('+', ' ');
i++;
if (filename.endsWith(".jxl") || filename.endsWith(".xml")) {
try {
JXL jxl = new JXL(new File(filename));
newListFrame(jxl).show();
} catch (Exception e) {
ExceptionDialog.show(activeListFrame, e);
}
}
}
if (trayIcon != null) {
trayIcon.setVisible(false);
}
activeListFrame.setVisible(true);
activeListFrame.toFront();
command.respond("OK");
}
}
public void lostOwnership(Clipboard clipboard, Transferable contents) {
clipboardOwner = false;
for (int i = 0, n = listFrameList.size(); i < n; i++) {
ListFrame listFrame = (ListFrame) listFrameList.get(i);
listFrame.disablePasteAction();
}
}
public ListFrame newListFrame() {
return newListFrame(new JXL());
}
public ListFrame newListFrame(JXL jxl) {
for (Iterator it = listFrameList.iterator(); it.hasNext();) {
ListFrame listFrame = (ListFrame) it.next();
File file = listFrame.getJXL().getFile();
if ((file != null) && file.equals(jxl.getFile())) {
activeListFrame = listFrame;
return listFrame;
}
}
ListFrame listFrame = new ListFrame(jxl);
listFrame.addWindowListener(activationListener);
listFrameList.add(listFrame);
ClientConfiguration.getDefault().restoreListFramePrefs(listFrame);
if (splashScreen != null) {
splashScreen.hide();
splashScreen.dispose();
splashScreen = null;
}
if (activeListFrame != null) {
int x = activeListFrame.getX() + 30;
int y = activeListFrame.getY() + 30;
listFrame.setLocation(x, y);
}
activeListFrame = listFrame;
return listFrame;
}
public void preferenceChange(PreferenceChangeEvent evt) {
int autoUpdateInterval = ClientConfiguration.getDefault().getAutoUpdateInterval();
if (this.autoUpdateInterval != autoUpdateInterval) {
this.autoUpdateInterval = autoUpdateInterval;
if (ClientConfiguration.getDefault().isAutoUpdateEnabled()) {
//System.err.println("Restarting AutoUpdate due to preference change.");
stopAutoUpdate();
startAutoUpdate();
}
}
}
public void showAutoUpdate() {
autoUpdateFrame.show();
}
public void startAutoUpdate() {
if (autoUpdateTimer != null) {
autoUpdateTimer.cancel();
}
autoUpdateFrame.setAutoUpdate(true);
autoUpdateTimer = new Timer(true);
autoUpdateTimer.scheduleAtFixedRate(new AutoUpdateTask(), 0, autoUpdateInterval * 60 * 1000);
}
public void stopAutoUpdate() {
if (autoUpdateConversion != null) {
autoUpdateConversion.cancel();
}
if (autoUpdateTimer != null) {
autoUpdateTimer.cancel();
}
autoUpdateFrame.setAutoUpdate(false);
}
public boolean wasMinimizedToTrayIcon() {
return minimizedToTrayIcon;
}
private void exit() {
stopAutoUpdate();
ClientConfiguration conf = ClientConfiguration.getDefault();
conf.storeAutoUpdateFramePrefs(autoUpdateFrame);
conf.cleanup();
if (Platform.isWindows() && (trayIcon != null)) {
trayIcon.freeIcon();
WindowsTrayIcon.cleanUp();
}
System.exit(ExitCodes.OK);
}
public void convert(JFrame parent, Document[] documents) {
if (!JPluckX.getInstance().checkDestination()) {
JOptionPane.showMessageDialog(parent,
"Documents cannot be converted until the destination is configured.",
"No Destination", JOptionPane.WARNING_MESSAGE);
return;
}
if (Conversion.isConversionActive()) {
JOptionPane.showMessageDialog(parent,
"A conversion is currently active.\n\n" +
"Wait until the current conversion has finished and try again.",
"Conversion Already Active", JOptionPane.INFORMATION_MESSAGE);
return;
}
if (ClientConfiguration.getDefault().isAutoUpdateEnabled()) {
JPluckX.getInstance().stopAutoUpdate();
}
Conversion conversion = new Conversion(documents);
ConversionDialog conversionDialog = new ConversionDialog(parent, conversion);
conversionDialog.setLocationRelativeTo(parent);
ConversionPane conversionPane = conversionDialog.getConversionPane();
conversion.addConversionListener(conversionDialog);
conversion.addConversionListener(conversionPane);
conversion.addSpiderListener(conversionPane);
conversion.addRecordListener(conversionPane);
conversion.setWindow(conversionDialog);
conversionDialog.show();
conversionDialog.dispose();
conversion.removeRecordListener(conversionPane);
conversion.removeSpiderListener(conversionPane);
conversion.removeConversionListener(conversionPane);
conversion.removeConversionListener(conversionDialog);
if (ClientConfiguration.getDefault().isAutoUpdateEnabled()) {
JPluckX.getInstance().startAutoUpdate();
}
}
public void openURL(String url, JFrame owner) {
if (Platform.isWindows()) {
Shell.open(url);
} else if (Platform.isMacOSX()) {
try {
BrowserLauncher.openURL(url);
} catch (IOException e) {
JOptionPane.showMessageDialog(owner,
"An error occurred while launching the web browser:\n" +
e.getMessage(), "Error launching browser", JOptionPane.ERROR_MESSAGE);
}
} else {
String browser = ClientConfiguration.getDefault().getBrowser();
if (browser.length() == 0) {
int result = JOptionPane.showConfirmDialog(owner,
"You have not yet configured the\nweb browser.\n\n" +
"Configure the web browser now?", "Configure Web Browser",
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
browser = PreferencesDialog.configureBrowser(owner);
}
}
if (browser.length() > 0) {
try {
Runtime.getRuntime().exec(browser + " " + url);
} catch (IOException e) {
int result = JOptionPane.showConfirmDialog(owner,
"An error occurred while launching the web browser:\n" +
e.getMessage() +
"\n\nReconfigure the web browser now?",
"Error launching browser", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
browser = PreferencesDialog.configureBrowser(owner);
}
}
}
}
}
public void copyFromShowcase(Document document) {
try {
JXL jxl = activeListFrame.getJXL();
jxl.addCopyOf(document);
jxl.save();
} catch (Exception e) {
ExceptionDialog.show(activeListFrame, e);
}
}
public boolean isWebStart() {
return webStart;
}
private class ActivationListener extends WindowAdapter {
public void windowActivated(WindowEvent e) {
activeListFrame = (ListFrame) e.getWindow();
}
public void windowGainedFocus(WindowEvent e) {
activeListFrame = (ListFrame) e.getWindow();
}
}
private class AutoUpdateTask extends TimerTask {
public void run() {
if (Conversion.isConversionActive()) {
return;
}
ClientConfiguration conf = ClientConfiguration.getDefault();
if (conf.isAutoUpdatePeriodEnabled() && !conf.getAutoUpdatePeriod().withinPeriod()) {
return;
}
File[] files = conf.getAutoUpdateJXLs();
Map jxlMap = new HashMap();
for (Iterator it = listFrameList.iterator(); it.hasNext();) {
ListFrame listFrame = (ListFrame) it.next();
JXL jxl = listFrame.getJXL();
jxlMap.put(jxl.getFile(), jxl);
}
List documentList = new ArrayList();
for (int i = 0; i < files.length; i++) {
try {
File file = files[i];
JXL jxl = (JXL) jxlMap.get(file);
if (jxl == null) {
jxl = new JXL(file);
}
Document[] documents = jxl.getDueDocuments();
Arrays.sort(documents);
documentList.addAll(Arrays.asList(documents));
} catch (Exception e) {
// TODO: Log exceptions
}
}
if (documentList.size() > 0) {
Document[] documents = (Document[]) documentList.toArray(new Document[documentList.size()]);
autoUpdateConversion = new Conversion(documents);
autoUpdateFrame.runConversion(autoUpdateConversion);
autoUpdateConversion = null;
}
}
}
}