package timeflow.app; import timeflow.app.ui.*; import timeflow.app.actions.*; import timeflow.app.ui.filter.*; import timeflow.data.db.*; import timeflow.data.time.RoughTime; import timeflow.format.field.*; import timeflow.format.file.*; import timeflow.model.*; import timeflow.views.*; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import timeflow.util.Pad; import java.awt.*; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; import java.util.ArrayList; public class TimeflowApp extends JFrame { public TFModel model = new TFModel(); public JFileChooser fileChooser; AboutWindow splash; String[][] examples; String[] templates; AppState state = new AppState(); JMenu openRecent = new JMenu("Open Recent"); public JMenu filterMenu; JMenuItem save = new JMenuItem("Save"); FilterControlPanel filterControlPanel; LinkTabPane leftPanel; TFListener filterMenuMaker = new TFListener() { @Override public void note(TFEvent e) { if (e.affectsSchema()) { filterMenu.removeAll(); for (Field f : model.getDB().getFields()) { if (f.getType() == String.class || f.getType() == String[].class || f.getType() == Double.class || f.getType() == RoughTime.class) { final JCheckBoxMenuItem item = new JCheckBoxMenuItem(f.getName()); final Field field = f; filterMenu.add(item); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { filterControlPanel.setFacet(field, item.getState()); leftPanel.setSelectedIndex(1); } }); } } } } }; void splash(boolean visible) { splash.setVisible(visible); } public void init() throws Exception { Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); setBounds(0, 0, Math.min(d.width, 1200), Math.min(d.height, 900)); setTitle(Display.version()); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); final QuitAction quitAction = new QuitAction(this, model); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { quitAction.quit(); } public void windowStateChanged(WindowEvent e) { repaint(); } }); Image icon = Toolkit.getDefaultToolkit().getImage("images/icon.gif"); setIconImage(icon); // read example directory String[] ex = getVisibleFiles("settings/examples"); int n = ex.length; examples = new String[n][2]; for (int i = 0; i < n; i++) { String s = ex[i]; int dot = s.lastIndexOf('.'); if (dot >= 0 && dot < s.length() - 1); s = s.substring(0, dot); examples[i][0] = s; examples[i][1] = "settings/examples/" + ex[i]; } templates = getVisibleFiles("settings/templates"); fileChooser = new JFileChooser(state.getCurrentFile()); getContentPane().setLayout(new BorderLayout()); // left tab area, with vertical gray divider. JPanel leftHolder = new JPanel(); getContentPane().add(leftHolder, BorderLayout.WEST); leftHolder.setLayout(new BorderLayout()); JPanel pad = new Pad(3, 3); pad.setBackground(Color.gray); leftHolder.add(pad, BorderLayout.EAST); leftPanel = new LinkTabPane();//JTabbedPane(); leftHolder.add(leftPanel, BorderLayout.CENTER); JPanel configPanel = new JPanel(); configPanel.setLayout(new BorderLayout()); filterMenu = new JMenu("Filters"); filterControlPanel = new FilterControlPanel(model, filterMenu); final GlobalDisplayPanel displayPanel = new GlobalDisplayPanel(model, filterControlPanel); configPanel.add(displayPanel, BorderLayout.NORTH); JPanel legend = new JPanel(); legend.setLayout(new BorderLayout()); configPanel.add(legend, BorderLayout.CENTER); legend.add(new SizeLegendPanel(model), BorderLayout.NORTH); legend.add(new ColorLegendPanel(model), BorderLayout.CENTER); leftPanel.addTab(configPanel, "Display", true); leftPanel.addTab(filterControlPanel, "Filter", true); // center tab area final LinkTabPane center = new LinkTabPane(); getContentPane().add(center, BorderLayout.CENTER); center.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { displayPanel.showLocalControl(center.getCurrentName()); } }); final IntroView intro = new IntroView(model); // we refer to this a bit later. final TimelineView timeline = new TimelineView(model); AbstractView[] views = { timeline, new CalendarView(model), new ListView(model), new TableView(model), new BarGraphView(model), intro, new DescriptionView(model), new SummaryView(model), }; for (int i = 0; i < views.length; i++) { center.addTab(views[i], views[i].getName(), i < 5); displayPanel.addLocalControl(views[i].getName(), views[i].getControls()); } // start off with intro screen center.setCurrentName(intro.getName()); displayPanel.showLocalControl(intro.getName()); // but then, once data is loaded, switch directly to the timeline view. model.addListener(new TFListener() { @Override public void note(TFEvent e) { if (e.type == e.type.DATABASE_CHANGE) { if (center.getCurrentName().equals(intro.getName())) { center.setCurrentName(timeline.getName()); displayPanel.showLocalControl(timeline.getName()); } } } }); JMenuBar menubar = new JMenuBar(); setJMenuBar(menubar); JMenu fileMenu = new JMenu("File"); menubar.add(fileMenu); fileMenu.add(new NewDataAction(this)); fileMenu.add(new CopySchemaAction(this)); JMenu templateMenu = new JMenu("New From Template"); fileMenu.add(templateMenu); for (int i = 0; i < templates.length; i++) { JMenuItem t = new JMenuItem(templates[i]); final String fileName = "settings/templates/" + templates[i]; templateMenu.add(t); t.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { load(fileName, FileExtensionCatalog.get(fileName), true); } }); } fileMenu.addSeparator(); JMenuItem open = new JMenuItem("Open..."); fileMenu.add(open); open.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { load(new TimeflowFormat(), false); } }); fileMenu.add(openRecent); makeRecentFileMenu(); fileMenu.addSeparator(); fileMenu.add(new ImportFromPasteAction(this)); JMenuItem impDel = new JMenuItem("Import CSV/TSV..."); fileMenu.add(impDel); impDel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (checkSaveStatus()) { importDelimited(); } } }); fileMenu.addSeparator(); fileMenu.add(save); save.setAccelerator(KeyStroke.getKeyStroke('S', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); save.setEnabled(false); save.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { save(model.getDbFile()); } }); model.addListener(new TFListener() { @Override public void note(TFEvent e) { save.setEnabled(!model.getReadOnly()); } }); JMenuItem saveAs = new JMenuItem("Save As..."); fileMenu.add(saveAs); saveAs.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveAs(); } }); fileMenu.addSeparator(); JMenuItem exportTSV = new JMenuItem("Export TSV..."); fileMenu.add(exportTSV); exportTSV.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { exportDelimited('\t'); } }); JMenuItem exportCSV = new JMenuItem("Export CSV..."); fileMenu.add(exportCSV); exportCSV.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { exportDelimited(','); } }); JMenuItem exportHTML = new JMenuItem("Export HTML..."); fileMenu.add(exportHTML); exportHTML.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { exportHtml(); } }); fileMenu.addSeparator(); fileMenu.add(quitAction); JMenu editMenu = new JMenu("Edit"); menubar.add(editMenu); editMenu.add(new AddRecordAction(this)); editMenu.addSeparator(); editMenu.add(new DateFieldAction(this)); editMenu.add(new AddFieldAction(this)); editMenu.add(new RenameFieldAction(this)); editMenu.add(new DeleteFieldAction(this)); editMenu.add(new ReorderFieldsAction(this)); editMenu.addSeparator(); editMenu.add(new EditSourceAction(this)); editMenu.addSeparator(); editMenu.add(new DeleteSelectedAction(this)); editMenu.add(new DeleteUnselectedAction(this)); menubar.add(filterMenu); model.addListener(filterMenuMaker); JMenu exampleMenu = new JMenu("Examples"); menubar.add(exampleMenu); for (int i = 0; i < examples.length; i++) { JMenuItem example = new JMenuItem(examples[i][0]); exampleMenu.add(example); final String file = examples[i][1]; example.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { load(file, FileExtensionCatalog.get(file), true); } }); } JMenu helpMenu = new JMenu("Help"); menubar.add(helpMenu); helpMenu.add(new WebDocAction(this)); JMenuItem about = new JMenuItem("About TimeFlow"); helpMenu.add(about); about.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { splash(true); } }); model.addListener(new TFListener() { @Override public void note(TFEvent e) { if (e.type == TFEvent.Type.DATABASE_CHANGE) { String name = model.getDbFile(); int n = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')); if (n > 0) { name = name.substring(n + 1); } setTitle(name); } } }); } void makeRecentFileMenu() { openRecent.removeAll(); try { for (File f : state.getRecentFiles()) { final String file = f.getAbsolutePath(); JMenuItem m = new JMenuItem(f.getName()); openRecent.add(m); m.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { load(file, FileExtensionCatalog.get(file), false); } }); } } catch (Exception e) { e.printStackTrace(System.out); } } void exportHtml() { int retval = fileChooser.showSaveDialog(this); if (retval == fileChooser.APPROVE_OPTION) { String fileName = fileChooser.getSelectedFile().getAbsolutePath(); try { FileWriter fw = new FileWriter(fileName); BufferedWriter out = new BufferedWriter(fw); new HtmlFormat().export(model, out); out.close(); fw.close(); } catch (Exception e) { System.out.println(e); showUserError("Couldn't save file: " + e); } } } void exportDelimited(char delimiter) { int retval = fileChooser.showSaveDialog(this); if (retval == fileChooser.APPROVE_OPTION) { String fileName = fileChooser.getSelectedFile().getAbsolutePath(); try { new DelimitedFormat(delimiter).write(model.getDB(), new File(fileName)); } catch (Exception e) { System.out.println(e); showUserError("Couldn't save file: " + e); } } } void load(Import importer, boolean readOnly) { if (!checkSaveStatus()) { return; } try { int retval = fileChooser.showOpenDialog(this); if (retval == fileChooser.APPROVE_OPTION) { load(fileChooser.getSelectedFile().getAbsolutePath(), importer, readOnly); noteFileUse(fileChooser.getSelectedFile().getAbsolutePath()); } } catch (Exception e) { showUserError("Couldn't read file."); System.out.println(e); } } public void showImportEditor(String fileName, String[][] data) { final ImportDelimitedPanel editor = new ImportDelimitedPanel(model); editor.setFileName(fileName); editor.setData(data); editor.setBounds(0, 0, 1024, 768); editor.setVisible(true); SwingUtilities.invokeLater(new Runnable() { public void run() { editor.scrollToTop(); } }); } void importDelimited() { if (!checkSaveStatus()) { return; } try { int result = fileChooser.showOpenDialog(this); if (result == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); String fileName = file.getAbsolutePath(); noteFileUse(fileName); String[][] data = DelimitedFormat.readArrayGuessDelim(fileName, System.out); showImportEditor(fileName, data); } else { System.out.println("OK, canceling import."); } } catch (Exception e) { showUserError("Couldn't read file format."); e.printStackTrace(System.out); } } void load(final String fileName, final Import importer, boolean readOnly) { if (!checkSaveStatus()) { return; } try { final File f = new File(fileName); ActDB db = importer.importFile(f); model.setDB(db, fileName, readOnly, TimeflowApp.this); if (!readOnly) { noteFileUse(fileName); } } catch (Exception e) { e.printStackTrace(System.out); showUserError("Couldn't read file."); model.noteError(this); } } public boolean save(String fileName) { try { FileWriter fw = new FileWriter(fileName); BufferedWriter out = new BufferedWriter(fw); new TimeflowFormat().export(model, out); out.close(); fw.close(); noteFileUse(fileName); if (!fileName.equals(model.getDbFile())) { model.setDbFile(fileName, false, this); } model.setChangedSinceSave(false); model.setReadOnly(false); save.setEnabled(true); return true; } catch (Exception e) { e.printStackTrace(System.out); showUserError("Couldn't save file: " + e); return false; } } public boolean checkSaveStatus() { boolean needSave = model.isChangedSinceSave(); if (!needSave) { return true; } Object[] options = null; if (model.isReadOnly()) { options = new Object[] { "Save As", "Discard Changes", "Cancel" }; } else { options = new Object[] { "Save", "Save As", "Discard Changes", "Cancel" }; } int n = JOptionPane.showOptionDialog( this, "The current data set has unsaved changes that will be lost.\n" + "Would you like to save them before continuing?", "Save Before Closing?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, model.isReadOnly() ? "Save As" : "Save"); Object result = options[n]; if ("Discard Changes".equals(result)) { return true; } if ("Cancel".equals(result)) { return false; } if ("Save".equals(result)) { return save(model.getDbFile()); } // we are now at "save as..." return saveAs(); } public boolean saveAs() { File current = fileChooser.getSelectedFile(); if (current != null) { fileChooser.setSelectedFile(new File(current.getAbsolutePath() + " (copy)")); } int retval = fileChooser.showSaveDialog(this); if (retval == fileChooser.APPROVE_OPTION) { String fileName = fileChooser.getSelectedFile().getAbsolutePath(); model.setReadOnly(false); save.setEnabled(true); return save(fileName); } else { return false; } } public void showUserError(Object o) { JOptionPane.showMessageDialog(this, o, "A problem occurred", JOptionPane.ERROR_MESSAGE); if (o instanceof Exception) { ((Exception) o).printStackTrace(System.out); } } public void noteFileUse(String file) { state.setCurrentFile(new File(file)); state.save(); makeRecentFileMenu(); } public void clearFilters() { filterControlPanel.clearFilters(); } static String[] getVisibleFiles(String dir) { String[] s = new File(dir).list(); ArrayList real = new ArrayList(); for (int i = 0; i < s.length; i++) { if (!s[i].startsWith(".")) { real.add(s[i]); } } return (String[]) real.toArray(new String[0]); } }