Normand Briere
2018-07-07 e416acb9b012b17d1efe49ad2199ea7132d874d1
timeline
132 files added
14288 ■■■■■ changed files
timeflow/app/AboutWindow.java 53 ●●●●● patch | view | raw | blame | history
timeflow/app/AppState.java 107 ●●●●● patch | view | raw | blame | history
timeflow/app/TimeflowApp.java 709 ●●●●● patch | view | raw | blame | history
timeflow/app/TimeflowAppLauncher.java 52 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/AddFieldAction.java 49 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/AddRecordAction.java 26 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/CopySchemaAction.java 29 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/DateFieldAction.java 24 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/DeleteFieldAction.java 43 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/DeleteSelectedAction.java 51 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/DeleteUnselectedAction.java 40 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/EditSourceAction.java 37 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/ImportFromPasteAction.java 53 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/NewDataAction.java 27 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/QuitAction.java 32 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/RenameFieldAction.java 100 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/ReorderFieldsAction.java 38 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/TimeflowAction.java 36 ●●●●● patch | view | raw | blame | history
timeflow/app/actions/WebDocAction.java 25 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/AddFieldPanel.java 24 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/ColorLegendPanel.java 63 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/ComponentCluster.java 37 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/DateFieldPanel.java 178 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/DottedLine.java 34 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/EditRecordPanel.java 134 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/EditValuePanel.java 111 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/GlobalDisplayPanel.java 174 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/HtmlDisplay.java 24 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/ImportDelimitedPanel.java 310 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/LinkTabPane.java 208 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/MassDeletePanel.java 80 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/ReorderFieldsPanel.java 68 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/SizeLegendPanel.java 113 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/StatusPanel.java 98 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/WaitingDialog.java 56 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/BabyHistogram.java 274 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/FilterCategoryPanel.java 164 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/FilterControlPanel.java 208 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/FilterDatePanel.java 173 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/FilterDefinitionPanel.java 10 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/FilterNumberPanel.java 171 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/FilterTitle.java 52 ●●●●● patch | view | raw | blame | history
timeflow/app/ui/filter/SearchPanel.java 52 ●●●●● patch | view | raw | blame | history
timeflow/data/analysis/DBAnalysis.java 14 ●●●●● patch | view | raw | blame | history
timeflow/data/analysis/FieldAnalysis.java 13 ●●●●● patch | view | raw | blame | history
timeflow/data/analysis/FrequencyAnalysis.java 76 ●●●●● patch | view | raw | blame | history
timeflow/data/analysis/MissingValueAnalysis.java 44 ●●●●● patch | view | raw | blame | history
timeflow/data/analysis/RangeDateAnalysis.java 62 ●●●●● patch | view | raw | blame | history
timeflow/data/analysis/RangeNumberAnalysis.java 85 ●●●●● patch | view | raw | blame | history
timeflow/data/db/Act.java 25 ●●●●● patch | view | raw | blame | history
timeflow/data/db/ActComparator.java 118 ●●●●● patch | view | raw | blame | history
timeflow/data/db/ActDB.java 30 ●●●●● patch | view | raw | blame | history
timeflow/data/db/ActList.java 23 ●●●●● patch | view | raw | blame | history
timeflow/data/db/ArrayDB.java 348 ●●●●● patch | view | raw | blame | history
timeflow/data/db/BasicAct.java 89 ●●●●● patch | view | raw | blame | history
timeflow/data/db/BasicDB.java 129 ●●●●● patch | view | raw | blame | history
timeflow/data/db/DBUtils.java 234 ●●●●● patch | view | raw | blame | history
timeflow/data/db/Field.java 50 ●●●●● patch | view | raw | blame | history
timeflow/data/db/Schema.java 130 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/ActFilter.java 14 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/AndFilter.java 50 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/ConstFilter.java 24 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/FieldValueFilter.java 37 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/FieldValueSetFilter.java 40 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/MissingValueFilter.java 27 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/NotFilter.java 24 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/NumericRangeFilter.java 28 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/OrFilter.java 38 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/StringMatchFilter.java 69 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/TimeIntervalFilter.java 35 ●●●●● patch | view | raw | blame | history
timeflow/data/db/filter/ValueFilter.java 5 ●●●●● patch | view | raw | blame | history
timeflow/data/time/Interval.java 114 ●●●●● patch | view | raw | blame | history
timeflow/data/time/RoughTime.java 128 ●●●●● patch | view | raw | blame | history
timeflow/data/time/TimeUnit.java 243 ●●●●● patch | view | raw | blame | history
timeflow/data/time/TimeUtils.java 21 ●●●●● patch | view | raw | blame | history
timeflow/format/field/DateTimeGuesser.java 115 ●●●●● patch | view | raw | blame | history
timeflow/format/field/DateTimeParser.java 39 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FieldFormat.java 64 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FieldFormatCatalog.java 54 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FieldFormatGuesser.java 50 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FormatDateTime.java 79 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FormatDouble.java 103 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FormatString.java 44 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FormatStringArray.java 59 ●●●●● patch | view | raw | blame | history
timeflow/format/field/FormatURL.java 41 ●●●●● patch | view | raw | blame | history
timeflow/format/file/DelimitedFormat.java 217 ●●●●● patch | view | raw | blame | history
timeflow/format/file/DelimitedText.java 214 ●●●●● patch | view | raw | blame | history
timeflow/format/file/Export.java 10 ●●●●● patch | view | raw | blame | history
timeflow/format/file/FileExtensionCatalog.java 28 ●●●●● patch | view | raw | blame | history
timeflow/format/file/HtmlFormat.java 143 ●●●●● patch | view | raw | blame | history
timeflow/format/file/Import.java 9 ●●●●● patch | view | raw | blame | history
timeflow/format/file/TimeflowFormat.java 163 ●●●●● patch | view | raw | blame | history
timeflow/model/Display.java 373 ●●●●● patch | view | raw | blame | history
timeflow/model/ModelPanel.java 36 ●●●●● patch | view | raw | blame | history
timeflow/model/TFEvent.java 43 ●●●●● patch | view | raw | blame | history
timeflow/model/TFListener.java 5 ●●●●● patch | view | raw | blame | history
timeflow/model/TFModel.java 303 ●●●●● patch | view | raw | blame | history
timeflow/model/VirtualField.java 39 ●●●●● patch | view | raw | blame | history
timeflow/util/Bag.java 136 ●●●●● patch | view | raw | blame | history
timeflow/util/ColorUtils.java 31 ●●●●● patch | view | raw | blame | history
timeflow/util/DoubleBag.java 145 ●●●●● patch | view | raw | blame | history
timeflow/util/IO.java 54 ●●●●● patch | view | raw | blame | history
timeflow/util/Pad.java 19 ●●●●● patch | view | raw | blame | history
timeflow/util/TimeIt.java 34 ●●●●● patch | view | raw | blame | history
timeflow/views/AbstractView.java 85 ●●●●● patch | view | raw | blame | history
timeflow/views/AbstractVisualizationView.java 244 ●●●●● patch | view | raw | blame | history
timeflow/views/BarGraphView.java 363 ●●●●● patch | view | raw | blame | history
timeflow/views/CalendarView.java 304 ●●●●● patch | view | raw | blame | history
timeflow/views/DescriptionView.java 71 ●●●●● patch | view | raw | blame | history
timeflow/views/HtmlControls.java 21 ●●●●● patch | view | raw | blame | history
timeflow/views/IntroView.java 105 ●●●●● patch | view | raw | blame | history
timeflow/views/ListView.java 266 ●●●●● patch | view | raw | blame | history
timeflow/views/SummaryView.java 181 ●●●●● patch | view | raw | blame | history
timeflow/views/TableView.java 267 ●●●●● patch | view | raw | blame | history
timeflow/views/TimelineView.java 465 ●●●●● patch | view | raw | blame | history
timeflow/vis/GroupVisualAct.java 98 ●●●●● patch | view | raw | blame | history
timeflow/vis/Mouseover.java 94 ●●●●● patch | view | raw | blame | history
timeflow/vis/MouseoverLabel.java 32 ●●●●● patch | view | raw | blame | history
timeflow/vis/TagVisualAct.java 52 ●●●●● patch | view | raw | blame | history
timeflow/vis/TimeScale.java 95 ●●●●● patch | view | raw | blame | history
timeflow/vis/VisualAct.java 314 ●●●●● patch | view | raw | blame | history
timeflow/vis/VisualActFactory.java 119 ●●●●● patch | view | raw | blame | history
timeflow/vis/VisualEncoder.java 138 ●●●●● patch | view | raw | blame | history
timeflow/vis/calendar/CalendarVisuals.java 113 ●●●●● patch | view | raw | blame | history
timeflow/vis/calendar/Grid.java 375 ●●●●● patch | view | raw | blame | history
timeflow/vis/calendar/GridCell.java 20 ●●●●● patch | view | raw | blame | history
timeflow/vis/timeline/AxisRenderer.java 88 ●●●●● patch | view | raw | blame | history
timeflow/vis/timeline/AxisTicMarks.java 117 ●●●●● patch | view | raw | blame | history
timeflow/vis/timeline/TimelineRenderer.java 184 ●●●●● patch | view | raw | blame | history
timeflow/vis/timeline/TimelineSlider.java 239 ●●●●● patch | view | raw | blame | history
timeflow/vis/timeline/TimelineTrack.java 129 ●●●●● patch | view | raw | blame | history
timeflow/vis/timeline/TimelineVisuals.java 345 ●●●●● patch | view | raw | blame | history
timeflow/app/AboutWindow.java
....@@ -0,0 +1,53 @@
1
+package timeflow.app;
2
+
3
+import timeflow.model.*;
4
+
5
+import java.awt.*;
6
+import java.io.File;
7
+import java.util.*;
8
+import javax.imageio.*;
9
+
10
+public class AboutWindow extends Window {
11
+
12
+ Image image;
13
+ Display display;
14
+ int w=640, h=380;
15
+
16
+ public AboutWindow(Frame owner, Display display) {
17
+ super(owner);
18
+ this.display=display;
19
+
20
+ try
21
+ {
22
+ // image=ImageIO.read(getClass().getClassLoader().getResourceAsStream("images/splash.jpg"));
23
+ image=ImageIO.read(new File("images/splash.jpg"));
24
+ w=image.getWidth(null);
25
+ h=image.getHeight(null);
26
+ }
27
+ catch (Exception e)
28
+ {
29
+ e.printStackTrace(System.out);
30
+ }
31
+ Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
32
+ setBounds((size.width-w)/2, (size.height-h)/2, w,h);
33
+
34
+ }
35
+
36
+ public void paint(Graphics g)
37
+ {
38
+ if (image!=null)
39
+ {
40
+ g.drawImage(image,0,0,null);
41
+ return;
42
+ }
43
+ int lx=15;
44
+ ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
45
+ g.setColor(display.getColor("splash.background"));
46
+ g.fillRect(0,0,w,h);
47
+ g.setFont(display.huge());
48
+ g.setColor(display.getColor("splash.text"));
49
+ g.drawString(Display.version(), lx, 35);
50
+ g.setFont(display.plain());
51
+ g.drawString("Prototype version",lx,60);
52
+ }
53
+}
timeflow/app/AppState.java
....@@ -0,0 +1,107 @@
1
+package timeflow.app;
2
+
3
+import timeflow.util.*;
4
+
5
+import java.io.*;
6
+import java.util.*;
7
+
8
+public class AppState
9
+{
10
+
11
+ private static final String FILE = "settings/info.txt";
12
+ private File currentFile, currentDir;
13
+ private LinkedList<File> recentFiles = new LinkedList<File>();
14
+
15
+ public AppState()
16
+ {
17
+ if (!new File(FILE).exists())
18
+ {
19
+ System.err.println("No existing settings file found.");
20
+ return;
21
+ }
22
+ try
23
+ {
24
+ for (String line : IO.lines(FILE))
25
+ {
26
+ String[] t = line.split("\t");
27
+ String command = t[0];
28
+ String arg = t[1];
29
+ if ("CURRENT_FILE".equals(command))
30
+ {
31
+ currentFile = new File(arg);
32
+ } else if ("RECENT_FILE".equals(command))
33
+ {
34
+ recentFiles.add(new File(arg).getAbsoluteFile());
35
+ } else if ("CURRENT_DIR".equals(command))
36
+ {
37
+ currentDir = new File(arg);
38
+ }
39
+ }
40
+ } catch (Exception e)
41
+ {
42
+ e.printStackTrace(System.out);
43
+ }
44
+ }
45
+
46
+ public List<File> getRecentFiles()
47
+ {
48
+ return (List<File>) recentFiles.clone();
49
+ }
50
+
51
+ public File getCurrentFile()
52
+ {
53
+ return currentFile;
54
+ }
55
+
56
+ public void setCurrentFile(File currentFile)
57
+ {
58
+ this.currentFile = currentFile.getAbsoluteFile();
59
+
60
+ // if list is big, remove one at end.
61
+ if (recentFiles.size() > 10)
62
+ {
63
+ recentFiles.removeLast();
64
+ }
65
+
66
+ // put at front of list
67
+ if (recentFiles.contains(this.currentFile))
68
+ {
69
+ recentFiles.remove(this.currentFile);
70
+ }
71
+ recentFiles.addFirst(this.currentFile);
72
+
73
+ // set current dir, too.
74
+ this.currentDir = currentDir;
75
+ }
76
+
77
+ public File getCurrentDir()
78
+ {
79
+ return currentDir;
80
+ }
81
+
82
+ public void setCurrentDir(File currentDir)
83
+ {
84
+ this.currentDir = currentDir;
85
+ }
86
+
87
+ public void save()
88
+ {
89
+ try
90
+ {
91
+ FileOutputStream fos = new FileOutputStream(FILE);
92
+ PrintStream out = new PrintStream(fos);
93
+ out.println("CURRENT_FILE\t" + currentFile);
94
+ out.println("CURRENT_DIR\t" + currentDir);
95
+ for (File f : recentFiles)
96
+ {
97
+ out.println("RECENT_FILE\t" + f);
98
+ }
99
+ out.flush();
100
+ out.close();
101
+ fos.close();
102
+ } catch (Exception e)
103
+ {
104
+ e.printStackTrace(System.out);
105
+ }
106
+ }
107
+}
timeflow/app/TimeflowApp.java
....@@ -0,0 +1,709 @@
1
+package timeflow.app;
2
+
3
+import timeflow.app.ui.*;
4
+import timeflow.app.actions.*;
5
+import timeflow.app.ui.filter.*;
6
+import timeflow.data.db.*;
7
+import timeflow.data.time.RoughTime;
8
+import timeflow.format.field.*;
9
+import timeflow.format.file.*;
10
+import timeflow.model.*;
11
+import timeflow.views.*;
12
+
13
+import javax.swing.*;
14
+import javax.swing.event.ChangeEvent;
15
+import javax.swing.event.ChangeListener;
16
+
17
+import timeflow.util.Pad;
18
+
19
+import java.awt.*;
20
+import java.awt.event.*;
21
+import java.beans.PropertyChangeEvent;
22
+import java.beans.PropertyChangeListener;
23
+import java.io.*;
24
+import java.util.ArrayList;
25
+
26
+public class TimeflowApp extends JFrame
27
+{
28
+
29
+ public TFModel model = new TFModel();
30
+ public JFileChooser fileChooser;
31
+ AboutWindow splash;
32
+ String[][] examples;
33
+ String[] templates;
34
+ AppState state = new AppState();
35
+ JMenu openRecent = new JMenu("Open Recent");
36
+ public JMenu filterMenu;
37
+ JMenuItem save = new JMenuItem("Save");
38
+ FilterControlPanel filterControlPanel;
39
+ LinkTabPane leftPanel;
40
+ TFListener filterMenuMaker = new TFListener()
41
+ {
42
+
43
+ @Override
44
+ public void note(TFEvent e)
45
+ {
46
+ if (e.affectsSchema())
47
+ {
48
+ filterMenu.removeAll();
49
+ for (Field f : model.getDB().getFields())
50
+ {
51
+ if (f.getType() == String.class || f.getType() == String[].class
52
+ || f.getType() == Double.class || f.getType() == RoughTime.class)
53
+ {
54
+ final JCheckBoxMenuItem item = new JCheckBoxMenuItem(f.getName());
55
+ final Field field = f;
56
+ filterMenu.add(item);
57
+ item.addActionListener(new ActionListener()
58
+ {
59
+
60
+ @Override
61
+ public void actionPerformed(ActionEvent e)
62
+ {
63
+ filterControlPanel.setFacet(field, item.getState());
64
+ leftPanel.setSelectedIndex(1);
65
+ }
66
+ });
67
+ }
68
+ }
69
+ }
70
+ }
71
+ };
72
+
73
+ void splash(boolean visible)
74
+ {
75
+ splash.setVisible(visible);
76
+ }
77
+
78
+ public void init() throws Exception
79
+ {
80
+ Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
81
+ setBounds(0, 0, Math.min(d.width, 1200), Math.min(d.height, 900));
82
+ setTitle(Display.version());
83
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
84
+ final QuitAction quitAction = new QuitAction(this, model);
85
+ addWindowListener(new WindowAdapter()
86
+ {
87
+
88
+ @Override
89
+ public void windowClosing(WindowEvent e)
90
+ {
91
+ quitAction.quit();
92
+ }
93
+
94
+ public void windowStateChanged(WindowEvent e)
95
+ {
96
+ repaint();
97
+ }
98
+ });
99
+ Image icon = Toolkit.getDefaultToolkit().getImage("images/icon.gif");
100
+ setIconImage(icon);
101
+
102
+ // read example directory
103
+ String[] ex = getVisibleFiles("settings/examples");
104
+ int n = ex.length;
105
+ examples = new String[n][2];
106
+ for (int i = 0; i < n; i++)
107
+ {
108
+ String s = ex[i];
109
+ int dot = s.lastIndexOf('.');
110
+ if (dot >= 0 && dot < s.length() - 1);
111
+ s = s.substring(0, dot);
112
+ examples[i][0] = s;
113
+ examples[i][1] = "settings/examples/" + ex[i];
114
+ }
115
+ templates = getVisibleFiles("settings/templates");
116
+ fileChooser = new JFileChooser(state.getCurrentFile());
117
+
118
+ getContentPane().setLayout(new BorderLayout());
119
+
120
+ // left tab area, with vertical gray divider.
121
+ JPanel leftHolder = new JPanel();
122
+ getContentPane().add(leftHolder, BorderLayout.WEST);
123
+
124
+ leftHolder.setLayout(new BorderLayout());
125
+ JPanel pad = new Pad(3, 3);
126
+ pad.setBackground(Color.gray);
127
+ leftHolder.add(pad, BorderLayout.EAST);
128
+
129
+ leftPanel = new LinkTabPane();//JTabbedPane();
130
+ leftHolder.add(leftPanel, BorderLayout.CENTER);
131
+
132
+ JPanel configPanel = new JPanel();
133
+ configPanel.setLayout(new BorderLayout());
134
+ filterMenu = new JMenu("Filters");
135
+ filterControlPanel = new FilterControlPanel(model, filterMenu);
136
+ final GlobalDisplayPanel displayPanel = new GlobalDisplayPanel(model, filterControlPanel);
137
+ configPanel.add(displayPanel, BorderLayout.NORTH);
138
+
139
+ JPanel legend = new JPanel();
140
+ legend.setLayout(new BorderLayout());
141
+ configPanel.add(legend, BorderLayout.CENTER);
142
+ legend.add(new SizeLegendPanel(model), BorderLayout.NORTH);
143
+ legend.add(new ColorLegendPanel(model), BorderLayout.CENTER);
144
+ leftPanel.addTab(configPanel, "Display", true);
145
+
146
+ leftPanel.addTab(filterControlPanel, "Filter", true);
147
+
148
+ // center tab area
149
+
150
+ final LinkTabPane center = new LinkTabPane();
151
+ getContentPane().add(center, BorderLayout.CENTER);
152
+
153
+ center.addPropertyChangeListener(new PropertyChangeListener()
154
+ {
155
+
156
+ @Override
157
+ public void propertyChange(PropertyChangeEvent evt)
158
+ {
159
+ displayPanel.showLocalControl(center.getCurrentName());
160
+ }
161
+ });
162
+
163
+ final IntroView intro = new IntroView(model); // we refer to this a bit later.
164
+ final TimelineView timeline = new TimelineView(model);
165
+ AbstractView[] views =
166
+ {
167
+ timeline,
168
+ new CalendarView(model),
169
+ new ListView(model),
170
+ new TableView(model),
171
+ new BarGraphView(model),
172
+ intro,
173
+ new DescriptionView(model),
174
+ new SummaryView(model),
175
+ };
176
+
177
+ for (int i = 0; i < views.length; i++)
178
+ {
179
+ center.addTab(views[i], views[i].getName(), i < 5);
180
+ displayPanel.addLocalControl(views[i].getName(), views[i].getControls());
181
+ }
182
+
183
+ // start off with intro screen
184
+ center.setCurrentName(intro.getName());
185
+ displayPanel.showLocalControl(intro.getName());
186
+
187
+ // but then, once data is loaded, switch directly to the timeline view.
188
+ model.addListener(new TFListener()
189
+ {
190
+
191
+ @Override
192
+ public void note(TFEvent e)
193
+ {
194
+ if (e.type == e.type.DATABASE_CHANGE)
195
+ {
196
+ if (center.getCurrentName().equals(intro.getName()))
197
+ {
198
+ center.setCurrentName(timeline.getName());
199
+ displayPanel.showLocalControl(timeline.getName());
200
+ }
201
+ }
202
+ }
203
+ });
204
+
205
+ JMenuBar menubar = new JMenuBar();
206
+ setJMenuBar(menubar);
207
+
208
+ JMenu fileMenu = new JMenu("File");
209
+ menubar.add(fileMenu);
210
+
211
+ fileMenu.add(new NewDataAction(this));
212
+ fileMenu.add(new CopySchemaAction(this));
213
+
214
+ JMenu templateMenu = new JMenu("New From Template");
215
+ fileMenu.add(templateMenu);
216
+ for (int i = 0; i < templates.length; i++)
217
+ {
218
+ JMenuItem t = new JMenuItem(templates[i]);
219
+ final String fileName = "settings/templates/" + templates[i];
220
+ templateMenu.add(t);
221
+ t.addActionListener(new ActionListener()
222
+ {
223
+
224
+ @Override
225
+ public void actionPerformed(ActionEvent e)
226
+ {
227
+ load(fileName, FileExtensionCatalog.get(fileName), true);
228
+ }
229
+ });
230
+ }
231
+
232
+ fileMenu.addSeparator();
233
+
234
+
235
+ JMenuItem open = new JMenuItem("Open...");
236
+ fileMenu.add(open);
237
+ open.addActionListener(new ActionListener()
238
+ {
239
+
240
+ @Override
241
+ public void actionPerformed(ActionEvent e)
242
+ {
243
+ load(new TimeflowFormat(), false);
244
+ }
245
+ });
246
+
247
+
248
+ fileMenu.add(openRecent);
249
+ makeRecentFileMenu();
250
+ fileMenu.addSeparator();
251
+ fileMenu.add(new ImportFromPasteAction(this));
252
+
253
+ JMenuItem impDel = new JMenuItem("Import CSV/TSV...");
254
+ fileMenu.add(impDel);
255
+ impDel.addActionListener(new ActionListener()
256
+ {
257
+
258
+ @Override
259
+ public void actionPerformed(ActionEvent e)
260
+ {
261
+ if (checkSaveStatus())
262
+ {
263
+ importDelimited();
264
+ }
265
+ }
266
+ });
267
+
268
+ fileMenu.addSeparator();
269
+
270
+ fileMenu.add(save);
271
+ save.setAccelerator(KeyStroke.getKeyStroke('S',
272
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
273
+ save.setEnabled(false);
274
+ save.addActionListener(new ActionListener()
275
+ {
276
+
277
+ @Override
278
+ public void actionPerformed(ActionEvent e)
279
+ {
280
+ save(model.getDbFile());
281
+
282
+ }
283
+ });
284
+ model.addListener(new TFListener()
285
+ {
286
+
287
+ @Override
288
+ public void note(TFEvent e)
289
+ {
290
+ save.setEnabled(!model.getReadOnly());
291
+ }
292
+ });
293
+
294
+ JMenuItem saveAs = new JMenuItem("Save As...");
295
+ fileMenu.add(saveAs);
296
+ saveAs.addActionListener(new ActionListener()
297
+ {
298
+
299
+ @Override
300
+ public void actionPerformed(ActionEvent e)
301
+ {
302
+ saveAs();
303
+ }
304
+ });
305
+
306
+ fileMenu.addSeparator();
307
+
308
+ JMenuItem exportTSV = new JMenuItem("Export TSV...");
309
+ fileMenu.add(exportTSV);
310
+ exportTSV.addActionListener(new ActionListener()
311
+ {
312
+
313
+ @Override
314
+ public void actionPerformed(ActionEvent e)
315
+ {
316
+ exportDelimited('\t');
317
+ }
318
+ });
319
+ JMenuItem exportCSV = new JMenuItem("Export CSV...");
320
+ fileMenu.add(exportCSV);
321
+ exportCSV.addActionListener(new ActionListener()
322
+ {
323
+
324
+ @Override
325
+ public void actionPerformed(ActionEvent e)
326
+ {
327
+ exportDelimited(',');
328
+ }
329
+ });
330
+ JMenuItem exportHTML = new JMenuItem("Export HTML...");
331
+ fileMenu.add(exportHTML);
332
+ exportHTML.addActionListener(new ActionListener()
333
+ {
334
+
335
+ @Override
336
+ public void actionPerformed(ActionEvent e)
337
+ {
338
+ exportHtml();
339
+ }
340
+ });
341
+ fileMenu.addSeparator();
342
+ fileMenu.add(quitAction);
343
+
344
+ JMenu editMenu = new JMenu("Edit");
345
+ menubar.add(editMenu);
346
+ editMenu.add(new AddRecordAction(this));
347
+ editMenu.addSeparator();
348
+ editMenu.add(new DateFieldAction(this));
349
+ editMenu.add(new AddFieldAction(this));
350
+ editMenu.add(new RenameFieldAction(this));
351
+ editMenu.add(new DeleteFieldAction(this));
352
+ editMenu.add(new ReorderFieldsAction(this));
353
+ editMenu.addSeparator();
354
+ editMenu.add(new EditSourceAction(this));
355
+ editMenu.addSeparator();
356
+ editMenu.add(new DeleteSelectedAction(this));
357
+ editMenu.add(new DeleteUnselectedAction(this));
358
+
359
+ menubar.add(filterMenu);
360
+ model.addListener(filterMenuMaker);
361
+
362
+
363
+ JMenu exampleMenu = new JMenu("Examples");
364
+ menubar.add(exampleMenu);
365
+
366
+ for (int i = 0; i < examples.length; i++)
367
+ {
368
+ JMenuItem example = new JMenuItem(examples[i][0]);
369
+ exampleMenu.add(example);
370
+ final String file = examples[i][1];
371
+ example.addActionListener(new ActionListener()
372
+ {
373
+
374
+ @Override
375
+ public void actionPerformed(ActionEvent e)
376
+ {
377
+ load(file, FileExtensionCatalog.get(file), true);
378
+ }
379
+ });
380
+ }
381
+
382
+ JMenu helpMenu = new JMenu("Help");
383
+ menubar.add(helpMenu);
384
+
385
+ helpMenu.add(new WebDocAction(this));
386
+
387
+ JMenuItem about = new JMenuItem("About TimeFlow");
388
+ helpMenu.add(about);
389
+ about.addActionListener(new ActionListener()
390
+ {
391
+
392
+ @Override
393
+ public void actionPerformed(ActionEvent e)
394
+ {
395
+ splash(true);
396
+ }
397
+ });
398
+
399
+ model.addListener(new TFListener()
400
+ {
401
+
402
+ @Override
403
+ public void note(TFEvent e)
404
+ {
405
+ if (e.type == TFEvent.Type.DATABASE_CHANGE)
406
+ {
407
+ String name = model.getDbFile();
408
+ int n = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
409
+ if (n > 0)
410
+ {
411
+ name = name.substring(n + 1);
412
+ }
413
+ setTitle(name);
414
+ }
415
+ }
416
+ });
417
+ }
418
+
419
+ void makeRecentFileMenu()
420
+ {
421
+ openRecent.removeAll();
422
+ try
423
+ {
424
+ for (File f : state.getRecentFiles())
425
+ {
426
+ final String file = f.getAbsolutePath();
427
+ JMenuItem m = new JMenuItem(f.getName());
428
+ openRecent.add(m);
429
+ m.addActionListener(new ActionListener()
430
+ {
431
+
432
+ @Override
433
+ public void actionPerformed(ActionEvent e)
434
+ {
435
+ load(file, FileExtensionCatalog.get(file), false);
436
+ }
437
+ });
438
+ }
439
+ } catch (Exception e)
440
+ {
441
+ e.printStackTrace(System.out);
442
+ }
443
+ }
444
+
445
+ void exportHtml()
446
+ {
447
+ int retval = fileChooser.showSaveDialog(this);
448
+ if (retval == fileChooser.APPROVE_OPTION)
449
+ {
450
+ String fileName = fileChooser.getSelectedFile().getAbsolutePath();
451
+ try
452
+ {
453
+ FileWriter fw = new FileWriter(fileName);
454
+ BufferedWriter out = new BufferedWriter(fw);
455
+ new HtmlFormat().export(model, out);
456
+ out.close();
457
+ fw.close();
458
+ } catch (Exception e)
459
+ {
460
+ System.out.println(e);
461
+ showUserError("Couldn't save file: " + e);
462
+ }
463
+ }
464
+ }
465
+
466
+ void exportDelimited(char delimiter)
467
+ {
468
+ int retval = fileChooser.showSaveDialog(this);
469
+ if (retval == fileChooser.APPROVE_OPTION)
470
+ {
471
+ String fileName = fileChooser.getSelectedFile().getAbsolutePath();
472
+ try
473
+ {
474
+ new DelimitedFormat(delimiter).write(model.getDB(), new File(fileName));
475
+ } catch (Exception e)
476
+ {
477
+ System.out.println(e);
478
+ showUserError("Couldn't save file: " + e);
479
+ }
480
+ }
481
+ }
482
+
483
+ void load(Import importer, boolean readOnly)
484
+ {
485
+ if (!checkSaveStatus())
486
+ {
487
+ return;
488
+ }
489
+ try
490
+ {
491
+ int retval = fileChooser.showOpenDialog(this);
492
+ if (retval == fileChooser.APPROVE_OPTION)
493
+ {
494
+ load(fileChooser.getSelectedFile().getAbsolutePath(), importer, readOnly);
495
+ noteFileUse(fileChooser.getSelectedFile().getAbsolutePath());
496
+ }
497
+ } catch (Exception e)
498
+ {
499
+ showUserError("Couldn't read file.");
500
+ System.out.println(e);
501
+ }
502
+ }
503
+
504
+ public void showImportEditor(String fileName, String[][] data)
505
+ {
506
+ final ImportDelimitedPanel editor = new ImportDelimitedPanel(model);
507
+ editor.setFileName(fileName);
508
+ editor.setData(data);
509
+ editor.setBounds(0, 0, 1024, 768);
510
+ editor.setVisible(true);
511
+ SwingUtilities.invokeLater(new Runnable()
512
+ {
513
+
514
+ public void run()
515
+ {
516
+ editor.scrollToTop();
517
+ }
518
+ });
519
+
520
+ }
521
+
522
+ void importDelimited()
523
+ {
524
+ if (!checkSaveStatus())
525
+ {
526
+ return;
527
+ }
528
+ try
529
+ {
530
+ int result = fileChooser.showOpenDialog(this);
531
+
532
+ if (result == JFileChooser.APPROVE_OPTION)
533
+ {
534
+ File file = fileChooser.getSelectedFile();
535
+ String fileName = file.getAbsolutePath();
536
+ noteFileUse(fileName);
537
+ String[][] data = DelimitedFormat.readArrayGuessDelim(fileName, System.out);
538
+ showImportEditor(fileName, data);
539
+
540
+ } else
541
+ {
542
+ System.out.println("OK, canceling import.");
543
+ }
544
+ } catch (Exception e)
545
+ {
546
+ showUserError("Couldn't read file format.");
547
+ e.printStackTrace(System.out);
548
+ }
549
+ }
550
+
551
+ void load(final String fileName, final Import importer, boolean readOnly)
552
+ {
553
+ if (!checkSaveStatus())
554
+ {
555
+ return;
556
+ }
557
+ try
558
+ {
559
+ final File f = new File(fileName);
560
+ ActDB db = importer.importFile(f);
561
+ model.setDB(db, fileName, readOnly, TimeflowApp.this);
562
+ if (!readOnly)
563
+ {
564
+ noteFileUse(fileName);
565
+ }
566
+ } catch (Exception e)
567
+ {
568
+ e.printStackTrace(System.out);
569
+ showUserError("Couldn't read file.");
570
+ model.noteError(this);
571
+ }
572
+ }
573
+
574
+ public boolean save(String fileName)
575
+ {
576
+ try
577
+ {
578
+ FileWriter fw = new FileWriter(fileName);
579
+ BufferedWriter out = new BufferedWriter(fw);
580
+ new TimeflowFormat().export(model, out);
581
+ out.close();
582
+ fw.close();
583
+ noteFileUse(fileName);
584
+ if (!fileName.equals(model.getDbFile()))
585
+ {
586
+ model.setDbFile(fileName, false, this);
587
+ }
588
+ model.setChangedSinceSave(false);
589
+ model.setReadOnly(false);
590
+ save.setEnabled(true);
591
+ return true;
592
+ } catch (Exception e)
593
+ {
594
+ e.printStackTrace(System.out);
595
+ showUserError("Couldn't save file: " + e);
596
+ return false;
597
+ }
598
+ }
599
+
600
+ public boolean checkSaveStatus()
601
+ {
602
+ boolean needSave = model.isChangedSinceSave();
603
+ if (!needSave)
604
+ {
605
+ return true;
606
+ }
607
+
608
+ Object[] options = null;
609
+ if (model.isReadOnly())
610
+ {
611
+ options = new Object[]
612
+ {
613
+ "Save As", "Discard Changes", "Cancel"
614
+ };
615
+ } else
616
+ {
617
+ options = new Object[]
618
+ {
619
+ "Save", "Save As", "Discard Changes", "Cancel"
620
+ };
621
+ }
622
+ int n = JOptionPane.showOptionDialog(
623
+ this,
624
+ "The current data set has unsaved changes that will be lost.\n"
625
+ + "Would you like to save them before continuing?",
626
+ "Save Before Closing?",
627
+ JOptionPane.YES_NO_OPTION,
628
+ JOptionPane.QUESTION_MESSAGE,
629
+ null,
630
+ options,
631
+ model.isReadOnly() ? "Save As" : "Save");
632
+ Object result = options[n];
633
+ if ("Discard Changes".equals(result))
634
+ {
635
+ return true;
636
+ }
637
+ if ("Cancel".equals(result))
638
+ {
639
+ return false;
640
+ }
641
+ if ("Save".equals(result))
642
+ {
643
+ return save(model.getDbFile());
644
+ }
645
+
646
+ // we are now at "save as..."
647
+ return saveAs();
648
+ }
649
+
650
+ public boolean saveAs()
651
+ {
652
+ File current = fileChooser.getSelectedFile();
653
+ if (current != null)
654
+ {
655
+ fileChooser.setSelectedFile(new File(current.getAbsolutePath() + " (copy)"));
656
+ }
657
+ int retval = fileChooser.showSaveDialog(this);
658
+ if (retval == fileChooser.APPROVE_OPTION)
659
+ {
660
+ String fileName = fileChooser.getSelectedFile().getAbsolutePath();
661
+ model.setReadOnly(false);
662
+ save.setEnabled(true);
663
+ return save(fileName);
664
+ } else
665
+ {
666
+ return false;
667
+ }
668
+ }
669
+
670
+ public void showUserError(Object o)
671
+ {
672
+ JOptionPane.showMessageDialog(this,
673
+ o,
674
+ "A problem occurred",
675
+ JOptionPane.ERROR_MESSAGE);
676
+ if (o instanceof Exception)
677
+ {
678
+ ((Exception) o).printStackTrace(System.out);
679
+ }
680
+ }
681
+
682
+ public void noteFileUse(String file)
683
+ {
684
+
685
+ state.setCurrentFile(new File(file));
686
+ state.save();
687
+ makeRecentFileMenu();
688
+
689
+ }
690
+
691
+ public void clearFilters()
692
+ {
693
+ filterControlPanel.clearFilters();
694
+ }
695
+
696
+ static String[] getVisibleFiles(String dir)
697
+ {
698
+ String[] s = new File(dir).list();
699
+ ArrayList<String> real = new ArrayList<String>();
700
+ for (int i = 0; i < s.length; i++)
701
+ {
702
+ if (!s[i].startsWith("."))
703
+ {
704
+ real.add(s[i]);
705
+ }
706
+ }
707
+ return (String[]) real.toArray(new String[0]);
708
+ }
709
+}
timeflow/app/TimeflowAppLauncher.java
....@@ -0,0 +1,52 @@
1
+package timeflow.app;
2
+
3
+import timeflow.model.*;
4
+
5
+import javax.swing.*;
6
+import java.awt.event.*;
7
+
8
+// For some reason we have to do this in a separate class in order to
9
+// get the menubar working right on the Mac.
10
+
11
+public class TimeflowAppLauncher {
12
+ public static void main(String[] args) throws Exception
13
+ {
14
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
15
+ System.setProperty("com.apple.mrj.application.apple.menu.about.name", "TimeFlow");
16
+ System.out.println("Running "+Display.version());
17
+
18
+ try {
19
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
20
+ }
21
+ catch (Exception e) {
22
+ System.out.println("Can't set system look & feel");
23
+ }
24
+
25
+ final TimeflowApp t=new TimeflowApp();
26
+ t.splash=new AboutWindow(t, t.model.getDisplay());
27
+ t.splash(true);
28
+ SwingUtilities.invokeLater(new Runnable() {
29
+ @Override
30
+ public void run() {
31
+ try
32
+ {
33
+ t.init();
34
+ t.setVisible(true);
35
+ }
36
+ catch (Exception e)
37
+ {
38
+ e.printStackTrace(System.out);
39
+ }
40
+ t.splash.addMouseListener(new MouseAdapter() {
41
+
42
+ @Override
43
+ public void mouseClicked(MouseEvent e) {
44
+ t.splash.setVisible(false);
45
+ }}
46
+ );
47
+ t.splash(false);
48
+ //t.splash.message=t.model.getDisplay().version();
49
+ }});
50
+
51
+ }
52
+}
timeflow/app/actions/AddFieldAction.java
....@@ -0,0 +1,49 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+
9
+import java.awt.event.*;
10
+import javax.swing.*;
11
+import java.util.*;
12
+
13
+public class AddFieldAction extends TimeflowAction {
14
+
15
+ public AddFieldAction(TimeflowApp app)
16
+ {
17
+ super(app, "Add Field...", null, "Add a field to this database");
18
+ }
19
+
20
+ @Override
21
+ public void actionPerformed(ActionEvent e) {
22
+ AddFieldPanel p=new AddFieldPanel();
23
+ Object[] options = {"Cancel", "Add Field"};
24
+ int n = JOptionPane.showOptionDialog(app,
25
+ p,
26
+ "Add New Field To Database",
27
+ JOptionPane.YES_NO_CANCEL_OPTION,
28
+ JOptionPane.QUESTION_MESSAGE,
29
+ null,
30
+ options,
31
+ "Add Field");
32
+ if (n==1)
33
+ {
34
+ String fieldName=p.name.getText();
35
+ TFModel model=getModel();
36
+ if (fieldName.trim().length()==0)
37
+ app.showUserError("Field names can't be all spaces!");
38
+ else if (model.getDB().getField(fieldName)!=null)
39
+ app.showUserError("That name is already taken!");
40
+ else
41
+ {
42
+ model.getDB().addField(fieldName, FieldFormatCatalog.javaClass((String)p.typeChoices.getSelectedItem()));
43
+ model.noteAddField(this);
44
+ }
45
+ }
46
+ else
47
+ System.out.println("Canceled!");
48
+ }
49
+}
timeflow/app/actions/AddRecordAction.java
....@@ -0,0 +1,26 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+
9
+import java.awt.Toolkit;
10
+import java.awt.event.*;
11
+import javax.swing.*;
12
+import java.util.*;
13
+
14
+public class AddRecordAction extends TimeflowAction {
15
+
16
+ public AddRecordAction(TimeflowApp app)
17
+ {
18
+ super(app, "Add Record...", null, "Add a record to this database");
19
+ accelerate('A');
20
+ }
21
+
22
+ @Override
23
+ public void actionPerformed(ActionEvent e) {
24
+ EditRecordPanel.add(getModel());
25
+ }
26
+}
timeflow/app/actions/CopySchemaAction.java
....@@ -0,0 +1,29 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+
9
+import java.awt.event.*;
10
+import javax.swing.*;
11
+import java.util.*;
12
+
13
+public class CopySchemaAction extends TimeflowAction {
14
+
15
+ public CopySchemaAction(TimeflowApp app)
16
+ {
17
+ super(app, "New With Same Fields", null,
18
+ "Create a new, blank database with same fields as the current one.");
19
+ }
20
+
21
+ public void actionPerformed(ActionEvent e)
22
+ {
23
+ java.util.List<Field> fields=getModel().getDB().getFields();
24
+ ActDB db=new BasicDB("Unspecified");
25
+ for (Field f: fields)
26
+ db.addField(f.getName(), f.getType());
27
+ getModel().setDB(db, "[new data]", true, this);
28
+ }
29
+}
timeflow/app/actions/DateFieldAction.java
....@@ -0,0 +1,24 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+
9
+import java.awt.event.*;
10
+import javax.swing.*;
11
+import java.util.*;
12
+
13
+public class DateFieldAction extends TimeflowAction {
14
+
15
+ public DateFieldAction(TimeflowApp app)
16
+ {
17
+ super(app, "Set Date Fields...", null, "Set date fields corresponding to start, end.");
18
+ }
19
+
20
+ @Override
21
+ public void actionPerformed(ActionEvent e) {
22
+ DateFieldPanel.popWindow(app.model);
23
+ }
24
+}
timeflow/app/actions/DeleteFieldAction.java
....@@ -0,0 +1,43 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+
8
+import java.awt.event.*;
9
+import javax.swing.*;
10
+import java.util.*;
11
+
12
+public class DeleteFieldAction extends TimeflowAction {
13
+
14
+ public DeleteFieldAction(TimeflowApp app)
15
+ {
16
+ super(app, "Delete Field...", null, "Delete a field from this database");
17
+ }
18
+
19
+ @Override
20
+ public void actionPerformed(ActionEvent e) {
21
+ ArrayList<String> options=new ArrayList<String>();
22
+ for (Field f: getModel().getDB().getFields())
23
+ options.add(f.getName());
24
+ String[] o=(String[])options.toArray(new String[0]);
25
+ String fieldToDelete = (String)JOptionPane.showInputDialog(
26
+ app,
27
+ "Field to delete:",
28
+ "Delete Field",
29
+ JOptionPane.PLAIN_MESSAGE,
30
+ null,
31
+ o,
32
+ o[0]);
33
+
34
+ if (fieldToDelete!=null)
35
+ {
36
+ TFModel model=getModel();
37
+ Field f=model.getDB().getField(fieldToDelete);
38
+ model.getDB().deleteField(f);
39
+ model.noteSchemaChange(this);
40
+ return;
41
+ }
42
+ }
43
+}
timeflow/app/actions/DeleteSelectedAction.java
....@@ -0,0 +1,51 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.*;
5
+import timeflow.app.*;
6
+import timeflow.data.db.*;
7
+
8
+import java.awt.event.*;
9
+import javax.swing.*;
10
+import java.util.*;
11
+
12
+
13
+public class DeleteSelectedAction extends TimeflowAction {
14
+
15
+ public DeleteSelectedAction(TimeflowApp app)
16
+ {
17
+ super(app, "Delete Selected Items...", null, "Delete the currently visible events");
18
+ }
19
+
20
+ @Override
21
+ public void actionPerformed(ActionEvent e) {
22
+
23
+ HashSet<Act> keepers=new HashSet<Act>(); // switching between sets and lists
24
+ keepers.addAll(getModel().getDB().all()); // for efficiency. maybe silly?
25
+ ActList selected=getModel().getActs();
26
+ for (Act a: selected)
27
+ keepers.remove(a);
28
+ ActList keepList=new ActList(getModel().getDB());
29
+ keepList.addAll(keepers);
30
+
31
+ MassDeletePanel panel=new MassDeletePanel(getModel(), keepList,
32
+ "Delete all selected items.");
33
+ Object[] options = {"Cancel", "Proceed"};
34
+ int n = JOptionPane.showOptionDialog(app,
35
+ panel,
36
+ "Delete Selected",
37
+ JOptionPane.YES_NO_CANCEL_OPTION,
38
+ JOptionPane.PLAIN_MESSAGE,
39
+ null,
40
+ options,
41
+ "Proceed");
42
+ panel.detachFromModel();
43
+ if (n==1)
44
+ {
45
+ panel.applyAction();
46
+ app.clearFilters();
47
+ getModel().noteSchemaChange(this);
48
+ }
49
+ }
50
+
51
+}
timeflow/app/actions/DeleteUnselectedAction.java
....@@ -0,0 +1,40 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.*;
5
+import timeflow.app.*;
6
+
7
+import java.awt.event.*;
8
+import javax.swing.*;
9
+
10
+
11
+public class DeleteUnselectedAction extends TimeflowAction {
12
+
13
+ public DeleteUnselectedAction(TimeflowApp app)
14
+ {
15
+ super(app, "Delete Unselected Items...", null, "Delete all but the currently visible events");
16
+ }
17
+
18
+ @Override
19
+ public void actionPerformed(ActionEvent e) {
20
+ MassDeletePanel panel=new MassDeletePanel(getModel(), getModel().getActs(),
21
+ "Delete unselected items.");
22
+ Object[] options = {"Cancel", "Proceed"};
23
+ int n = JOptionPane.showOptionDialog(app,
24
+ panel,
25
+ "Delete Unselected",
26
+ JOptionPane.YES_NO_CANCEL_OPTION,
27
+ JOptionPane.PLAIN_MESSAGE,
28
+ null,
29
+ options,
30
+ "Proceed");
31
+ panel.detachFromModel();
32
+ if (n==1)
33
+ {
34
+ panel.applyAction();
35
+ app.clearFilters();
36
+ getModel().noteSchemaChange(this);
37
+ }
38
+ }
39
+
40
+}
timeflow/app/actions/EditSourceAction.java
....@@ -0,0 +1,37 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+
7
+import java.awt.event.*;
8
+import javax.swing.*;
9
+
10
+
11
+public class EditSourceAction extends TimeflowAction {
12
+
13
+ public EditSourceAction(TimeflowApp app)
14
+ {
15
+ super(app, "Edit Source/Credit Line...", null, "Edit credit line for this database");
16
+ }
17
+
18
+ @Override
19
+ public void actionPerformed(ActionEvent e) {
20
+ TFModel model=getModel();
21
+ String source = (String)JOptionPane.showInputDialog(
22
+ app,
23
+ null,
24
+ "Edit Source/Credit Line",
25
+ JOptionPane.PLAIN_MESSAGE,
26
+ null,
27
+ null,
28
+ model.getDB().getSource());
29
+
30
+ if (source!=null) {
31
+ model.getDB().setSource(source);
32
+ model.noteNewSource(this);
33
+ return;
34
+ }
35
+ }
36
+
37
+}
timeflow/app/actions/ImportFromPasteAction.java
....@@ -0,0 +1,53 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+import timeflow.format.file.DelimitedFormat;
9
+
10
+import java.awt.event.*;
11
+import javax.swing.*;
12
+import java.util.*;
13
+
14
+public class ImportFromPasteAction extends TimeflowAction {
15
+
16
+ public ImportFromPasteAction(TimeflowApp app)
17
+ {
18
+ super(app, "Paste From Spreadsheet / HTML...", null, "Import from copy-and-pasted data.");
19
+ }
20
+
21
+ public void actionPerformed(ActionEvent event)
22
+ {
23
+ if (!app.checkSaveStatus())
24
+ return;
25
+ JTextArea text=new JTextArea(10,40);
26
+ JScrollPane scroll=new JScrollPane(text);
27
+ text.setText("Paste here! (replacing this :-)");
28
+ text.setSelectionStart(0);
29
+ text.setSelectionEnd(text.getText().length());
30
+ Object[] options = {"Cancel", "Import"};
31
+ int n = JOptionPane.showOptionDialog(app,
32
+ scroll,
33
+ "Import From Paste",
34
+ JOptionPane.YES_NO_CANCEL_OPTION,
35
+ JOptionPane.QUESTION_MESSAGE,
36
+ null,
37
+ options,
38
+ "Import");
39
+ if (n==1)
40
+ {
41
+ try
42
+ {
43
+ String pasted=text.getText();
44
+ String[][] data=DelimitedFormat.readArrayFromString(pasted, System.out);
45
+ app.showImportEditor("Paste", data);
46
+ }
47
+ catch (Exception e)
48
+ {
49
+ app.showUserError(e);
50
+ }
51
+ }
52
+ }
53
+}
timeflow/app/actions/NewDataAction.java
....@@ -0,0 +1,27 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+
9
+import java.awt.event.*;
10
+import javax.swing.*;
11
+import java.util.*;
12
+
13
+public class NewDataAction extends TimeflowAction {
14
+
15
+ public NewDataAction(TimeflowApp app)
16
+ {
17
+ super(app, "New", null, "Create a new, blank database");
18
+ accelerate('N');
19
+
20
+ }
21
+
22
+ public void actionPerformed(ActionEvent e)
23
+ {
24
+ if (app.checkSaveStatus())
25
+ getModel().setDB(new BasicDB("Unspecified"), "[new data]", true, this);
26
+ }
27
+}
timeflow/app/actions/QuitAction.java
....@@ -0,0 +1,32 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.TimeflowApp;
5
+import timeflow.app.ui.*;
6
+import timeflow.data.db.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+
9
+import java.awt.event.*;
10
+import javax.swing.*;
11
+import java.util.*;
12
+
13
+public class QuitAction extends TimeflowAction {
14
+
15
+ public QuitAction(TimeflowApp app, TFModel model)
16
+ {
17
+ super(app, "Quit", null, "Quit the program");
18
+ }
19
+
20
+ @Override
21
+ public void actionPerformed(ActionEvent e) {
22
+ quit();
23
+ }
24
+
25
+ public void quit()
26
+ {
27
+ if (app.checkSaveStatus())
28
+ {
29
+ System.exit(0);
30
+ }
31
+ }
32
+}
timeflow/app/actions/RenameFieldAction.java
....@@ -0,0 +1,100 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.*;
5
+import timeflow.app.*;
6
+import timeflow.data.db.*;
7
+
8
+import java.awt.*;
9
+import java.awt.event.*;
10
+
11
+import javax.swing.*;
12
+
13
+import java.util.*;
14
+
15
+public class RenameFieldAction extends TimeflowAction {
16
+
17
+ public RenameFieldAction(TimeflowApp app)
18
+ {
19
+ super(app, "Rename Field...", null, "Rename a field from this database");
20
+ }
21
+
22
+ @Override
23
+ public void actionPerformed(ActionEvent e) {
24
+ JPanel panel=new JPanel();
25
+ panel.setLayout(new GridLayout(4,1));
26
+ panel.add(new JLabel("Choose a field and type a new name."));
27
+ final JComboBox fieldChoices=new JComboBox();
28
+ panel.add(fieldChoices);
29
+ ArrayList<String> options=new ArrayList<String>();
30
+ for (Field f: getModel().getDB().getFields())
31
+ fieldChoices.addItem(f.getName());
32
+ JPanel inputPanel=new JPanel();
33
+ inputPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
34
+ inputPanel.add(new JLabel("New Name:"));
35
+ final JTextField nameField=new JTextField(20);
36
+ inputPanel.add(nameField);
37
+ nameField.requestFocus();
38
+ final JLabel feedback=new JLabel("(No name entered)");
39
+
40
+ nameField.addKeyListener(new KeyListener() {
41
+ @Override
42
+ public void keyPressed(KeyEvent e) {
43
+ // TODO Auto-generated method stub
44
+
45
+ }
46
+
47
+ @Override
48
+ public void keyReleased(KeyEvent e) {
49
+ String name=nameField.getText();
50
+ Field other=getModel().getDB().getField(name);
51
+ //System.out.println("name="+name);
52
+ if (name.trim().length()==0)
53
+ {
54
+ feedback.setText("(No name entered)");
55
+ } else if (other!=null && !other.getName().equals(fieldChoices.getSelectedItem()))
56
+ {
57
+ feedback.setText("A field named '"+name+"' already exists.");
58
+ } else
59
+ feedback.setText("");
60
+ }
61
+
62
+ @Override
63
+ public void keyTyped(KeyEvent e) {
64
+ // TODO Auto-generated method stub
65
+
66
+ }});
67
+
68
+ panel.add(inputPanel);
69
+ feedback.setForeground(Color.gray);
70
+ panel.add(feedback);
71
+
72
+ String[] o={"OK", "Cancel"};
73
+ int n = JOptionPane.showOptionDialog(
74
+ app,
75
+ panel,
76
+ "Rename Field",
77
+ JOptionPane.YES_NO_CANCEL_OPTION,
78
+ JOptionPane.PLAIN_MESSAGE,
79
+ null,
80
+ o,
81
+ o[0]);
82
+
83
+ if (n==0)
84
+ {
85
+ Field old=getModel().getDB().getField((String)fieldChoices.getSelectedItem());
86
+ String newName=nameField.getText();
87
+ Field conflict=getModel().getDB().getField(newName);
88
+ boolean tooSpacey=newName.trim().length()==0;
89
+ if (tooSpacey)
90
+ app.showUserError("Can't change the field name to be empty.");
91
+ else if (conflict!=null && conflict!=old)
92
+ app.showUserError("A field named '"+newName+"' already exists.");
93
+ else
94
+ {
95
+ getModel().getDB().renameField(old, nameField.getText());
96
+ getModel().noteSchemaChange(this);
97
+ }
98
+ }
99
+ }
100
+}
timeflow/app/actions/ReorderFieldsAction.java
....@@ -0,0 +1,38 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.*;
5
+import timeflow.app.*;
6
+
7
+import java.awt.event.*;
8
+import javax.swing.*;
9
+
10
+
11
+public class ReorderFieldsAction extends TimeflowAction {
12
+
13
+ public ReorderFieldsAction(TimeflowApp app)
14
+ {
15
+ super(app, "Reorder Fields...", null, "Edit the order of fields");
16
+ }
17
+
18
+ @Override
19
+ public void actionPerformed(ActionEvent e) {
20
+ ReorderFieldsPanel panel=new ReorderFieldsPanel(getModel());
21
+ Object[] options = {"Cancel", "Apply"};
22
+ int n = JOptionPane.showOptionDialog(app,
23
+ panel,
24
+ "Reorder Fields",
25
+ JOptionPane.YES_NO_CANCEL_OPTION,
26
+ JOptionPane.PLAIN_MESSAGE,
27
+ null,
28
+ options,
29
+ "Apply");
30
+ panel.detachFromModel();
31
+ if (n==1)
32
+ {
33
+ panel.applyReordering();
34
+ getModel().noteSchemaChange(this);
35
+ }
36
+ }
37
+
38
+}
timeflow/app/actions/TimeflowAction.java
....@@ -0,0 +1,36 @@
1
+package timeflow.app.actions;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.*;
5
+import timeflow.format.file.*;
6
+
7
+import javax.swing.*;
8
+
9
+import java.awt.Toolkit;
10
+import java.io.*;
11
+
12
+public abstract class TimeflowAction extends AbstractAction {
13
+ TimeflowApp app;
14
+
15
+ public TimeflowAction(TimeflowApp app, String text, ImageIcon icon, String desc)
16
+ {
17
+ super(text, icon);
18
+ this.app=app;
19
+ putValue(SHORT_DESCRIPTION, desc);
20
+ }
21
+
22
+
23
+ protected void accelerate(char c)
24
+ {
25
+ putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(c,
26
+ Toolkit.getDefaultToolkit( ).getMenuShortcutKeyMask( ), false));
27
+ }
28
+
29
+
30
+ protected TFModel getModel()
31
+ {
32
+ return app.model;
33
+ }
34
+
35
+
36
+}
timeflow/app/actions/WebDocAction.java
....@@ -0,0 +1,25 @@
1
+package timeflow.app.actions;
2
+
3
+import java.awt.Toolkit;
4
+import java.awt.event.ActionEvent;
5
+
6
+import javax.swing.Action;
7
+import javax.swing.JOptionPane;
8
+import javax.swing.KeyStroke;
9
+
10
+import timeflow.app.TimeflowApp;
11
+import timeflow.app.ui.ReorderFieldsPanel;
12
+import timeflow.model.Display;
13
+
14
+public class WebDocAction extends TimeflowAction {
15
+ public WebDocAction(TimeflowApp app)
16
+ {
17
+ super(app, "Documentation & License Info...", null, "Read web documentation.");
18
+ }
19
+
20
+ @Override
21
+ public void actionPerformed(ActionEvent e) {
22
+ Display.launchBrowser("http://wiki.github.com/FlowingMedia/TimeFlow/");
23
+ }
24
+
25
+}
timeflow/app/ui/AddFieldPanel.java
....@@ -0,0 +1,24 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.app.*;
4
+import timeflow.format.field.FieldFormatCatalog;
5
+
6
+import javax.swing.*;
7
+import java.awt.*;
8
+import java.awt.event.*;
9
+
10
+
11
+public class AddFieldPanel extends JPanel {
12
+ public JTextField name=new JTextField(12);
13
+ public JComboBox typeChoices=new JComboBox();
14
+ public AddFieldPanel()
15
+ {
16
+ for (String choice: FieldFormatCatalog.classNames())
17
+ typeChoices.addItem(choice);
18
+ setLayout(new GridLayout(2,2));
19
+ add(new JLabel("Field Name"));
20
+ add(name);
21
+ add(new JLabel("Field Type"));
22
+ add(typeChoices);
23
+ }
24
+}
timeflow/app/ui/ColorLegendPanel.java
....@@ -0,0 +1,63 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.filter.FilterCategoryPanel;
5
+import timeflow.data.db.*;
6
+import timeflow.data.db.filter.FieldValueFilter;
7
+import timeflow.data.db.filter.ValueFilter;
8
+import timeflow.data.time.*;
9
+
10
+import timeflow.util.*;
11
+
12
+import java.awt.*;
13
+
14
+import javax.swing.JLabel;
15
+import javax.swing.event.ListSelectionEvent;
16
+import javax.swing.event.ListSelectionListener;
17
+
18
+public class ColorLegendPanel extends ModelPanel {
19
+
20
+
21
+ Field oldColor;
22
+
23
+ public ColorLegendPanel(TFModel model)
24
+ {
25
+ super(model);
26
+ setBackground(Color.white);
27
+ setLayout(new GridLayout(1,1));
28
+ }
29
+
30
+ @Override
31
+ public void note(TFEvent e) {
32
+ Field color=getModel().getColorField();
33
+ if (color!=null && color!=oldColor)
34
+ {
35
+ removeAll();
36
+ final FilterCategoryPanel p=new FilterCategoryPanel("Color Legend: '"+color.getName()+"'",
37
+ color, this);
38
+ add(p);
39
+ Bag<String> data=DBUtils.countValues(getModel().getDB().all(), color);
40
+ p.setData(data);
41
+ p.dataList.addListSelectionListener(new ListSelectionListener() {
42
+ @Override
43
+ public void valueChanged(ListSelectionEvent e) {
44
+ ValueFilter f=(ValueFilter)p.defineFilter();
45
+ getModel().setGrayFilter(f, this);
46
+ }
47
+ });
48
+
49
+ oldColor=color;
50
+ revalidate();
51
+ return;
52
+ } else if (color==null)
53
+ {
54
+ removeAll();
55
+ }
56
+ repaint();
57
+ }
58
+
59
+ public Dimension getPreferredSize()
60
+ {
61
+ return new Dimension(200,400);
62
+ }
63
+}
timeflow/app/ui/ComponentCluster.java
....@@ -0,0 +1,37 @@
1
+package timeflow.app.ui;
2
+
3
+import javax.swing.*;
4
+import java.awt.*;
5
+
6
+public class ComponentCluster extends JPanel
7
+{
8
+ int numComps=0;
9
+ int x1=80;
10
+ int width=200;
11
+ int compH=30;
12
+ DottedLine line=new DottedLine();
13
+
14
+ public ComponentCluster(String name)
15
+ {
16
+ setBackground(Color.white);
17
+ setLayout(null);
18
+ JLabel label=new JLabel(name);
19
+ add(label);
20
+ label.setBounds(3,3,50,30);
21
+ add(line);
22
+ }
23
+
24
+ public void addContent(JComponent c)
25
+ {
26
+ add(c);
27
+ c.setBorder(null);
28
+ c.setBounds(x1,10+numComps*compH, c.getPreferredSize().width, c.getPreferredSize().height);
29
+ numComps++;
30
+ line.setBounds(x1-10,10,1,numComps*compH-5);
31
+ }
32
+
33
+ public Dimension getPreferredSize()
34
+ {
35
+ return new Dimension(width, 20+compH*numComps);
36
+ }
37
+}
timeflow/app/ui/DateFieldPanel.java
....@@ -0,0 +1,178 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.model.*;
4
+import timeflow.data.time.*;
5
+import timeflow.data.db.*;
6
+import timeflow.data.db.filter.*;
7
+import timeflow.format.field.*;
8
+import timeflow.format.file.TimeflowFormat;
9
+
10
+import javax.swing.*;
11
+import java.util.*;
12
+import java.awt.*;
13
+import java.awt.event.*;
14
+
15
+
16
+public class DateFieldPanel extends JPanel
17
+{
18
+ TFModel model;
19
+ int numRows;
20
+ HashMap<String, Integer> numBad=new HashMap<String, Integer>();
21
+ private static String[] mappable={VirtualField.START, VirtualField.END};
22
+ JLabel status=new JLabel("");
23
+ FieldMap[] panels=new FieldMap[mappable.length];
24
+ JButton submit, cancel;
25
+
26
+ public DateFieldPanel(TFModel model, boolean hasButtons)
27
+ {
28
+ this.model=model;
29
+
30
+ ActDB db=model.getDB();
31
+ numRows=db.size();
32
+ ActList all=db.all();
33
+
34
+
35
+ // calculate stats.
36
+ for (Field f: db.getFields(RoughTime.class))
37
+ {
38
+ int bad=DBUtils.count(all, new MissingValueFilter(f));
39
+ numBad.put(f.getName(),bad);
40
+ }
41
+
42
+ setLayout(new BorderLayout());
43
+ JPanel top=new JPanel();
44
+ if (hasButtons)
45
+ {
46
+ submit=new JButton("Submit");
47
+ top.add(submit);
48
+ cancel=new JButton("Cancel");
49
+ top.add(cancel);
50
+ }
51
+ else
52
+ {
53
+ JLabel about=new JLabel("Dates");
54
+ top.add(about);
55
+ }
56
+ top.add(status);
57
+ status.setForeground(Color.red);
58
+ add(top, BorderLayout.SOUTH);
59
+ JPanel bottom=new JPanel();
60
+ add(bottom, BorderLayout.CENTER);
61
+ bottom.setLayout(new GridLayout(mappable.length,1));
62
+
63
+ // add panels.
64
+ for (int i=0; i<mappable.length; i++)
65
+ {
66
+ panels[i]=new FieldMap(mappable[i],i==0);
67
+ bottom.add(panels[i]);
68
+ }
69
+
70
+
71
+ // to do: add a status field or something
72
+ // note inconsistencies, like:
73
+ // * no start defined.
74
+ // * ends after starts
75
+ }
76
+
77
+ public void map()
78
+ {
79
+ ActDB db=model.getDB();
80
+ for (int i=0; i<panels.length; i++)
81
+ {
82
+ String choice=(String)panels[i].choices.getSelectedItem();
83
+ db.setAlias("None".equals(choice) ? null : db.getField(choice), panels[i].name);
84
+ }
85
+ model.noteSchemaChange(this);
86
+ }
87
+
88
+ class FieldMap extends JPanel
89
+ {
90
+ String name;
91
+ int bad;
92
+ JComboBox choices;
93
+ JLabel definedLabel=new JLabel("# def goes here");
94
+ boolean important;
95
+
96
+ FieldMap(String name, boolean important)
97
+ {
98
+ this.name=name;
99
+ this.important=important;
100
+ setBackground(Color.white);
101
+ setLayout(new GridLayout(1,3));
102
+
103
+ add(new JLabel(" "+(important? "* ":"")+VirtualField.humanName(name)));//, BorderLayout.NORTH);
104
+
105
+ choices=new JComboBox();
106
+ choices.addItem("None");
107
+ for (Field f: model.getDB().getFields(RoughTime.class))
108
+ {
109
+ choices.addItem(f.getName());//+" "+percentDef+"% defined");
110
+ }
111
+ add(choices);
112
+ choices.addActionListener(new ActionListener() {
113
+ @Override
114
+ public void actionPerformed(ActionEvent e) {
115
+ showDefined();
116
+ }});
117
+
118
+ add(definedLabel);
119
+ definedLabel.setForeground(Color.gray);
120
+
121
+ Field current=model.getDB().getField(name);
122
+ if (current!=null)
123
+ choices.setSelectedItem(current.getName());
124
+
125
+ showDefined();
126
+ }
127
+
128
+ void showDefined()
129
+ {
130
+ String choice=(String)choices.getSelectedItem();
131
+ String val="";
132
+ boolean none="None".equals(choice);
133
+ int percentDef=0;
134
+ if (!none)
135
+ {
136
+ percentDef=(int)(100*(1-numBad.get(choice)/(double)numRows));
137
+ val=" "+percentDef+"% defined";
138
+ }
139
+ definedLabel.setText(val);
140
+ if (important)
141
+ {
142
+ if (none)
143
+ status.setText(" Need \"Start\" for timeline/calendar.");
144
+ else if (percentDef==0)
145
+ status.setText(" No dates defined in "+choice+".");
146
+ else
147
+ status.setText("");
148
+ }
149
+ }
150
+ }
151
+
152
+ public Dimension getPreferredSize()
153
+ {
154
+ return new Dimension(400,80+mappable.length*25);
155
+ }
156
+
157
+ public static void popWindow(TFModel model)
158
+ {
159
+ final JFrame window=new JFrame("Date Fields");
160
+ window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
161
+ window.getContentPane().setLayout(new GridLayout(1,1));
162
+ final DateFieldPanel p=new DateFieldPanel(model, true);
163
+ p.submit.addActionListener(new ActionListener() {
164
+ @Override
165
+ public void actionPerformed(ActionEvent e) {
166
+ p.map();
167
+ window.setVisible(false);
168
+ }});
169
+ p.cancel.addActionListener(new ActionListener() {
170
+ @Override
171
+ public void actionPerformed(ActionEvent e) {
172
+ window.setVisible(false);
173
+ }});
174
+ window.getContentPane().add(p);
175
+ window.setBounds(50,50,window.getPreferredSize().width,window.getPreferredSize().height);
176
+ window.setVisible(true);
177
+ }
178
+}
timeflow/app/ui/DottedLine.java
....@@ -0,0 +1,34 @@
1
+package timeflow.app.ui;
2
+
3
+import javax.swing.*;
4
+import java.awt.*;
5
+
6
+public class DottedLine extends JPanel
7
+{
8
+ public void paintComponent(Graphics g)
9
+ {
10
+ int w=getSize().width, h=getSize().height;
11
+ g.setColor(Color.white);
12
+ g.fillRect(0,0,w,h);
13
+ g.setColor(Color.lightGray);
14
+ if (w>h)
15
+ {
16
+ for (int x=0; x<w; x+=4)
17
+ {
18
+ g.drawLine(x,0,x+1,0);
19
+ }
20
+ }
21
+ else
22
+ {
23
+ for (int y=0; y<h; y+=4)
24
+ {
25
+ g.drawLine(0,y,0,y+1);
26
+ }
27
+ }
28
+ }
29
+
30
+ public Dimension getPreferredSize()
31
+ {
32
+ return new Dimension(1,1);
33
+ }
34
+}
timeflow/app/ui/EditRecordPanel.java
....@@ -0,0 +1,134 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.ImportDelimitedPanel.SchemaPanel;
5
+import timeflow.data.time.*;
6
+import timeflow.data.db.*;
7
+
8
+import javax.swing.*;
9
+
10
+import java.util.*;
11
+import java.awt.*;
12
+import java.awt.event.ActionEvent;
13
+import java.awt.event.ActionListener;
14
+
15
+// panel with form for editing a given database entry
16
+public class EditRecordPanel extends JPanel
17
+{
18
+
19
+ Act act;
20
+ HashMap<Field, EditValuePanel> fieldUI = new HashMap<Field, EditValuePanel>();
21
+ JButton submit, cancel;
22
+ Dimension idealSize = new Dimension();
23
+ TFModel model;
24
+
25
+ private static void edit(final TFModel model, final Act act, final boolean isAdd)
26
+ {
27
+ final JFrame window = new JFrame(isAdd ? "Add Record" : "Edit Record");
28
+ window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
29
+ final EditRecordPanel editor = new EditRecordPanel(model, act);
30
+ window.getContentPane().setLayout(new GridLayout(1, 1));
31
+ window.getContentPane().add(editor);
32
+ editor.submit.addActionListener(new ActionListener()
33
+ {
34
+
35
+ @Override
36
+ public void actionPerformed(ActionEvent e)
37
+ {
38
+ window.setVisible(false);
39
+ editor.submitValues();
40
+ model.noteAdd(this);
41
+ }
42
+ });
43
+ editor.cancel.addActionListener(new ActionListener()
44
+ {
45
+
46
+ @Override
47
+ public void actionPerformed(ActionEvent e)
48
+ {
49
+ window.setVisible(false);
50
+ if (isAdd)
51
+ {
52
+ model.getDB().delete(act);
53
+ }
54
+ }
55
+ });
56
+ window.setBounds(50, 50, 700, 500);
57
+ window.pack();
58
+ window.setVisible(true);
59
+ }
60
+
61
+ public static void edit(TFModel model, Act act)
62
+ {
63
+ edit(model, act, false);
64
+ }
65
+
66
+ public static void add(TFModel model)
67
+ {
68
+ Act act = model.getDB().createAct();
69
+ edit(model, act, true);
70
+ }
71
+
72
+ public static void add(TFModel model, RoughTime r)
73
+ {
74
+ Act act = model.getDB().createAct();
75
+ act.set(act.getDB().getField(VirtualField.START), r);
76
+ edit(model, act, true);
77
+ }
78
+
79
+ public EditRecordPanel(TFModel model, Act act)
80
+ {
81
+ this.model = model;
82
+ this.act = act;
83
+
84
+ setBackground(Color.white);
85
+ setLayout(new BorderLayout());
86
+
87
+ JPanel buttons = new JPanel();
88
+ add(buttons, BorderLayout.SOUTH);
89
+ buttons.setBackground(Color.lightGray);
90
+ submit = new JButton("OK");
91
+ buttons.add(submit);
92
+ cancel = new JButton("Cancel");
93
+ buttons.add(cancel);
94
+
95
+ JPanel entryPanel = new JPanel();
96
+ JScrollPane scroller = new JScrollPane(entryPanel);
97
+ add(scroller, BorderLayout.CENTER);
98
+
99
+ java.util.List<Field> fields = act.getDB().getFields();
100
+ int n = fields.size();
101
+ entryPanel.setLayout(null);
102
+
103
+ DBUtils.setRecSizesFromCurrent(act.getDB());
104
+ int top = 0;
105
+
106
+ for (Field f : fields)
107
+ {
108
+ EditValuePanel p = new EditValuePanel(f.getName(), act.get(f),
109
+ f.getType(), f.getRecommendedSize() > 100);
110
+ entryPanel.add(p);
111
+ Dimension d = p.getPreferredSize();
112
+ p.setBounds(0, top, d.width, d.height);
113
+ top += d.height;
114
+ idealSize.width = Math.max(d.width + 5, idealSize.width);
115
+ idealSize.height = Math.max(top + 45, idealSize.height);
116
+ fieldUI.put(f, p);
117
+ }
118
+
119
+ }
120
+
121
+ public Dimension getPreferredSize()
122
+ {
123
+ return idealSize;
124
+ }
125
+
126
+ public void submitValues()
127
+ {
128
+ for (Field f : fieldUI.keySet())
129
+ {
130
+ act.set(f, fieldUI.get(f).getInputValue());
131
+ }
132
+ model.noteRecordChange(this);
133
+ }
134
+}
timeflow/app/ui/EditValuePanel.java
....@@ -0,0 +1,111 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.format.field.*;
4
+
5
+import javax.swing.*;
6
+import javax.swing.text.JTextComponent;
7
+
8
+import java.awt.*;
9
+import java.awt.event.*;
10
+
11
+public class EditValuePanel extends JPanel
12
+{
13
+
14
+ FieldFormat parser;
15
+ boolean longField;
16
+ JLabel feedback = new JLabel()
17
+ {
18
+
19
+ public Dimension getPreferredSize()
20
+ {
21
+ Dimension d = super.getPreferredSize();
22
+ return new Dimension(200, d.height);
23
+ }
24
+ };
25
+ static final String space = " ";
26
+ JTextComponent input;
27
+
28
+ public EditValuePanel(String name, Object startValue, Class type, boolean longField)
29
+ {
30
+ parser = FieldFormatCatalog.getFormat(type);
31
+
32
+ if (longField)
33
+ {
34
+ setLayout(new BorderLayout());
35
+ JPanel top = new JPanel();
36
+ top.setLayout(new GridLayout(2, 2));
37
+ top.add(new JPanel());
38
+ top.add(new JPanel());
39
+ JLabel fieldLabel = new JLabel(space + name + " (long)");
40
+ top.add(fieldLabel);
41
+ top.add(feedback);
42
+ add(top, BorderLayout.NORTH);
43
+ input = new JTextArea(5, 60);
44
+ ((JTextArea) input).setLineWrap(true);
45
+ ((JTextArea) input).setWrapStyleWord(true);
46
+ JScrollPane scroller = new JScrollPane(input);
47
+ scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
48
+ add(scroller, BorderLayout.CENTER);
49
+ add(new JPanel(), BorderLayout.WEST);
50
+ add(new JPanel(), BorderLayout.SOUTH);
51
+ } else
52
+ {
53
+ setLayout(new GridLayout(1, 4));
54
+ JLabel fieldLabel = new JLabel(space + name);
55
+ add(fieldLabel);
56
+ JLabel typeLabel = new JLabel(FieldFormatCatalog.humanName(type));
57
+ add(typeLabel);
58
+ typeLabel.setForeground(Color.gray);
59
+ input = new JTextField(8);
60
+ add(input);
61
+ // enough room for "couldn't understand"
62
+ add(feedback);
63
+ }
64
+ input.setText(startValue == null ? "" : parser.format(startValue));
65
+ input.addKeyListener(new KeyAdapter()
66
+ {
67
+
68
+ @Override
69
+ public void keyReleased(KeyEvent e)
70
+ {
71
+ parse();
72
+ }
73
+ });
74
+ parse();
75
+ }
76
+
77
+ void parse()
78
+ {
79
+ try
80
+ {
81
+ parser.parse(input.getText());
82
+ } catch (Exception e)
83
+ {
84
+ }
85
+ feedback.setText(" " + parser.feedback());
86
+ feedback.setForeground(parser.isUnderstood() ? Color.gray : Color.red);
87
+ }
88
+
89
+ public Object getInputValue()
90
+ {
91
+ try
92
+ {
93
+ return parser.parse(input.getText());
94
+ } catch (Exception e)
95
+ {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ public boolean isOK()
101
+ {
102
+ return parser.isUnderstood();
103
+ }
104
+
105
+ public Dimension getPreferredSize()
106
+ {
107
+ Dimension d = super.getPreferredSize();
108
+ int w = Math.max(300, d.width);
109
+ return new Dimension(w, d.height);
110
+ }
111
+}
timeflow/app/ui/GlobalDisplayPanel.java
....@@ -0,0 +1,174 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.app.ui.filter.FilterControlPanel;
4
+import timeflow.data.db.*;
5
+import timeflow.data.time.*;
6
+import timeflow.model.*;
7
+
8
+import javax.swing.*;
9
+
10
+import timeflow.util.*;
11
+
12
+import java.awt.*;
13
+import java.awt.event.*;
14
+import java.util.List;
15
+
16
+public class GlobalDisplayPanel extends ModelPanel
17
+{
18
+
19
+ JPanel encodings = new JPanel();
20
+ JPanel localControls = new JPanel();
21
+ JPanel globalControls = new JPanel();
22
+ CardLayout localCards = new CardLayout();
23
+
24
+ public GlobalDisplayPanel(TFModel model, FilterControlPanel filterControls)
25
+ {
26
+ super(model);
27
+ setBackground(Color.white);
28
+ setLayout(new BorderLayout());
29
+
30
+ add(localControls, BorderLayout.CENTER);
31
+ localControls.setBackground(Color.white);
32
+ localControls.setLayout(localCards);
33
+
34
+ JPanel p = new JPanel();
35
+ p.setBackground(Color.white);
36
+ p.setLayout(new BorderLayout());
37
+
38
+ JPanel globalLabel = new JPanel();
39
+ globalLabel.setLayout(new BorderLayout());
40
+
41
+ JPanel topLine = new Pad(2, 3);
42
+ topLine.setBackground(Color.gray);
43
+ globalLabel.add(topLine, BorderLayout.NORTH);
44
+
45
+ JPanel bottomLine = new Pad(2, 3);
46
+ bottomLine.setBackground(Color.gray);
47
+ globalLabel.add(bottomLine, BorderLayout.SOUTH);
48
+
49
+ JLabel label = new JLabel(" Global Controls", JLabel.LEFT)
50
+ {
51
+
52
+ public Dimension getPreferredSize()
53
+ {
54
+ return new Dimension(30, 30);
55
+ }
56
+ };
57
+ label.setBackground(Color.lightGray);
58
+ label.setForeground(Color.darkGray);
59
+ globalLabel.add(label, BorderLayout.CENTER);
60
+ p.add(globalLabel, BorderLayout.NORTH);
61
+
62
+ JPanel global = new JPanel();
63
+ global.setLayout(new BorderLayout());
64
+ global.add(new StatusPanel(model, filterControls), BorderLayout.NORTH);
65
+
66
+ encodings.setLayout(new GridLayout(4, 1));
67
+ encodings.setBackground(Color.white);
68
+ global.add(encodings, BorderLayout.CENTER);
69
+ p.add(global, BorderLayout.CENTER);
70
+ add(p, BorderLayout.SOUTH);
71
+
72
+ makeEncodingPanel();
73
+ }
74
+
75
+ public void showLocalControl(String name)
76
+ {
77
+ localCards.show(localControls, name);
78
+ }
79
+
80
+ public void addLocalControl(String name, JComponent control)
81
+ {
82
+ localControls.add(control, name);
83
+ }
84
+
85
+ void makeEncodingPanel()
86
+ {
87
+ encodings.removeAll();
88
+ ActDB db = getModel().getDB();
89
+ if (db == null)
90
+ {
91
+ return;
92
+ }
93
+
94
+ java.util.List<Field> dimensions = DBUtils.categoryFields(db);
95
+ java.util.List<Field> measures = db.getFields(Double.class);
96
+ java.util.List<Field> fields = db.getFields();
97
+
98
+ makeChooser(VirtualField.LABEL, "Label", "None", fields); //db.getFields()); // String.class));
99
+ makeChooser(VirtualField.TRACK, "Groups", "None", dimensions);
100
+ makeChooser(VirtualField.COLOR, "Color", "Same As Groups", dimensions);
101
+
102
+ makeChooser(VirtualField.SIZE, "Dot Size", "None", measures);
103
+ }
104
+
105
+ private JComboBox makeChooser(final String alias, String title, String nothingLabel, List<Field> fields)
106
+ {
107
+ if (fields.size() == 0)
108
+ {
109
+ return null;
110
+ }
111
+ JPanel panel = new JPanel();
112
+ panel.setBackground(Color.white);
113
+ panel.setLayout(new BorderLayout());
114
+ JPanel topPad = new Pad(10, 7);
115
+ topPad.setBackground(Color.white);
116
+ panel.add(topPad, BorderLayout.NORTH);
117
+
118
+ JPanel rightPad = new Pad(10, 10);
119
+ panel.add(rightPad, BorderLayout.EAST);
120
+ rightPad.setBackground(Color.white);
121
+
122
+ panel.add(new JLabel(" " + title)
123
+ {
124
+
125
+ public Dimension getPreferredSize()
126
+ {
127
+ return new Dimension(60, 25);
128
+ }
129
+ },
130
+ BorderLayout.WEST);
131
+ final JComboBox c = new JComboBox();
132
+
133
+ if (nothingLabel != null)
134
+ {
135
+ c.addItem(nothingLabel);
136
+ }
137
+ for (Field f : fields)
138
+ {
139
+ c.addItem(f.getName());
140
+ }
141
+
142
+ Field current = getModel().getDB().getField(alias);
143
+ if (current != null)
144
+ {
145
+ c.setSelectedItem(current.getName());
146
+ }
147
+ c.addActionListener(new ActionListener()
148
+ {
149
+
150
+ @Override
151
+ public void actionPerformed(ActionEvent e)
152
+ {
153
+ Field realField = c.getSelectedIndex() == 0
154
+ ? null : getModel().getDB().getField((String) c.getSelectedItem());
155
+ getModel().setFieldAlias(realField, alias, GlobalDisplayPanel.this);
156
+ }
157
+ });
158
+ c.setBackground(Color.white);
159
+ c.setBorder(null);
160
+ panel.add(c, BorderLayout.CENTER);
161
+ encodings.add(panel);
162
+ c.setBorder(null);
163
+ return c;
164
+ }
165
+
166
+ @Override
167
+ public void note(TFEvent e)
168
+ {
169
+ if (e.affectsSchema())
170
+ {
171
+ makeEncodingPanel();
172
+ }
173
+ }
174
+}
timeflow/app/ui/HtmlDisplay.java
....@@ -0,0 +1,24 @@
1
+package timeflow.app.ui;
2
+
3
+import java.awt.Font;
4
+
5
+import javax.swing.JEditorPane;
6
+import javax.swing.UIManager;
7
+import javax.swing.text.html.HTMLDocument;
8
+import javax.swing.text.html.StyleSheet;
9
+
10
+public class HtmlDisplay {
11
+ public static JEditorPane create()
12
+ {
13
+ JEditorPane p = new JEditorPane();
14
+ p.setEditable(false);
15
+ p.setContentType("text/html");
16
+
17
+ Font font = UIManager.getFont("Label.font");
18
+ String bodyRule = "body { font-family: "+font.getFamily()+"; "+
19
+ "font-size: " + font.getSize() + "pt; }";
20
+ StyleSheet styles=((HTMLDocument)p.getDocument()).getStyleSheet();
21
+ styles.addRule(bodyRule);
22
+ return p;
23
+ }
24
+}
timeflow/app/ui/ImportDelimitedPanel.java
....@@ -0,0 +1,310 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.data.time.*;
4
+import timeflow.data.db.*;
5
+import timeflow.format.field.*;
6
+import timeflow.format.file.DelimitedFormat;
7
+import timeflow.model.*;
8
+
9
+import timeflow.util.*;
10
+
11
+import javax.swing.*;
12
+
13
+import java.awt.*;
14
+import java.awt.event.*;
15
+import java.text.ParseException;
16
+import java.util.*;
17
+import java.io.*;
18
+import java.net.MalformedURLException;
19
+import java.net.URL;
20
+
21
+public class ImportDelimitedPanel extends JFrame
22
+{
23
+ String fileName;
24
+ SchemaPanel schemaPanel;
25
+ boolean exitOnClose=false; // for testing!
26
+ JScrollPane scroller;
27
+ TFModel model;
28
+ JLabel numLinesLabel=new JLabel();
29
+
30
+ // for testing:
31
+ public static void main(String[] args) throws Exception
32
+ {
33
+ System.out.println("Starting test of ImportEditor");
34
+ String file="data/probate.tsv";
35
+ String[][] data=DelimitedFormat.readArrayGuessDelim(file, System.out);
36
+ ImportDelimitedPanel editor=new ImportDelimitedPanel(new TFModel());
37
+ editor.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
38
+ editor.setFileName(file);
39
+ editor.setData(data);
40
+ editor.setBounds(50,50,900,800);
41
+ editor.setVisible(true);
42
+ editor.exitOnClose=true;
43
+ }
44
+
45
+ public ImportDelimitedPanel(final TFModel model)
46
+ {
47
+ super("Import File");
48
+ this.model=model;
49
+ setBackground(Color.white);
50
+
51
+ setLayout(new BorderLayout());
52
+ JPanel top=new JPanel();
53
+ add(top, BorderLayout.NORTH);
54
+ top.setLayout(new FlowLayout(FlowLayout.LEFT));
55
+ top.setBackground(Color.lightGray);
56
+ top.add(numLinesLabel);
57
+ final JTextField source=new JTextField(12);
58
+
59
+ JButton done=new JButton("Import This");
60
+ top.add(done);
61
+ done.addActionListener(new ActionListener() {
62
+ @Override
63
+ public void actionPerformed(ActionEvent e) {
64
+ model.setDB(schemaPanel.makeDB(source.getText()), fileName, true, this);
65
+ setVisible(false);
66
+ if (exitOnClose)
67
+ System.exit(0);
68
+ }});
69
+
70
+ JButton cancel=new JButton("Cancel");
71
+ top.add(cancel);
72
+ cancel.addActionListener(new ActionListener() {
73
+ @Override
74
+ public void actionPerformed(ActionEvent e) {
75
+ setVisible(false);
76
+ if (exitOnClose)
77
+ System.exit(0);
78
+ }});
79
+
80
+ top.add(new JLabel(" Enter A Source:"));
81
+ top.add(source);
82
+ schemaPanel=new SchemaPanel();
83
+ schemaPanel.setBackground(Color.white);
84
+ scroller=new JScrollPane(schemaPanel);
85
+ add(scroller, BorderLayout.CENTER);
86
+ }
87
+
88
+ public void scrollToTop()
89
+ {
90
+ scroller.getViewport().setViewPosition(new Point(0,0));
91
+ }
92
+
93
+ public void setFileName(String fileName)
94
+ {
95
+ this.fileName=fileName;
96
+ }
97
+
98
+ public void setData(String[][] data)
99
+ {
100
+ numLinesLabel.setText((data.length-1)+" records read. ");
101
+ schemaPanel.display(data);
102
+ }
103
+
104
+ class SchemaPanel extends JPanel
105
+ {
106
+ int numFields, rows;
107
+ String[][] data;
108
+ ArrayList<FieldPanel> panels=new ArrayList<FieldPanel>();
109
+
110
+ ActDB makeDB(String source)
111
+ {
112
+ // count number of fields that are not ignored.
113
+ int n=0;
114
+ for (FieldPanel fp: panels)
115
+ if (!fp.ignore.isSelected())
116
+ n++;
117
+
118
+ Class[] types=new Class[n];
119
+ String[] fieldNames=new String[n];
120
+ int[] index=new int[n];
121
+ if (source.trim().length()==0)
122
+ source="[source unspecified]";
123
+ int i=0, j=0;
124
+ for (FieldPanel fp: panels)
125
+ {
126
+ if (!fp.ignore.isSelected())
127
+ {
128
+ fieldNames[i]=fp.fieldName;
129
+ String typeChoice=(String)fp.typeChoices.getSelectedItem();
130
+ Class type=FieldFormatCatalog.javaClass(typeChoice);
131
+ System.out.println("Type: "+type+" for: "+typeChoice+" from "+fp.fieldName);
132
+ types[i]=type;
133
+ index[i]=j;
134
+ i++;
135
+ }
136
+ j++;
137
+ }
138
+
139
+ ActDB db= new ArrayDB(fieldNames, types, source);
140
+ HashMap<Integer, StringBuffer> errors=new HashMap<Integer, StringBuffer>();
141
+ for (i=1; i<data.length; i++)
142
+ {
143
+ Act act=db.createAct();
144
+ for (int k=0; k<n; k++)
145
+ {
146
+ j=index[k];
147
+ String s=data[i][j];
148
+ Field f=db.getField(fieldNames[k]);
149
+ FieldFormat format=FieldFormatCatalog.getFormat(types[k]);
150
+ try
151
+ {
152
+ Object o=format.parse(s);
153
+ act.set(f,o);
154
+ }
155
+ catch (Exception e)
156
+ {
157
+ StringBuffer b=errors.get(i-1);
158
+ if (b==null)
159
+ {
160
+ b=new StringBuffer();
161
+ errors.put(i-1,b);
162
+ }
163
+ else
164
+ b.append("; ");
165
+ b.append(f.getName()+":"+s);
166
+ }
167
+ }
168
+ }
169
+
170
+ if (errors.size()>0)
171
+ {
172
+ Field error=db.addField("UNPARSED FIELDS", String.class);
173
+ for (int row:errors.keySet())
174
+ {
175
+ db.get(row).set(error, errors.get(row).toString());
176
+ }
177
+ }
178
+
179
+ for (j=0; j<n; j++)
180
+ {
181
+ System.out.println(db.getField(fieldNames[j]));
182
+ }
183
+
184
+ return db;
185
+ }
186
+
187
+ void display(String[][] data)
188
+ {
189
+ removeAll();
190
+
191
+ this.data=data;
192
+
193
+ // analyze data.
194
+ Class[] guesses=FieldFormatGuesser.analyze(data, 1, 100);
195
+
196
+ // go through first row, which is headers.
197
+ String[] headers=data[0];
198
+
199
+ // if there are duplicate headers, add indicators.
200
+ HashSet<String> h=new HashSet<String>();
201
+ for (int i=0; i<headers.length; i++)
202
+ {
203
+ String base=headers[i];
204
+ int j=2;
205
+ String name=base;
206
+ while (h.contains(name))
207
+ {
208
+ name=base+" "+j;
209
+ j++;
210
+ }
211
+ headers[i]=name;
212
+ h.add(name);
213
+ }
214
+
215
+ numFields=headers.length;
216
+ int cols=2;
217
+ rows=(int)Math.ceil(numFields/2.0);
218
+ setLayout(new GridLayout(rows, cols));
219
+ for (int i=0; i<numFields; i++)
220
+ {
221
+ Bag<String> vals=new Bag<String>();
222
+
223
+ for (int j=1; j<data.length; j++)
224
+ {
225
+ vals.add(data[j][i]);
226
+ }
227
+ java.util.List<String> top=vals.listTop(5);
228
+ int n=top.size();
229
+ String[] samples=new String[n];
230
+ for (int j=0; j<n; j++)
231
+ {
232
+ String s=top.get(j);
233
+ samples[j]=(s.length()==0 ? "*MISSING*" : s)+" ("+vals.num(s)+" times)";
234
+ }
235
+
236
+ JPanel p=new JPanel();
237
+ add(p);
238
+ p.setLayout(new BorderLayout());
239
+ FieldPanel f=new FieldPanel(headers[i], samples, guesses[i]);
240
+ panels.add(f);
241
+ p.add(f, BorderLayout.CENTER);
242
+ JPanel hr=new JPanel();
243
+ hr.setPreferredSize(new Dimension(20,20));
244
+ p.add(hr, BorderLayout.SOUTH);
245
+ }
246
+ }
247
+
248
+ public Dimension getPreferredSize()
249
+ {
250
+ return new Dimension(400,150*rows);
251
+ }
252
+ }
253
+
254
+ class FieldPanel extends JPanel
255
+ {
256
+ JComboBox typeChoices;
257
+ String fieldName;
258
+ JCheckBox ignore;
259
+ JLabel fieldLabel;
260
+ int x1=5, y1=20, y3=150,x2=150, x3=150, x4=375, dh=2;
261
+
262
+
263
+ FieldPanel(String fieldName, String[] sampleValues, Class typeGuess)
264
+ {
265
+ // just going with a null layout here, because it's a lot simpler!
266
+
267
+ setLayout(null);
268
+ setBackground(Color.white);
269
+ this.fieldName=fieldName;
270
+
271
+ fieldLabel=new JLabel(" \""+fieldName+'"');
272
+ fieldLabel.setFont(model.getDisplay().big());
273
+ add(fieldLabel);
274
+ fieldLabel.setBounds(x1,y1,fieldLabel.getPreferredSize().width, fieldLabel.getPreferredSize().height);
275
+
276
+ typeChoices=new JComboBox();
277
+ for (String choice: FieldFormatCatalog.classNames())
278
+ typeChoices.addItem(choice);
279
+ typeChoices.setSelectedItem(FieldFormatCatalog.humanName(typeGuess));
280
+ add(typeChoices);
281
+ int y2=fieldLabel.getY()+fieldLabel.getHeight()+dh+5;
282
+ typeChoices.setBounds(x1,y2,
283
+ typeChoices.getPreferredSize().width, typeChoices.getPreferredSize().height);
284
+
285
+ ignore=new JCheckBox("Ignore Field");
286
+ add(ignore);
287
+ ignore.setBounds(x1,typeChoices.getY()+typeChoices.getHeight()+dh,
288
+ ignore.getPreferredSize().width, ignore.getPreferredSize().height);
289
+ ignore.addActionListener(new ActionListener() {
290
+ @Override
291
+ public void actionPerformed(ActionEvent e) {
292
+ Color c=ignore.isSelected() ? Color.gray : Color.black;
293
+ fieldLabel.setForeground(c);
294
+ typeChoices.setForeground(c);
295
+ }});
296
+
297
+ JTextArea values=new JTextArea();
298
+ values.setForeground(Color.gray);
299
+ for (int i=0; i<sampleValues.length; i++)
300
+ values.append(sampleValues[i]+"\n");
301
+ add(values);
302
+ values.setBounds(x3,y2,x4-x3,y3-y2);
303
+ }
304
+
305
+ public Dimension getPreferredSize()
306
+ {
307
+ return new Dimension(500,150);
308
+ }
309
+ }
310
+}
timeflow/app/ui/LinkTabPane.java
....@@ -0,0 +1,208 @@
1
+package timeflow.app.ui;
2
+
3
+import javax.swing.*;
4
+import javax.swing.event.ChangeListener;
5
+
6
+import java.awt.*;
7
+import java.awt.event.*;
8
+import java.util.*;
9
+
10
+// custom JTabbedPane-like thing.
11
+public class LinkTabPane extends JPanel {
12
+
13
+ ArrayList<String> tabNames=new ArrayList<String>();
14
+ HashMap<String, JComponent> tabMap=new HashMap<String, JComponent>();
15
+ String currentName;
16
+ CardLayout cards=new CardLayout();
17
+ JPanel center=new JPanel();
18
+ LinkTop top=new LinkTop();
19
+
20
+ public LinkTabPane()
21
+ {
22
+ setBackground(Color.white);
23
+ setLayout(new BorderLayout());
24
+ add(top, BorderLayout.NORTH);
25
+ add(center, BorderLayout.CENTER);
26
+ center.setLayout(cards);
27
+ top.addMouseListener(new MouseAdapter() {
28
+ @Override
29
+ public void mousePressed(MouseEvent e) {
30
+ String s=top.getName(e.getX());
31
+ if (s!=null)
32
+ {
33
+ String old=currentName;
34
+ setCurrentName(s);
35
+ firePropertyChange("tab", old, s);
36
+ }
37
+ }});
38
+ }
39
+
40
+ public String getTitleAt(int i)
41
+ {
42
+ return tabNames.get(i);
43
+ }
44
+
45
+ public void setSelectedIndex(int i)
46
+ {
47
+ setCurrentName(getTitleAt(i));
48
+ }
49
+
50
+ public void addTab(JComponent component, String name, boolean left)
51
+ {
52
+ tabNames.add(name);
53
+ tabMap.put(name, component);
54
+ center.add(component, name);
55
+ top.addName(name, left);
56
+ repaint();
57
+ if (currentName==null)
58
+ currentName=name;
59
+ }
60
+
61
+ public String getCurrentName()
62
+ {
63
+ return currentName;
64
+ }
65
+
66
+ public void setCurrentName(final String currentName)
67
+ {
68
+ this.currentName=currentName;
69
+ top.repaint();
70
+ SwingUtilities.invokeLater(new Runnable() {public void run() {cards.show(center, currentName);}});
71
+
72
+ }
73
+
74
+ class LinkTop extends JPanel
75
+ {
76
+ int left, right;
77
+ ArrayList<HotLink> leftHots=new ArrayList<HotLink>();
78
+ ArrayList<HotLink> rightHots=new ArrayList<HotLink>();
79
+ Font font=new Font("Verdana", Font.PLAIN, 14);
80
+ FontMetrics fm=getFontMetrics(font);
81
+
82
+ LinkTop()
83
+ {
84
+ setLayout(new FlowLayout(FlowLayout.LEFT));
85
+ setBackground(new Color(220,220,220));
86
+ }
87
+
88
+ public void paintComponent(Graphics g1)
89
+ {
90
+ int w=getSize().width, h=getSize().height;
91
+ Graphics2D g=(Graphics2D)g1;
92
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
93
+ g.setColor(getBackground());
94
+ g.fillRect(0,0,w,h);
95
+ g.setColor(Color.gray);
96
+ for (int i=0; i<2; i++)
97
+ {
98
+ g.drawLine(0,i,w,i);
99
+ g.drawLine(0,h-1-i,w,h-1-i);
100
+ }
101
+
102
+ for (HotLink hot: leftHots)
103
+ {
104
+ draw(g, hot, h, 0);
105
+ }
106
+
107
+ for (HotLink hot: rightHots)
108
+ {
109
+ draw(g, hot, h, w);
110
+ }
111
+
112
+ for (int i=0; i<leftHots.size(); i++)
113
+ {
114
+
115
+ if (i<leftHots.size()-1)
116
+ {
117
+ HotLink hot=leftHots.get(i);
118
+ for (int j=0; j<1; j++)
119
+ g.drawLine(hot.x+hot.width-1-j, 7, hot.x+hot.width-1-j, h-7);
120
+ }
121
+ }
122
+
123
+ for (int i=0; i<rightHots.size(); i++)
124
+ {
125
+
126
+ if (i<rightHots.size()-1)
127
+ {
128
+ HotLink hot=rightHots.get(i);
129
+ for (int j=0; j<1; j++)
130
+ g.drawLine(hot.x+w-1-j, 7, hot.x+w-1-j, h-7);
131
+ }
132
+ }
133
+ }
134
+
135
+ void draw(Graphics g, HotLink hot, int h, int dx)
136
+ {
137
+ int x=hot.x+dx;
138
+ if (hot.s.equals(currentName))
139
+ {
140
+ g.setColor(Color.lightGray);
141
+ g.fillRect(x,2,hot.width,h-4);
142
+ g.setColor(Color.gray);
143
+ g.drawLine(x-1, 0, x-1, h);
144
+ g.drawLine(x+hot.width-1, 0, x+hot.width-1, h);
145
+ }
146
+ g.setColor(Color.darkGray);
147
+ g.setFont(font);
148
+ int sw=fm.stringWidth(hot.s);
149
+ g.drawString(hot.s, x+(hot.width-sw)/2, h-10);
150
+
151
+ }
152
+
153
+ String getName(int x)
154
+ {
155
+ for (HotLink h: leftHots)
156
+ {
157
+ if (h.x<=x && h.x+h.width>x)
158
+ return h.s;
159
+ }
160
+ for (HotLink h: rightHots)
161
+ {
162
+ int w=getSize().width;
163
+ if (h.x+w<=x && h.x+h.width+w>x)
164
+ return h.s;
165
+ }
166
+
167
+ if (leftHots.size()>0)
168
+ return leftHots.get(leftHots.size()-1).s;
169
+ if (rightHots.size()>0)
170
+ return rightHots.get(0).s;
171
+ return null;
172
+ }
173
+
174
+ void addName(String name, boolean leftward)
175
+ {
176
+ if (leftward)
177
+ {
178
+ int x=right;
179
+ int w=fm.stringWidth(name)+24;
180
+ leftHots.add(new HotLink(name, x, 0, w, 30));
181
+ right+=w;
182
+ }
183
+ else
184
+ {
185
+ int x=left;
186
+ int w=fm.stringWidth(name)+24;
187
+ rightHots.add(new HotLink(name, x-w, 0, w, 30));
188
+ left-=w;
189
+ }
190
+ }
191
+
192
+ class HotLink extends Rectangle
193
+ {
194
+ String s;
195
+ HotLink(String s, int x, int y, int w, int h)
196
+ {
197
+ super(x,y,w,h);
198
+ this.s=s;
199
+ }
200
+ }
201
+
202
+ public Dimension getPreferredSize()
203
+ {
204
+ return new Dimension(30,30);
205
+ }
206
+ }
207
+
208
+}
timeflow/app/ui/MassDeletePanel.java
....@@ -0,0 +1,80 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.model.*;
5
+import timeflow.views.*;
6
+
7
+import javax.swing.*;
8
+import javax.swing.table.*;
9
+
10
+import java.awt.*;
11
+import java.util.*;
12
+
13
+public class MassDeletePanel extends ModelPanel
14
+{
15
+ TableView table;
16
+ ActList keepers;
17
+
18
+ public MassDeletePanel(TFModel model, ActList keepers, String title)
19
+ {
20
+ super(model);
21
+ this.keepers=keepers;
22
+ setLayout(new BorderLayout());
23
+
24
+ JPanel top=new JPanel();
25
+ top.setLayout(new GridLayout(4,1));
26
+ top.add(new JPanel());
27
+ top.add(new JLabel(title));
28
+ int n=keepers.size();
29
+ String message=null;
30
+ if (n>1)
31
+ message="These are the "+n+" items that will remain.";
32
+ else if (n==1)
33
+ message="This in the only item that will remain.";
34
+ else
35
+ message="No items will remain!";
36
+
37
+ JLabel instructions=new JLabel(message);
38
+ top.add(instructions);
39
+ top.add(new JPanel());
40
+ add(top, BorderLayout.NORTH);
41
+
42
+ table=new TableView(model);
43
+ model.removeListener(table);
44
+ add(table, BorderLayout.CENTER);
45
+ table.setEditable(false);
46
+ table.setActs(keepers);
47
+ }
48
+
49
+ public void applyAction()
50
+ {
51
+ ActDB db=getModel().getDB();
52
+ HashSet<Act> keepSet=new HashSet<Act>();
53
+ keepSet.addAll(keepers);
54
+
55
+ for (Act a: db.all())
56
+ if (!keepSet.contains(a))
57
+ db.delete(a);
58
+
59
+ // we assume the caller will decide what event to fire.
60
+ }
61
+
62
+ public void detachFromModel()
63
+ {
64
+ TFModel model=getModel();
65
+ model.removeListener(table);
66
+ model.removeListener(this);
67
+ }
68
+
69
+ public Dimension getPreferredSize()
70
+ {
71
+ Dimension d=super.getPreferredSize();
72
+ return new Dimension(Math.max(700, d.width), 250);
73
+ }
74
+
75
+ @Override
76
+ public void note(TFEvent e) {
77
+ // TODO Auto-generated method stub
78
+
79
+ }
80
+}
timeflow/app/ui/ReorderFieldsPanel.java
....@@ -0,0 +1,68 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.model.*;
5
+import timeflow.views.*;
6
+
7
+import javax.swing.*;
8
+import javax.swing.table.*;
9
+
10
+import java.awt.*;
11
+import java.util.*;
12
+
13
+public class ReorderFieldsPanel extends ModelPanel
14
+{
15
+ TableView table;
16
+
17
+ public ReorderFieldsPanel(TFModel model)
18
+ {
19
+ super(model);
20
+ setLayout(new BorderLayout());
21
+
22
+ JPanel top=new JPanel();
23
+ top.setLayout(new GridLayout(3,1));
24
+ top.add(new JPanel());
25
+ JLabel instructions=new JLabel("Drag and drop the column headers to reorder fields.");
26
+ top.add(instructions);
27
+ top.add(new JPanel());
28
+ add(top, BorderLayout.NORTH);
29
+
30
+ table=new TableView(model);
31
+ add(table, BorderLayout.CENTER);
32
+ table.setEditable(false);
33
+ table.setReorderable(true);
34
+ table.onscreen(true);
35
+ }
36
+
37
+ public void applyReordering()
38
+ {
39
+ Enumeration<TableColumn> columns=table.getTable().getTableHeader().getColumnModel().getColumns();
40
+ ArrayList<Field> newOrder=new ArrayList<Field>();
41
+ while (columns.hasMoreElements())
42
+ {
43
+ TableColumn col=columns.nextElement();
44
+ String name=col.getHeaderValue().toString();
45
+ newOrder.add(getModel().getDB().getField(name));
46
+ }
47
+ getModel().getDB().setNewFieldOrder(newOrder);
48
+ }
49
+
50
+ public void detachFromModel()
51
+ {
52
+ TFModel model=getModel();
53
+ model.removeListener(table);
54
+ model.removeListener(this);
55
+ }
56
+
57
+ public Dimension getPreferredSize()
58
+ {
59
+ Dimension d=super.getPreferredSize();
60
+ return new Dimension(Math.max(700, d.width), 250);
61
+ }
62
+
63
+ @Override
64
+ public void note(TFEvent e) {
65
+ // TODO Auto-generated method stub
66
+
67
+ }
68
+}
timeflow/app/ui/SizeLegendPanel.java
....@@ -0,0 +1,113 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.model.*;
4
+import timeflow.data.db.*;
5
+import timeflow.data.time.*;
6
+
7
+import timeflow.util.*;
8
+
9
+import java.awt.*;
10
+import java.awt.geom.AffineTransform;
11
+
12
+public class SizeLegendPanel extends ModelPanel {
13
+ Field sizeField;
14
+ double min, max;
15
+
16
+ public SizeLegendPanel(TFModel model)
17
+ {
18
+ super(model);
19
+ setBackground(Color.white);
20
+ }
21
+
22
+ @Override
23
+ public void note(TFEvent e) {
24
+ Field size=getModel().getDB().getField(VirtualField.SIZE);
25
+
26
+ if (size!=null && (size!=sizeField || e.affectsData()))
27
+ {
28
+ double[] minmax=DBUtils.minmax(getModel().getActs(), size);
29
+ min=minmax[0];
30
+ max=minmax[1];
31
+ }
32
+ sizeField=size;
33
+ repaint();
34
+ }
35
+
36
+ public Dimension getPreferredSize()
37
+ {
38
+ return new Dimension(200,40);
39
+ }
40
+
41
+ public void paintComponent(Graphics g1)
42
+ {
43
+ Graphics2D g=(Graphics2D)g1;
44
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
45
+ int w=getSize().width;
46
+ int h=getSize().height;
47
+ TFModel model=getModel();
48
+ Display display=model.getDisplay();
49
+ g.setColor(getBackground());
50
+ g.setFont(display.plain());
51
+ g.fillRect(0,0,w,h);
52
+ g.setColor(Color.gray);
53
+
54
+ if (sizeField==null)
55
+ {
56
+ return;
57
+ }
58
+ else if (Double.isNaN(min))
59
+ {
60
+ g.drawString("All values missing.",3,20);
61
+ return;
62
+ }
63
+ else
64
+ {
65
+ AffineTransform old=g.getTransform();
66
+ g.setTransform(AffineTransform.getTranslateInstance(20, 0));
67
+ if (min==max)
68
+ {
69
+ g.setColor(Color.gray);
70
+ g.fillOval(3,h/2-3,6,6);
71
+ g.setColor(Color.black);
72
+ g.setFont(display.tiny());
73
+ g.drawString(format(min),12,h/2+5);
74
+ }
75
+ else
76
+ {
77
+ String leftLabel=format(min);
78
+ String rightLabel=format(max);
79
+ g.setFont(display.tiny());
80
+ int lw=display.tinyFontMetrics().stringWidth(leftLabel);
81
+ int rw=display.tinyFontMetrics().stringWidth(rightLabel);
82
+ g.setColor(Color.black);
83
+ int ty=h/2+5;;
84
+ g.drawString(leftLabel,2,ty);
85
+ g.setColor(Color.lightGray);
86
+ double maxAbs=Math.max(Math.abs(min), Math.abs(max));
87
+ int dx=8+lw;
88
+ for (int i=0; i<5; i++)
89
+ {
90
+ double z=(i*max+(4-i)*min)/4;
91
+ int r=(int)(Math.sqrt(Math.abs(z/maxAbs))*Display.MAX_DOT_SIZE);
92
+ if (r<1)
93
+ r=1;
94
+ if (z>0)
95
+ g.fillOval(dx,h/2-r,2*r,2*r);
96
+ else
97
+ g.drawOval(dx,h/2-r,2*r,2*r);
98
+ dx+=5+2*r;
99
+ }
100
+ g.setColor(Color.black);
101
+ g.drawString(rightLabel,dx+4,ty);
102
+ }
103
+ g.setTransform(old);
104
+ }
105
+ }
106
+
107
+ String format(double x)
108
+ {
109
+ if (Math.abs(x)>10 && (max-min)>10)
110
+ return Display.format(Math.round(x));
111
+ return Display.format(x);
112
+ }
113
+}
timeflow/app/ui/StatusPanel.java
....@@ -0,0 +1,98 @@
1
+package timeflow.app.ui;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.filter.FilterControlPanel;
5
+import timeflow.data.db.*;
6
+import timeflow.data.db.filter.ActFilter;
7
+
8
+import javax.swing.*;
9
+import java.awt.*;
10
+import java.awt.event.ActionEvent;
11
+import java.awt.event.ActionListener;
12
+import java.text.*;
13
+
14
+public class StatusPanel extends ModelPanel
15
+{
16
+ JLabel numLabel=new JLabel("")
17
+ {
18
+ public Dimension getPreferredSize()
19
+ {
20
+ return new Dimension(30,25);
21
+ }
22
+ };
23
+ JLabel filterLabel=new JLabel("")
24
+ {
25
+ public Dimension getPreferredSize()
26
+ {
27
+ return new Dimension(30,25);
28
+ }
29
+ };
30
+
31
+ static final DecimalFormat niceFormat=new DecimalFormat("###,###");
32
+
33
+ public StatusPanel(TFModel model, final FilterControlPanel filterControls) {
34
+ super(model);
35
+ setLayout(new BorderLayout());
36
+ setBackground(new Color(245, 245, 245));
37
+ setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 15));
38
+ JPanel center=new JPanel();
39
+ center.setBackground(getBackground());
40
+ center.setLayout(new GridLayout(2,1));
41
+ add(center, BorderLayout.CENTER);
42
+
43
+ center.add(numLabel);
44
+ numLabel.setFont(model.getDisplay().plain());
45
+ numLabel.setBackground(new Color(245, 245, 245));
46
+
47
+ JPanel bottom=new JPanel();
48
+ center.add(bottom);
49
+ bottom.setLayout(new BorderLayout());
50
+ bottom.add(filterLabel, BorderLayout.CENTER);
51
+ bottom.setBackground(new Color(245, 245, 245));
52
+ filterLabel.setFont(model.getDisplay().plain());
53
+ filterLabel.setBackground(new Color(245, 245, 245));
54
+ filterLabel.setForeground(Color.red);
55
+
56
+ JPanel clearPanel=new JPanel();
57
+ clearPanel.setBackground(new Color(245, 245, 245));
58
+ clearPanel.setLayout(new GridLayout(1,1));
59
+ JButton clear=new JButton(new ImageIcon("images/button_clear_all.gif"));
60
+ clear.setBorder(null);
61
+ clear.addActionListener(new ActionListener() {
62
+ @Override
63
+ public void actionPerformed(ActionEvent e) {
64
+ filterControls.clearFilters();
65
+ }
66
+ });
67
+ clearPanel.add(clear);
68
+ bottom.add(clearPanel, BorderLayout.EAST);
69
+
70
+ add(new DottedLine(), BorderLayout.SOUTH);
71
+ }
72
+
73
+ @Override
74
+ public void note(TFEvent e) {
75
+
76
+ ActDB db=getModel().getDB();
77
+ if (db==null || db.size()==0)
78
+ {
79
+ numLabel.setForeground(new Color(245,245,245));//Color.gray);
80
+ numLabel.setText("No data");
81
+ return;
82
+ }
83
+ int numTotal=db.size();
84
+ ActList acts=getModel().getActs();
85
+ int numShown=acts.size();
86
+ filterLabel.setText(numShown<numTotal ? " Filters applied" : " Not Filtering");
87
+ filterLabel.setForeground(numShown==numTotal ? Color.lightGray : Color.red);
88
+ numLabel.setForeground(numShown==0 ? Color.red : Color.darkGray);
89
+ String plural=(numTotal==1 ? "" : "s");
90
+ if (numShown==numTotal)
91
+ numLabel.setText(" Showing All "+niceFormat.format(numTotal)+" Event"+plural);
92
+ else
93
+ numLabel.setText(" Showing "+niceFormat.format(numShown)
94
+ +" / "+niceFormat.format(numTotal)+" Event"+ plural);
95
+ repaint();
96
+ }
97
+
98
+}
timeflow/app/ui/WaitingDialog.java
....@@ -0,0 +1,56 @@
1
+package timeflow.app.ui;
2
+
3
+import javax.swing.*;
4
+import java.awt.*;
5
+import java.awt.event.*;
6
+
7
+public class WaitingDialog extends JFrame {
8
+
9
+ Timer timer;
10
+
11
+ public static void main(String[] args)
12
+ {
13
+ new WaitingDialog("Testing", "Hello, world!");
14
+ }
15
+
16
+ public WaitingDialog(String title, String message)
17
+ {
18
+ super(title);
19
+ Throbber throbber=new Throbber();
20
+ timer=new Timer(50, throbber);
21
+ getContentPane().setLayout(new FlowLayout(FlowLayout.LEFT));
22
+ getContentPane().add(throbber);
23
+ getContentPane().add(new JLabel(message));
24
+ setBounds(400,400,300,150);
25
+ setVisible(true);
26
+ timer.start();
27
+ }
28
+
29
+ public void stop()
30
+ {
31
+ timer.stop();
32
+ setVisible(false);
33
+ }
34
+
35
+ class Throbber extends JPanel implements ActionListener
36
+ {
37
+ int count=0;
38
+ public void paintComponent(Graphics g)
39
+ {
40
+ int w=getSize().width, h=getSize().height;
41
+ int c=count%256;
42
+ g.setColor(new Color(c,c,c));
43
+ g.fillRect(0,0,w,h);
44
+ }
45
+ @Override
46
+ public void actionPerformed(ActionEvent e) {
47
+ repaint();
48
+ count+=10;
49
+ }
50
+
51
+ public Dimension getPreferredSize()
52
+ {
53
+ return new Dimension(30,100);
54
+ }
55
+ }
56
+}
timeflow/app/ui/filter/BabyHistogram.java
....@@ -0,0 +1,274 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import javax.swing.*;
4
+
5
+import timeflow.data.time.Interval;
6
+import timeflow.model.Display;
7
+
8
+
9
+import java.awt.*;
10
+import java.awt.event.MouseAdapter;
11
+import java.awt.event.MouseEvent;
12
+import java.awt.event.MouseMotionAdapter;
13
+import java.text.*;
14
+
15
+
16
+public class BabyHistogram extends JPanel {
17
+ private int[] buckets;
18
+ private double[] x;
19
+ private double min, max;
20
+ private int numDefined;
21
+ private int maxBucket;
22
+ private double value;
23
+ private static final DecimalFormat df=new DecimalFormat("###,###,###,###.##");
24
+
25
+
26
+ Point mouseHit=new Point();
27
+ Point mouse=new Point(-1,0);
28
+ enum Modify {START, END, POSITION, NONE};
29
+ Modify change=Modify.NONE;
30
+ Rectangle startRect=new Rectangle(-1,-1,0,0);
31
+ Rectangle endRect=new Rectangle(-1,-1,0,0);
32
+ Rectangle positionRect=new Rectangle(-1,-1,0,0);
33
+ Color sidePlain=Color.orange;
34
+ Color sideMouse=new Color(230,100,0);
35
+ double relLow=0, relHigh=1, originalLow, originalHigh;
36
+
37
+ public void setRelRange(double relLow, double relHigh)
38
+ {
39
+ this.relLow=Math.max(0,relLow);
40
+ this.relHigh=Math.min(1,relHigh);
41
+ repaint();
42
+ }
43
+
44
+ public void setTrueRange(double low, double high)
45
+ {
46
+ double span=max-min;
47
+ if (span<=0) // nothing much to do...
48
+ return;
49
+
50
+ setRelRange((low-min)/span, (high-min)/span);
51
+ }
52
+
53
+ public boolean isEverything()
54
+ {
55
+ return relLow==0 && relHigh==1;
56
+ }
57
+
58
+ public double getLow()
59
+ {
60
+ return abs(relLow);
61
+ }
62
+
63
+ public double getHigh()
64
+ {
65
+ return abs(relHigh);
66
+ }
67
+
68
+ private double abs(double x)
69
+ {
70
+ return x*max+(1-x)*min;
71
+ }
72
+
73
+ public BabyHistogram(final Runnable changeAction)
74
+ {
75
+
76
+ addMouseListener(new MouseAdapter() {
77
+
78
+ @Override
79
+ public void mousePressed(MouseEvent e) {
80
+ int mx=e.getX();
81
+ int my=e.getY();
82
+ int lx=lowX(), hx=highX();
83
+
84
+ int ox=0;
85
+ if (Math.abs(mx-lx)<Math.abs(mx-hx))
86
+ {
87
+ change=Modify.START;
88
+ ox=lx;
89
+ }
90
+ else
91
+ {
92
+ change=Modify.END;
93
+ ox=hx;
94
+ }
95
+ mouseHit.setLocation(ox,my);
96
+ mouse.setLocation(mx,my);
97
+ originalLow=relLow;
98
+ originalHigh=relHigh;
99
+ repaint();
100
+ }
101
+
102
+ @Override
103
+ public void mouseReleased(MouseEvent e) {
104
+ change=Modify.NONE;
105
+ repaint();
106
+ }});
107
+ addMouseMotionListener(new MouseMotionAdapter() {
108
+
109
+ @Override
110
+ public void mouseDragged(MouseEvent e) {
111
+
112
+ if (change==Modify.NONE)
113
+ return;
114
+ mouse.setLocation(e.getX(), e.getY());
115
+ int mouseDiff=mouse.x-mouseHit.x;
116
+ double relDiff=mouseDiff/(double)getSize().width;
117
+ switch (change)
118
+ {
119
+ case POSITION:
120
+ relLow=originalLow+relDiff;
121
+ relHigh=originalHigh+relDiff;
122
+
123
+ break;
124
+ case START: relLow=originalLow+relDiff;
125
+ break;
126
+ case END: relHigh=originalHigh+relDiff;
127
+ }
128
+ relLow=Math.max(0, relLow);
129
+ relHigh=Math.min(1, relHigh);
130
+ changeAction.run();
131
+ repaint();
132
+ }
133
+ });
134
+
135
+ }
136
+
137
+ public void setData(double[] x)
138
+ {
139
+ relLow=0;
140
+ relHigh=1;
141
+
142
+ this.x=x;
143
+ int n=x.length;
144
+
145
+ // do some quick checks on the data.
146
+ boolean positive=true;
147
+ min=Double.NaN;
148
+ max=Double.NaN;
149
+ numDefined=0;
150
+ for (int i=0; i<n; i++)
151
+ {
152
+ double m=x[i];
153
+ if (!Double.isNaN(m))
154
+ {
155
+ numDefined++;
156
+ positive &= m>0;
157
+ if (Double.isNaN(min))
158
+ {
159
+ min=m;
160
+ max=m;
161
+ value=m;
162
+ }
163
+ else
164
+ {
165
+ min=Math.min(m, min);
166
+ max=Math.max(m, max);
167
+ }
168
+ }
169
+ }
170
+
171
+ if (numDefined==0)
172
+ return;
173
+ if (min==max)
174
+ {
175
+ buckets=new int[1];
176
+ buckets[0]=numDefined;
177
+ maxBucket=numDefined;
178
+ return;
179
+ }
180
+ int numBuckets=(int)Math.min(50, 2*Math.sqrt(numDefined));
181
+ buckets=new int[numBuckets];
182
+ maxBucket=0;
183
+ for (int i=0; i<n; i++)
184
+ {
185
+ if (!Double.isNaN(x[i]))
186
+ {
187
+ int b=(int)((numBuckets-1)*(x[i]-min)/(max-min));
188
+ buckets[b]++;
189
+ maxBucket=Math.max(maxBucket, buckets[b]);
190
+ }
191
+ }
192
+ }
193
+
194
+ public void paintComponent(Graphics g)
195
+ {
196
+ int w=getSize().width;
197
+ int h=getSize().height;
198
+ g.setColor(Color.white);
199
+ g.fillRect(0,0,w,h);
200
+
201
+ if (x==null)
202
+ {
203
+ say(g, "No data");
204
+ return;
205
+ }
206
+
207
+ if (x.length==0)
208
+ {
209
+ say(g, "No values");
210
+ return;
211
+ }
212
+
213
+ if (numDefined==0)
214
+ {
215
+ say(g, "No defined values");
216
+ return;
217
+ }
218
+
219
+ int n=buckets.length;
220
+ if (n==1)
221
+ {
222
+ say(g, "All defined vals = "+df.format(value));
223
+ return;
224
+ }
225
+
226
+ // wow, if we got here we really have a histogram and not a degenerate mess!
227
+
228
+ Color bar=Display.barColor;
229
+ g.setColor(bar);
230
+ for (int i=0; i<n; i++)
231
+ {
232
+ int x1=(i*w)/n;
233
+ int x2=((i+1)*w)/n;
234
+ int y1=h-(buckets[i]*h)/maxBucket;
235
+ if (buckets[i]>0 && y1>h-2)
236
+ y1=h-2;
237
+ g.fillRect(x1,y1,x2-x1-1,h-y1);
238
+ }
239
+
240
+ // now draw thumb.
241
+
242
+ int thumb1=lowX();
243
+ int thumb2=highX();
244
+ g.setColor(Color.black);
245
+ g.drawLine(thumb1,0,thumb1,h);
246
+ g.drawLine(thumb2,0,thumb2,h);
247
+ g.setColor(new Color(235,235,235,160));
248
+ g.fillRect(0,0,thumb1,h);
249
+ g.fillRect(thumb2+1,0,w-thumb2-1,h);
250
+ g.setColor(Color.lightGray);
251
+ g.drawRect(0,0,w-1,h-1);
252
+ }
253
+
254
+ int lowX()
255
+ {
256
+ return (int)((getSize().width-1)*relLow);
257
+ }
258
+
259
+ int highX()
260
+ {
261
+ return (int)((getSize().width-1)*relHigh);
262
+ }
263
+
264
+ void say(Graphics g, String s)
265
+ {
266
+ g.setColor(Color.gray);
267
+ g.drawString(s,5,getSize().height-5);
268
+ }
269
+
270
+ public Dimension getPreferredSize()
271
+ {
272
+ return new Dimension(200,60);
273
+ }
274
+}
timeflow/app/ui/filter/FilterCategoryPanel.java
....@@ -0,0 +1,164 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import timeflow.util.*;
4
+
5
+import javax.swing.*;
6
+
7
+import timeflow.data.db.*;
8
+import timeflow.data.db.filter.*;
9
+import timeflow.model.ModelPanel;
10
+
11
+import java.awt.*;
12
+import java.awt.event.*;
13
+
14
+public class FilterCategoryPanel extends FilterDefinitionPanel
15
+{
16
+ public JList dataList=new JList();
17
+ Field field;
18
+
19
+ public FilterCategoryPanel(final Field field, final ModelPanel parent)
20
+ {
21
+ this(field.getName(), field, parent);
22
+ }
23
+
24
+ public FilterCategoryPanel(String title, final Field field, final ModelPanel parent)
25
+ {
26
+ this.field=field;
27
+ setLayout(new BorderLayout());
28
+ setBackground(Color.white);
29
+ setBorder(BorderFactory.createEmptyBorder(0,5,0,5));
30
+
31
+ add(new FilterTitle(title, field, parent, true), BorderLayout.NORTH);
32
+
33
+
34
+ JScrollPane scroller=new JScrollPane(dataList);
35
+ scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
36
+ scroller.setBorder(null);
37
+ add(scroller, BorderLayout.CENTER);
38
+ dataList.setForeground(Color.darkGray);
39
+ dataList.setSelectionForeground(Color.black);
40
+ dataList.setSelectionBackground(new Color(220,235,255));
41
+ dataList.setFont(parent.getModel().getDisplay().small());
42
+ scroller.getVerticalScrollBar().setBackground(Color.white);
43
+
44
+
45
+ // ok, the following is ugly code to insert a new mouselistener
46
+ // that lets the user deselect items when they are clicked.
47
+ // i tried a bunch of stuff but this is all that would work--
48
+ // and searching the web yielded only solutions similar to this.
49
+ // also, there's a weird dance with consuming/not consuming events
50
+ // that is designed to allow a certain kind of multi-selection behavior
51
+ // with the mouse, while letting you scroll through items one at a time
52
+ // with the keyboard. this was the product of a long series of
53
+ // conversations with target users.
54
+ MouseListener[] old = dataList.getMouseListeners();
55
+ for (MouseListener m: old)
56
+ dataList.removeMouseListener(m);
57
+
58
+ dataList.addMouseListener(new MouseAdapter()
59
+ {
60
+ public void mousePressed(MouseEvent e)
61
+ {
62
+ if (e.isControlDown() || e.isMetaDown() || e.isShiftDown())
63
+ return;
64
+ final int index = dataList.locationToIndex(e.getPoint());
65
+ if (dataList.isSelectedIndex(index))
66
+ {
67
+ SwingUtilities.invokeLater(new Runnable()
68
+ {
69
+ public void run()
70
+ {
71
+ dataList.removeSelectionInterval(index, index);
72
+
73
+ }
74
+ });
75
+ e.consume();
76
+ }
77
+ else
78
+ {
79
+ SwingUtilities.invokeLater(new Runnable()
80
+ {
81
+ public void run()
82
+ {
83
+ dataList.addSelectionInterval(index, index);
84
+
85
+ }
86
+ });
87
+ e.consume();
88
+ }
89
+ }
90
+ });
91
+
92
+ for (MouseListener m: old)
93
+ dataList.addMouseListener(m);
94
+
95
+ dataList.setCellRenderer(new DefaultListCellRenderer() {
96
+ @Override
97
+ public Component getListCellRendererComponent(JList list,
98
+ Object value, int index, boolean isSelected,
99
+ boolean cellHasFocus) {
100
+ Component c=super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
101
+ if (field==parent.getModel().getColorField())
102
+ {
103
+ String text=value.toString();
104
+ int n=text.lastIndexOf('-');
105
+ if (n>1)
106
+ text=text.substring(0,n-1);
107
+ c.setForeground(parent.getModel().getDisplay().makeColor(text));
108
+ }
109
+ return c;
110
+ }});
111
+
112
+ }
113
+
114
+ public void setData(Bag<String> data)
115
+ {
116
+ dataList.removeAll();
117
+ java.util.List<String> items=data.list();
118
+ String[] s=(String[])items.toArray(new String[0]);
119
+ for (int i=0; i<s.length; i++)
120
+ {
121
+ int num=data.num(s[i]);
122
+ if (s[i]==null || s[i].length()==0)
123
+ s[i]="(missing)";
124
+ s[i]+=" - "+num;
125
+ }
126
+ dataList.setListData(s);
127
+ }
128
+
129
+ public Dimension getPreferredSize()
130
+ {
131
+ return new Dimension(200,200);
132
+ }
133
+
134
+ @Override
135
+ public ActFilter defineFilter() {
136
+ Object[] o=dataList.getSelectedValues();
137
+ if (o==null || o.length==0)
138
+ return null;
139
+
140
+ int n=o.length;
141
+ String[] s=new String[n];
142
+ for (int i=0; i<n; i++)
143
+ {
144
+ String w=(String)o[i];
145
+ int m=w.lastIndexOf('-');
146
+ s[i]=w.substring(0, m-1);
147
+ if ("(missing)".equals(s[i]))
148
+ s[i]="";
149
+ }
150
+
151
+ if (s.length==1)
152
+ return new FieldValueFilter(field, s[0]);
153
+ FieldValueSetFilter f=new FieldValueSetFilter(field);
154
+ for (int i=0; i<s.length; i++)
155
+ f.addValue(s[i]);
156
+ return f;
157
+ }
158
+
159
+ @Override
160
+ public void clearFilter() {
161
+ dataList.clearSelection();
162
+ }
163
+
164
+}
timeflow/app/ui/filter/FilterControlPanel.java
....@@ -0,0 +1,208 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import timeflow.model.*;
4
+import timeflow.app.ui.StatusPanel;
5
+import timeflow.data.db.*;
6
+import timeflow.data.db.filter.*;
7
+import timeflow.data.time.RoughTime;
8
+
9
+import timeflow.util.*;
10
+
11
+import java.util.*;
12
+import javax.swing.*;
13
+import javax.swing.event.ListSelectionEvent;
14
+import javax.swing.event.ListSelectionListener;
15
+
16
+import java.awt.*;
17
+
18
+public class FilterControlPanel extends ModelPanel
19
+{
20
+ FacetSubpanel inside=new FacetSubpanel();
21
+ SearchPanel searchPanel;
22
+ boolean inverted=false;
23
+ JMenu menuToSyncWith;
24
+
25
+
26
+ public FilterControlPanel(TFModel model, JMenu menuToSyncWith)
27
+ {
28
+ super(model);
29
+ this.menuToSyncWith=menuToSyncWith;
30
+ searchPanel=new SearchPanel(model, this);
31
+ setLayout(new BorderLayout());
32
+ setBackground(Color.white);
33
+ JPanel top=new JPanel();
34
+ top.setBackground(Color.white);
35
+ top.setLayout(new BorderLayout());
36
+ top.setBackground(Color.white);
37
+
38
+ top.add(new StatusPanel(model, this), BorderLayout.NORTH);
39
+ top.add(searchPanel, BorderLayout.CENTER);
40
+
41
+ add(top, BorderLayout.NORTH);
42
+ add(inside, BorderLayout.CENTER);
43
+ }
44
+
45
+ void setInverted(boolean inverted)
46
+ {
47
+ this.inverted=inverted;
48
+ makeFilter();
49
+ }
50
+
51
+ void makeFilter()
52
+ {
53
+ AndFilter filter=new AndFilter();
54
+ String s=searchPanel.entry.getText();
55
+ if (s.length()>0)
56
+ filter.and(new StringMatchFilter(getModel().getDB(), s, true));
57
+ for (FilterDefinitionPanel f: inside.facetTable.values())
58
+ filter.and(f.defineFilter());
59
+ getModel().setFilter(inverted ? new NotFilter(filter) : filter, this);
60
+ }
61
+
62
+ public void clearFilters()
63
+ {
64
+
65
+ searchPanel.entry.setText("");
66
+ for (FilterDefinitionPanel d: inside.facetTable.values())
67
+ d.clearFilter();
68
+ inverted=false;
69
+ searchPanel.invert.setSelected(false);
70
+ for (Field f:getModel().getDB().getFields())
71
+ {
72
+ inside.setFacet(f, false);
73
+ }
74
+ makeFilter();
75
+ }
76
+
77
+ @Override
78
+ public void note(TFEvent e) {
79
+ if (e.affectsSchema())
80
+ {
81
+ inside.clearFacets();
82
+ searchPanel.entry.setText("");
83
+ }
84
+ }
85
+
86
+ public void setFacet(Field field, boolean on)
87
+ {
88
+ inside.setFacet(field, on);
89
+ makeFilter();
90
+ }
91
+
92
+ class FacetSubpanel extends JPanel
93
+ {
94
+
95
+ ArrayList<Field> facets=new ArrayList<Field>();
96
+ HashMap<Field, FilterDefinitionPanel> facetTable=new HashMap<Field, FilterDefinitionPanel>();
97
+
98
+ FacetSubpanel()
99
+ {
100
+ setLayout(null);
101
+ setBackground(Color.white);
102
+ }
103
+
104
+ FilterDefinitionPanel makePanel(Field field)
105
+ {
106
+ if (field.getType()==Double.class)
107
+ {
108
+ final FilterNumberPanel num=new FilterNumberPanel(field, new Runnable() {
109
+ @Override
110
+ public void run() {
111
+ makeFilter();
112
+ }}, FilterControlPanel.this);
113
+ num.setData(DBUtils.getValues(getModel().getDB(), field));
114
+ return num;
115
+ }
116
+
117
+ if (field.getType()==RoughTime.class)
118
+ {
119
+ final FilterDatePanel date=new FilterDatePanel(field, new Runnable() {
120
+ @Override
121
+ public void run() {
122
+ makeFilter();
123
+ }}, FilterControlPanel.this);
124
+ date.setData(DBUtils.getValues(getModel().getDB(), field));
125
+ return date;
126
+ }
127
+
128
+ final FilterCategoryPanel p= new FilterCategoryPanel(field, FilterControlPanel.this);
129
+ p.dataList.addListSelectionListener(new ListSelectionListener() {
130
+ @Override
131
+ public void valueChanged(ListSelectionEvent e) {
132
+ makeFilter();
133
+ }
134
+ });
135
+ Bag<String> data=DBUtils.countValues(getModel().getDB().all(), field);
136
+ p.setData(data);
137
+ return p;
138
+ }
139
+
140
+ public void clearFacets()
141
+ {
142
+ removeAll();
143
+ facets.clear();
144
+ facetTable.clear();
145
+ revalidate();
146
+ repaint();
147
+ }
148
+
149
+ public void setFacet(Field field, boolean on)
150
+ {
151
+ FilterDefinitionPanel panel=facetTable.get(field);
152
+ if (on == (panel!=null))
153
+ return;
154
+ if (on)
155
+ {
156
+ panel=makePanel(field);
157
+ add(panel);
158
+ facets.add(field);
159
+ facetTable.put(field,panel);
160
+ }
161
+ else
162
+ {
163
+ remove(panel);
164
+ facets.remove(field);
165
+ facetTable.remove(field);
166
+ }
167
+
168
+ doFacetLayout();
169
+ if (menuToSyncWith!=null)
170
+ for (int i=0; i<menuToSyncWith.getItemCount(); i++)
171
+ {
172
+ JCheckBoxMenuItem item=(JCheckBoxMenuItem)menuToSyncWith.getItem(i);
173
+ if (item.getText().equals(field.getName()))
174
+ {
175
+ item.setSelected(on);
176
+ }
177
+ }
178
+ revalidate();
179
+ repaint();
180
+ }
181
+
182
+ public void setBounds(int x, int y, int w, int h)
183
+ {
184
+ super.setBounds(x,y,w,h);
185
+ doFacetLayout();
186
+ }
187
+
188
+ void doFacetLayout()
189
+ {
190
+ int w=getSize().width, h=getSize().height;
191
+ int goodSize=0;
192
+ for (Field f: facets)
193
+ {
194
+ FilterDefinitionPanel p=facetTable.get(f);
195
+ goodSize+=p.getPreferredSize().height;
196
+ }
197
+ int top=0;
198
+ for (Field f: facets)
199
+ {
200
+ FilterDefinitionPanel p=facetTable.get(f);
201
+ int pref=p.getPreferredSize().height;
202
+ int panelHeight=(goodSize<= h ? pref : (pref*h)/goodSize);
203
+ p.setBounds(0,top,w,panelHeight);
204
+ top+=panelHeight;
205
+ }
206
+ }
207
+ }
208
+}
timeflow/app/ui/filter/FilterDatePanel.java
....@@ -0,0 +1,173 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.db.filter.*;
5
+import timeflow.model.*;
6
+
7
+import java.awt.*;
8
+import java.awt.event.ActionEvent;
9
+import java.awt.event.ActionListener;
10
+import java.text.*;
11
+import java.util.Date;
12
+
13
+import javax.swing.*;
14
+
15
+// in theory it should be easy to refactor this to share code with
16
+// NumberFilterPanel.
17
+// but, i'm not sure how to do it in a way that doesn't make the code
18
+// seem too complicated.
19
+
20
+public class FilterDatePanel extends FilterDefinitionPanel
21
+{
22
+ BabyHistogram histogram;
23
+ Field field;
24
+ JTextField startEntry;
25
+ JTextField endEntry;
26
+ JCheckBox nullBox;
27
+ Runnable action;
28
+ SimpleDateFormat df=new SimpleDateFormat("MMM dd yyyy");
29
+
30
+ public FilterDatePanel(final Field field, final Runnable action, final FilterControlPanel parent)
31
+ {
32
+ this.field=field;
33
+ this.action=action;
34
+ setLayout(new BorderLayout());
35
+ setBorder(BorderFactory.createEmptyBorder(0,5,0,5));
36
+ setBackground(Color.white);
37
+ add(new FilterTitle(field, parent, false), BorderLayout.NORTH);
38
+
39
+ Runnable fullAction=new Runnable()
40
+ {
41
+ public void run()
42
+ {
43
+ startEntry.setText(format(histogram.getLow()));
44
+ endEntry.setText(format(histogram.getHigh()));
45
+ action.run();
46
+ }
47
+ };
48
+
49
+ histogram=new BabyHistogram(fullAction);
50
+
51
+ add(histogram, BorderLayout.CENTER);
52
+
53
+ JPanel bottomStuff=new JPanel();
54
+ bottomStuff.setLayout(new GridLayout(2,1));
55
+ add(bottomStuff, BorderLayout.SOUTH);
56
+
57
+ JPanel lowHighPanel=new JPanel();
58
+ bottomStuff.add(lowHighPanel);
59
+ lowHighPanel.setBackground(Color.white);
60
+ lowHighPanel.setLayout(new BorderLayout());
61
+ Font small=parent.getModel().getDisplay().small();
62
+
63
+ startEntry=new JTextField(7);
64
+ startEntry.addActionListener(new ActionListener() {
65
+ @Override
66
+ public void actionPerformed(ActionEvent e) {
67
+ setLowFromText();
68
+ action.run();
69
+ }});
70
+ lowHighPanel.add(startEntry, BorderLayout.WEST);
71
+ startEntry.setFont(small);
72
+
73
+ JLabel rangeLabel=new JLabel("to", JLabel.CENTER);
74
+ rangeLabel.setForeground(Color.gray);
75
+ rangeLabel.setFont(small);
76
+ lowHighPanel.add(rangeLabel, BorderLayout.CENTER);
77
+ endEntry=new JTextField(7);
78
+ lowHighPanel.add(endEntry, BorderLayout.EAST);
79
+ endEntry.addActionListener(new ActionListener() {
80
+ @Override
81
+ public void actionPerformed(ActionEvent e) {
82
+ setHighFromText();
83
+ action.run();
84
+ }});
85
+ endEntry.setFont(small);
86
+
87
+ nullBox=new JCheckBox("Include Missing Values");
88
+ nullBox.addActionListener(new ActionListener() {
89
+ @Override
90
+ public void actionPerformed(ActionEvent e) {
91
+ action.run();
92
+ }});
93
+ bottomStuff.add(nullBox);
94
+ bottomStuff.setBackground(Color.white);
95
+ nullBox.setBackground(Color.white);
96
+ nullBox.setForeground(Color.gray);
97
+ nullBox.setFont(small);
98
+
99
+ }
100
+
101
+ String format(double x)
102
+ {
103
+ Date date=new Date((long)x);
104
+ return df.format(date);
105
+ }
106
+
107
+ void setLowFromText()
108
+ {
109
+ try
110
+ {
111
+ long low=df.parse(startEntry.getText()).getTime();
112
+ long high=(long)histogram.getHigh();
113
+ if (low>high)
114
+ {
115
+ high=low;
116
+ endEntry.setText(startEntry.getText());
117
+ }
118
+ histogram.setTrueRange(low,high);
119
+
120
+ }
121
+ catch (Exception e)
122
+ {
123
+
124
+ }
125
+ }
126
+
127
+
128
+ void setHighFromText()
129
+ {
130
+ try
131
+ {
132
+ long high=df.parse(endEntry.getText()).getTime();
133
+ double low=(long)histogram.getLow();
134
+ if (low>high)
135
+ {
136
+ low=high;
137
+ startEntry.setText(endEntry.getText());
138
+ }
139
+ histogram.setTrueRange(low,high);
140
+
141
+ }
142
+ catch (Exception e)
143
+ {
144
+
145
+ }
146
+ }
147
+
148
+ public void setData(double[] data)
149
+ {
150
+ histogram.setData(data);
151
+ startEntry.setText(format(histogram.getLow()));
152
+ endEntry.setText(format(histogram.getHigh()));
153
+ repaint();
154
+ }
155
+
156
+ public Dimension getPreferredSize()
157
+ {
158
+ return new Dimension(200,160);
159
+ }
160
+
161
+ @Override
162
+ public ActFilter defineFilter() {
163
+ long low=(long)histogram.getLow();
164
+ long high=(long)histogram.getHigh();
165
+ boolean acceptNull=nullBox.isSelected();
166
+ return new TimeIntervalFilter(low, high, acceptNull, field);
167
+ }
168
+
169
+ @Override
170
+ public void clearFilter() {
171
+ histogram.setRelRange(0, 1);
172
+ }
173
+}
timeflow/app/ui/filter/FilterDefinitionPanel.java
....@@ -0,0 +1,10 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import timeflow.data.db.filter.ActFilter;
4
+
5
+import javax.swing.*;
6
+
7
+public abstract class FilterDefinitionPanel extends JPanel {
8
+ public abstract ActFilter defineFilter();
9
+ public abstract void clearFilter();
10
+}
timeflow/app/ui/filter/FilterNumberPanel.java
....@@ -0,0 +1,171 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.db.filter.*;
5
+import timeflow.model.*;
6
+
7
+import java.awt.*;
8
+import java.awt.event.ActionEvent;
9
+import java.awt.event.ActionListener;
10
+
11
+import javax.swing.*;
12
+
13
+import timeflow.util.*;
14
+
15
+public class FilterNumberPanel extends FilterDefinitionPanel
16
+{
17
+ BabyHistogram histogram;
18
+ Field field;
19
+ JTextField lowEntry;
20
+ JTextField highEntry;
21
+ JCheckBox nullBox;
22
+ Runnable action;
23
+
24
+ public FilterNumberPanel(final Field field, final Runnable action, final FilterControlPanel parent)
25
+ {
26
+ this.field=field;
27
+ this.action=action;
28
+ setLayout(new BorderLayout());
29
+ setBackground(Color.white);
30
+ setBorder(BorderFactory.createEmptyBorder(0,5,0,5));
31
+
32
+
33
+ setBackground(Color.white);
34
+ add(new FilterTitle(field, parent, false), BorderLayout.NORTH);
35
+
36
+ Runnable fullAction=new Runnable()
37
+ {
38
+ public void run()
39
+ {
40
+ lowEntry.setText(format(histogram.getLow()));
41
+ highEntry.setText(format(histogram.getHigh()));
42
+ action.run();
43
+ }
44
+ };
45
+
46
+ histogram=new BabyHistogram(fullAction);
47
+
48
+ add(histogram, BorderLayout.CENTER);
49
+
50
+ JPanel bottomStuff=new JPanel();
51
+ bottomStuff.setLayout(new GridLayout(2,1));
52
+ add(bottomStuff, BorderLayout.SOUTH);
53
+ bottomStuff.setBackground(Color.white);
54
+
55
+ JPanel lowHighPanel=new JPanel();
56
+ bottomStuff.add(lowHighPanel);
57
+ lowHighPanel.setBackground(Color.white);
58
+
59
+ lowHighPanel.setLayout(new BorderLayout());
60
+
61
+ Font small=parent.getModel().getDisplay().small();
62
+ lowEntry=new JTextField(7);
63
+ lowEntry.setFont(small);
64
+ lowEntry.addActionListener(new ActionListener() {
65
+ @Override
66
+ public void actionPerformed(ActionEvent e) {
67
+ setLowFromText();
68
+ action.run();
69
+ }});
70
+ lowHighPanel.add(lowEntry, BorderLayout.WEST);
71
+ JLabel rangeLabel=new JLabel("to", JLabel.CENTER);
72
+
73
+ rangeLabel.setFont(small);
74
+ rangeLabel.setForeground(Color.gray);
75
+ lowHighPanel.add(rangeLabel, BorderLayout.CENTER);
76
+ highEntry=new JTextField(7);
77
+ lowHighPanel.add(highEntry, BorderLayout.EAST);
78
+ highEntry.addActionListener(new ActionListener() {
79
+ @Override
80
+ public void actionPerformed(ActionEvent e) {
81
+ setHighFromText();
82
+ action.run();
83
+ }});
84
+ highEntry.setFont(small);
85
+
86
+ nullBox=new JCheckBox("Include Missing Values");
87
+ nullBox.addActionListener(new ActionListener() {
88
+ @Override
89
+ public void actionPerformed(ActionEvent e) {
90
+ action.run();
91
+ }});
92
+ bottomStuff.add(nullBox);
93
+ nullBox.setBackground(Color.white);
94
+ nullBox.setForeground(Color.gray);
95
+ nullBox.setFont(small);
96
+ }
97
+
98
+ String format(double x)
99
+ {
100
+ if (Math.abs(x)>10)
101
+ return Display.format(Math.round(x));
102
+ return Display.format(x);
103
+ }
104
+
105
+ void setLowFromText()
106
+ {
107
+ try
108
+ {
109
+ double low=Double.parseDouble(lowEntry.getText());
110
+ double high=histogram.getHigh();
111
+ if (low>high)
112
+ {
113
+ high=low;
114
+ highEntry.setText(lowEntry.getText());
115
+ }
116
+ histogram.setTrueRange(low,high);
117
+
118
+ }
119
+ catch (Exception e)
120
+ {
121
+
122
+ }
123
+ }
124
+
125
+
126
+ void setHighFromText()
127
+ {
128
+ try
129
+ {
130
+ double high=Double.parseDouble(highEntry.getText());
131
+ double low=histogram.getLow();
132
+ if (low>high)
133
+ {
134
+ low=high;
135
+ lowEntry.setText(highEntry.getText());
136
+ }
137
+ histogram.setTrueRange(low,high);
138
+
139
+ }
140
+ catch (Exception e)
141
+ {
142
+
143
+ }
144
+ }
145
+
146
+ public void setData(double[] data)
147
+ {
148
+ histogram.setData(data);
149
+ lowEntry.setText(Display.format(histogram.getLow()));
150
+ highEntry.setText(Display.format(histogram.getHigh()));
151
+ repaint();
152
+ }
153
+
154
+ public Dimension getPreferredSize()
155
+ {
156
+ return new Dimension(200,160);
157
+ }
158
+
159
+ @Override
160
+ public ActFilter defineFilter() {
161
+ double low=histogram.getLow();
162
+ double high=histogram.getHigh();
163
+ boolean acceptNull=nullBox.isSelected();
164
+ return new NumericRangeFilter(field, low, high, acceptNull);
165
+ }
166
+
167
+ @Override
168
+ public void clearFilter() {
169
+ histogram.setRelRange(0, 1);
170
+ }
171
+}
timeflow/app/ui/filter/FilterTitle.java
....@@ -0,0 +1,52 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import java.awt.BorderLayout;
4
+import java.awt.Color;
5
+import java.awt.event.MouseAdapter;
6
+import java.awt.event.MouseEvent;
7
+
8
+import javax.swing.*;
9
+
10
+import timeflow.app.ui.DottedLine;
11
+import timeflow.data.db.Field;
12
+import timeflow.model.ModelPanel;
13
+
14
+import timeflow.util.*;
15
+
16
+public class FilterTitle extends JPanel {
17
+ public FilterTitle(final Field field, final ModelPanel parent, boolean dots)
18
+ {
19
+ this(field.getName(), field, parent, dots);
20
+ }
21
+ public FilterTitle(String title, final Field field, final ModelPanel parent, boolean dots)
22
+ {
23
+ JPanel top=new JPanel();
24
+ top.setBackground(Color.white);
25
+ top.setLayout(new BorderLayout());
26
+ JLabel label=new JLabel(title);
27
+ JPanel pad=new Pad(30,30);
28
+ pad.setBackground(Color.white);
29
+ top.add(pad, BorderLayout.NORTH);
30
+ top.add(label, BorderLayout.CENTER);
31
+ label.setBackground(Color.white);
32
+
33
+ if (parent instanceof FilterControlPanel)
34
+ {
35
+ ImageIcon redX=new ImageIcon("images/red_circle.gif");
36
+ JLabel close=new JLabel(redX);
37
+ close.setBackground(Color.white);
38
+ top.add(close, BorderLayout.EAST);
39
+ close.addMouseListener(new MouseAdapter() {
40
+ @Override
41
+ public void mousePressed(MouseEvent e) {
42
+ ((FilterControlPanel)parent).setFacet(field, false);
43
+ }
44
+ });
45
+ }
46
+ setLayout(new BorderLayout());
47
+ add(top, BorderLayout.CENTER);
48
+
49
+ if (dots)
50
+ add(new DottedLine(), BorderLayout.SOUTH);
51
+ }
52
+}
timeflow/app/ui/filter/SearchPanel.java
....@@ -0,0 +1,52 @@
1
+package timeflow.app.ui.filter;
2
+
3
+import javax.swing.*;
4
+import java.awt.event.*;
5
+
6
+import timeflow.data.db.*;
7
+import timeflow.data.time.*;
8
+import timeflow.model.*;
9
+
10
+import java.awt.*;
11
+
12
+public class SearchPanel extends ModelPanel {
13
+
14
+ JTextField entry;
15
+ JCheckBox invert;
16
+
17
+ public SearchPanel(TFModel model, final FilterControlPanel f) {
18
+ super(model);
19
+ setBackground(Color.white);
20
+ setBorder(BorderFactory.createEmptyBorder(15, 5,0,0));
21
+ setLayout(new GridLayout(1,1));
22
+ JPanel top=new JPanel();
23
+ top.setLayout(new BorderLayout());
24
+ add(top);
25
+ top.setBackground(Color.white);
26
+ JLabel label=model.getDisplay().label("Search");
27
+ top.add(label, BorderLayout.WEST);
28
+ entry=new JTextField(8);
29
+ top.add(entry, BorderLayout.CENTER);
30
+ entry.addActionListener(new ActionListener() {
31
+ @Override
32
+ public void actionPerformed(ActionEvent e) {
33
+ f.makeFilter();
34
+ }});
35
+
36
+ invert=new JCheckBox("Invert", false);
37
+ top.add(invert, BorderLayout.EAST);
38
+ invert.addActionListener(new ActionListener() {
39
+ @Override
40
+ public void actionPerformed(ActionEvent e) {
41
+ f.setInverted(invert.isSelected());
42
+ }});
43
+ invert.setFont(f.getModel().getDisplay().small());
44
+ invert.setForeground(Color.gray);
45
+ invert.setBackground(Color.white);
46
+ }
47
+
48
+ @Override
49
+ public void note(TFEvent e) {
50
+ }
51
+
52
+}
timeflow/data/analysis/DBAnalysis.java
....@@ -0,0 +1,14 @@
1
+package timeflow.data.analysis;
2
+
3
+import timeflow.data.db.*;
4
+
5
+public interface DBAnalysis {
6
+
7
+ public enum InterestLevel {IGNORE, BORING, INTERESTING, VERY_INTERESTING};
8
+
9
+ public String getName();
10
+ public InterestLevel perform(ActList acts);
11
+ public String[] getResultDescription();
12
+
13
+
14
+}
timeflow/data/analysis/FieldAnalysis.java
....@@ -0,0 +1,13 @@
1
+package timeflow.data.analysis;
2
+
3
+import timeflow.data.db.*;
4
+
5
+public interface FieldAnalysis {
6
+
7
+ public String getName();
8
+ public boolean canHandleType(Class type);
9
+ public DBAnalysis.InterestLevel perform(ActList acts, Field field);
10
+ public String[] getResultDescription();
11
+
12
+
13
+}
timeflow/data/analysis/FrequencyAnalysis.java
....@@ -0,0 +1,76 @@
1
+package timeflow.data.analysis;
2
+
3
+import timeflow.util.*;
4
+import timeflow.data.time.*;
5
+import timeflow.data.analysis.DBAnalysis.InterestLevel;
6
+import timeflow.data.db.*;
7
+import java.util.*;
8
+
9
+public class FrequencyAnalysis implements FieldAnalysis {
10
+ String[] description;
11
+
12
+ @Override
13
+ public String getName() {
14
+ return "Frequency Of Values";
15
+ }
16
+
17
+ @Override
18
+ public String[] getResultDescription() {
19
+ return description;
20
+ }
21
+
22
+ @Override
23
+ public InterestLevel perform(ActList acts, Field field) {
24
+ Bag<Object> bag=new Bag<Object>();
25
+ if (field.getType()==String[].class)
26
+ {
27
+ for (Act a: acts)
28
+ {
29
+ String[] tags=a.getTextList(field);
30
+ if (tags!=null)
31
+ for (String tag:tags)
32
+ bag.add(tag);
33
+ }
34
+ }
35
+ else
36
+ for (Act a: acts)
37
+ bag.add(a.get(field));
38
+
39
+ int numItems=acts.size();
40
+ int numDistinctVals=bag.size();
41
+ int numNullVals=bag.num(null)+bag.num("");
42
+
43
+ if (numItems==numDistinctVals)
44
+ description=new String[] {"All values are defined and unique."};
45
+ else if (numItems==numDistinctVals+numNullVals-1)
46
+ description=new String[] {"All defined values are unique."};
47
+ else if (numDistinctVals==1)
48
+ description=new String[] {"This field is always equal to "+string(bag.list().get(0))};
49
+ else if (numDistinctVals<4)
50
+ {
51
+ List<Object> all=bag.list();
52
+ description=new String[] {"This field takes only "+all.size()+" values.",
53
+ "which are: "+all};
54
+ }
55
+ else
56
+ {
57
+ List<Object> all=bag.list();
58
+ description=new String[] {"There are "+numDistinctVals+" distinct values.",
59
+ "Most common: \""+string(all.get(0))+"\", occurring "+bag.num(all.get(0))+" times."};
60
+ }
61
+ return InterestLevel.INTERESTING;
62
+ }
63
+
64
+ private static String[] empty=new String[0];
65
+ static String string(Object o)
66
+ {
67
+ if (o==null || "".equals(o) || empty.equals(o))
68
+ return "[missing]";
69
+ return o.toString();
70
+ }
71
+
72
+ @Override
73
+ public boolean canHandleType(Class type) {
74
+ return type==Double.class || type==String.class || type==String[].class;
75
+ }
76
+}
timeflow/data/analysis/MissingValueAnalysis.java
....@@ -0,0 +1,44 @@
1
+package timeflow.data.analysis;
2
+
3
+import timeflow.data.analysis.DBAnalysis.*;
4
+import timeflow.data.db.*;
5
+import timeflow.data.db.filter.*;
6
+
7
+public class MissingValueAnalysis implements FieldAnalysis {
8
+
9
+ int numNull;
10
+ int percent;
11
+
12
+ @Override
13
+ public String getName() {
14
+ return "Missing/Blank Values";
15
+ }
16
+
17
+ @Override
18
+ public String[] getResultDescription() {
19
+ String s;
20
+ if (numNull==0)
21
+ s="No missing values";
22
+ else if (numNull==1)
23
+ s= "One missing value";
24
+ else
25
+ s=numNull+" missing values: "+percent+"%";
26
+ return new String[] {s};
27
+ }
28
+
29
+ @Override
30
+ public InterestLevel perform(ActList acts, Field field) {
31
+ numNull=DBUtils.count(acts, new MissingValueFilter(field));
32
+ percent=(int)Math.round(100*numNull/(double)acts.size());
33
+ if (numNull==0)
34
+ return InterestLevel.IGNORE;
35
+ if (numNull<5)
36
+ return InterestLevel.VERY_INTERESTING;
37
+ return InterestLevel.INTERESTING;
38
+ }
39
+
40
+ @Override
41
+ public boolean canHandleType(Class type) {
42
+ return true;
43
+ }
44
+}
timeflow/data/analysis/RangeDateAnalysis.java
....@@ -0,0 +1,62 @@
1
+package timeflow.data.analysis;
2
+
3
+import java.sql.Date;
4
+
5
+import timeflow.data.analysis.DBAnalysis.*;
6
+import timeflow.data.db.*;
7
+import timeflow.data.db.filter.*;
8
+import timeflow.data.time.RoughTime;
9
+
10
+public class RangeDateAnalysis implements FieldAnalysis {
11
+
12
+ String[] description;
13
+
14
+ @Override
15
+ public String getName() {
16
+ return "Date Range";
17
+ }
18
+
19
+ @Override
20
+ public String[] getResultDescription() {
21
+ return description;
22
+ }
23
+
24
+ @Override
25
+ public InterestLevel perform(ActList acts, Field field) {
26
+ long low=0;
27
+ long high=0;
28
+
29
+ boolean defined=false;
30
+ for (Act a: acts)
31
+ {
32
+ if (a.get(field)==null)
33
+ continue;
34
+ long x=a.getTime(field).getTime();
35
+ if (defined)
36
+ {
37
+ low=Math.min(low,x);
38
+ high=Math.max(high, x);
39
+ } else
40
+ {
41
+ defined=true;
42
+ low=x;
43
+ high=low;
44
+ }
45
+ }
46
+ if (defined)
47
+ description= new String[]
48
+ {
49
+ "Lowest value: "+new Date(low),
50
+ "Highest value: "+new Date(high),
51
+ };
52
+ else
53
+ description=new String[] {"No values defined."};
54
+
55
+ return InterestLevel.INTERESTING;
56
+ }
57
+
58
+ @Override
59
+ public boolean canHandleType(Class type) {
60
+ return type==RoughTime.class;
61
+ }
62
+}
timeflow/data/analysis/RangeNumberAnalysis.java
....@@ -0,0 +1,85 @@
1
+package timeflow.data.analysis;
2
+
3
+import timeflow.data.analysis.DBAnalysis.*;
4
+import timeflow.data.db.*;
5
+import timeflow.data.db.filter.*;
6
+import java.text.*;
7
+
8
+public class RangeNumberAnalysis implements FieldAnalysis {
9
+
10
+ String[] description;
11
+ static DecimalFormat intFormat=new DecimalFormat("###,###,###,###");
12
+ static DecimalFormat df=new DecimalFormat("###,###,###,###.##");
13
+
14
+ @Override
15
+ public String getName() {
16
+ return "Value Range";
17
+ }
18
+
19
+ @Override
20
+ public String[] getResultDescription() {
21
+ return description;
22
+ }
23
+
24
+ @Override
25
+ public InterestLevel perform(ActList acts, Field field) {
26
+ double low=0;
27
+ double high=0;
28
+ int numZero=0;
29
+ double sum=0;
30
+ int numDefined=0;
31
+
32
+ boolean defined=false;
33
+ for (Act a: acts)
34
+ {
35
+ if (a.get(field)==null)
36
+ continue;
37
+
38
+ double x=a.getValue(field);
39
+ numDefined++;
40
+ sum+=x;
41
+
42
+ if (x==0)
43
+ numZero++;
44
+ if (defined)
45
+ {
46
+ low=Math.min(low,x);
47
+ high=Math.max(high, x);
48
+ } else
49
+ {
50
+ defined=true;
51
+ low=x;
52
+ high=low;
53
+ }
54
+ }
55
+ if (defined)
56
+ description= new String[]
57
+ {
58
+ "Average: "+df.format((sum/numDefined)),
59
+ "Lowest: "+df.format(low),
60
+ "Highest: "+df.format(high),
61
+ "Number of zero values: "+df.format(numZero)
62
+ };
63
+ else
64
+ description=new String[] {"No values defined."};
65
+
66
+ return InterestLevel.INTERESTING;
67
+ }
68
+
69
+ static String format(double x)
70
+ {
71
+
72
+ if (Math.abs(x)>.1)
73
+ {
74
+ if (Math.round(x)-x<.01)
75
+ return intFormat.format(x);
76
+ return df.format(x);
77
+ }
78
+ return ""+x;
79
+ }
80
+
81
+ @Override
82
+ public boolean canHandleType(Class type) {
83
+ return type==Double.class;
84
+ }
85
+}
timeflow/data/db/Act.java
....@@ -0,0 +1,25 @@
1
+package timeflow.data.db;
2
+
3
+import timeflow.data.time.*;
4
+
5
+import java.net.URL;
6
+import java.util.*;
7
+
8
+public interface Act {
9
+
10
+ public ActDB getDB();
11
+
12
+ public Object get(Field field);
13
+ public double getValue(Field field);
14
+ public String getString(Field field);
15
+ public String[] getTextList(Field field);
16
+ public RoughTime getTime(Field field);
17
+ public URL getURL(Field field);
18
+
19
+ public void set(Field field, Object value);
20
+ public void setText(Field field, String text);
21
+ public void setTextList(Field field, String[] list);
22
+ public void setValue(Field field, double value);
23
+ public void setTime(Field field, RoughTime time);
24
+ public void setURL(Field field, URL url);
25
+}
timeflow/data/db/ActComparator.java
....@@ -0,0 +1,118 @@
1
+package timeflow.data.db;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.time.RoughTime;
6
+
7
+public abstract class ActComparator implements Comparator<Act> {
8
+
9
+ protected Field field;
10
+ protected boolean ascending=true;
11
+ protected String description;
12
+
13
+
14
+ private ActComparator(Field field, String description)
15
+ {
16
+ this.field=field;
17
+ this.description=description;
18
+ }
19
+
20
+ public String getDescription()
21
+ {
22
+ return description + (ascending ? "" : " (descending)");
23
+ }
24
+
25
+ public static ActComparator by(Field field)
26
+ {
27
+ Class type=field.getType();
28
+ if (type==Double.class)
29
+ return new NumberComparator(field);
30
+ if (type==String[].class)
31
+ return new ArrayComparator(field);
32
+ if (type==RoughTime.class)
33
+ return new TimeComparator(field);
34
+ return new StringComparator(field);
35
+ }
36
+
37
+ static class TimeComparator extends ActComparator
38
+ {
39
+
40
+ TimeComparator(Field field)
41
+ {
42
+ super(field, "by time");
43
+ }
44
+
45
+ @Override
46
+ public int compare(Act o1, Act o2) {
47
+ RoughTime a1=o1.getTime(field);
48
+ RoughTime a2=o2.getTime(field);
49
+ if (a1==a2)
50
+ return 0;
51
+ if (a1==null)
52
+ return ascending ? 1 : -1;
53
+ if (a2==null)
54
+ return ascending ? -1 : 1;
55
+ int n=a1.compareTo(a2);
56
+ return ascending ? n : -n;
57
+ }
58
+ }
59
+
60
+
61
+ static class ArrayComparator extends ActComparator
62
+ {
63
+
64
+ ArrayComparator(Field field)
65
+ {
66
+ super(field, "by length of "+field.getName());
67
+ }
68
+
69
+ @Override
70
+ public int compare(Act o1, Act o2) {
71
+ int n=length(o1.getTextList(field))-length(o2.getTextList(field));
72
+ return ascending ? n : -n;
73
+ }
74
+
75
+ static int length(String[] s)
76
+ {
77
+ return s==null ? 0 : s.length;
78
+ }
79
+ }
80
+
81
+ static class StringComparator extends ActComparator
82
+ {
83
+
84
+ StringComparator(Field field)
85
+ {
86
+ super(field, "by "+field.getName());
87
+ }
88
+
89
+ @Override
90
+ public int compare(Act o1, Act o2) {
91
+ int n=val(o1.getString(field)).toString().compareTo(val(o2.getString(field)).toString());
92
+ return ascending ? n : -n;
93
+ }
94
+
95
+ String val(String s)
96
+ {
97
+ return s==null ? "" : s;
98
+ }
99
+ }
100
+
101
+ static class NumberComparator extends ActComparator
102
+ {
103
+
104
+ NumberComparator(Field field)
105
+ {
106
+ super(field, "by "+field.getName());
107
+ }
108
+
109
+ @Override
110
+ public int compare(Act o1, Act o2) {
111
+ double x=o1.getValue(field)-o2.getValue(field);
112
+ int n=x>0 ? 1 : x<0 ? -1 : 0;
113
+ return ascending ? n : -n;
114
+ }
115
+
116
+
117
+ }
118
+}
timeflow/data/db/ActDB.java
....@@ -0,0 +1,30 @@
1
+package timeflow.data.db;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.db.filter.ActFilter;
6
+
7
+public interface ActDB extends Iterable<Act> {
8
+
9
+ public String getSource();
10
+ public String getDescription();
11
+ public void setSource(String source);
12
+ public void setDescription(String description);
13
+
14
+ public List<String> getFieldKeys();
15
+ public List<Field> getFields();
16
+ public List<Field> getFields(Class type);
17
+ public Field addField(String name, Class type);
18
+ public Field getField(String name);
19
+ public void deleteField(Field field);
20
+ public void setAlias(Field field, String name);
21
+ public void setNewFieldOrder(List<Field> newOrder);
22
+ public void renameField(Field field, String name);
23
+
24
+ public void delete(Act act);
25
+ public Act createAct();
26
+ public ActList select(ActFilter filter);
27
+ public ActList all();
28
+ public int size();
29
+ public Act get(int i);
30
+}
timeflow/data/db/ActList.java
....@@ -0,0 +1,23 @@
1
+package timeflow.data.db;
2
+
3
+import java.util.*;
4
+
5
+public class ActList extends ArrayList<Act> {
6
+
7
+ private ActDB db;
8
+
9
+ public ActList(ActDB db)
10
+ {
11
+ this.db=db;
12
+ }
13
+
14
+ public ActDB getDB()
15
+ {
16
+ return db;
17
+ }
18
+
19
+ public ActList copy()
20
+ {
21
+ return (ActList)clone();
22
+ }
23
+}
timeflow/data/db/ArrayDB.java
....@@ -0,0 +1,348 @@
1
+package timeflow.data.db;
2
+
3
+import java.net.URL;
4
+import java.util.*;
5
+
6
+import timeflow.data.db.filter.*;
7
+import timeflow.data.time.*;
8
+
9
+public class ArrayDB implements ActDB
10
+{
11
+
12
+ private Schema schema;
13
+ private List<Act> data = new ArrayList<Act>();
14
+ private Field[] fields;
15
+ private String source = "[unknown]";
16
+ private String description = "";
17
+
18
+ public String getDescription()
19
+ {
20
+ return description;
21
+ }
22
+
23
+ public void setDescription(String description)
24
+ {
25
+ this.description = description;
26
+ }
27
+
28
+ public String getSource()
29
+ {
30
+ return source;
31
+ }
32
+
33
+ public void setSource(String source)
34
+ {
35
+ this.source = source;
36
+ }
37
+
38
+ @Override
39
+ public void setAlias(Field field, String name)
40
+ {
41
+ schema.addAlias(field, name);
42
+ }
43
+
44
+ public ArrayDB(String[] fieldNames, Class[] types, String source)
45
+ {
46
+ this.schema = new Schema();
47
+ this.source = source;
48
+ int n = fieldNames.length;
49
+ fields = new Field[n];
50
+ for (int i = 0; i < n; i++)
51
+ {
52
+ fields[i] = schema.add(fieldNames[i], types[i]);
53
+ fields[i].index = i;
54
+ }
55
+ }
56
+
57
+ public Field[] getFieldArray()
58
+ {
59
+ return fields;
60
+ }
61
+
62
+ @Override
63
+ public Field addField(String name, Class type)
64
+ {
65
+
66
+ int n = fields.length;
67
+
68
+ // make new Field.
69
+ Field field = new Field(name, type);
70
+ field.index = n;
71
+
72
+ // make new array of fields.
73
+ Field[] moreFields = new Field[n + 1];
74
+ System.arraycopy(fields, 0, moreFields, 0, n);
75
+ moreFields[n] = field;
76
+ this.fields = moreFields;
77
+
78
+ // go through all the data items and expand their arrays, too.
79
+ for (Act d : data)
80
+ {
81
+ IndexedAct item = (IndexedAct) d;
82
+ Object[] old = item.data;
83
+ item.data = new Object[n + 1];
84
+ System.arraycopy(old, 0, item.data, 0, n);
85
+ }
86
+
87
+ //System.out.println("Field added: "+field);
88
+ schema.add(field);
89
+
90
+ return field;
91
+ }
92
+
93
+ public Field getField(String name)
94
+ {
95
+ return schema.getField(name);
96
+ }
97
+
98
+ @Override
99
+ public ActList all()
100
+ {
101
+ return select(null);
102
+ }
103
+
104
+ @Override
105
+ public Act createAct()
106
+ {
107
+ IndexedAct act = new IndexedAct(this, fields.length);
108
+ data.add(act);
109
+ return act;
110
+ }
111
+
112
+ @Override
113
+ public void delete(Act act)
114
+ {
115
+ data.remove(act);
116
+ }
117
+
118
+ @Override
119
+ public void deleteField(Field deadField)
120
+ {
121
+
122
+ System.out.println("Deleting: " + deadField);
123
+
124
+ schema.delete(deadField);
125
+ int n = fields.length;
126
+ int m = deadField.index;
127
+
128
+ // make new array of fields.
129
+ Field[] fewerFields = new Field[n - 1];
130
+ removeItem(fields, fewerFields, m);
131
+ fields = fewerFields;
132
+
133
+ // go through all the data items and contract their arrays, too.
134
+ for (Act d : data)
135
+ {
136
+ IndexedAct item = (IndexedAct) d;
137
+ Object[] old = item.data;
138
+ item.data = new Object[n - 1];
139
+ removeItem(old, item.data, m);
140
+ }
141
+
142
+ // change field indices
143
+ for (int i = 0; i < fields.length; i++)
144
+ {
145
+ System.out.println("fields[" + i + "]=" + fields[i]);
146
+ if (fields[i].index > deadField.index)
147
+ {
148
+ fields[i].index--;
149
+ }
150
+ }
151
+ }
152
+
153
+ private static void removeItem(Object[] a, Object[] b, int m)
154
+ {
155
+ int n = a.length;
156
+ if (m > 0)
157
+ {
158
+ System.arraycopy(a, 0, b, 0, m);
159
+ }
160
+ if (m < n - 1)
161
+ {
162
+ System.arraycopy(a, m + 1, b, m, n - m - 1);
163
+ }
164
+ }
165
+
166
+ @Override
167
+ public List<Field> getFields(Class type)
168
+ {
169
+ return schema.getFields(type);
170
+ }
171
+
172
+ @Override
173
+ public ActList select(ActFilter filter)
174
+ {
175
+ ActList set = new ActList(this);
176
+ for (Act a : data)
177
+ {
178
+ if (filter == null || filter.accept(a))
179
+ {
180
+ set.add(a);
181
+ }
182
+ }
183
+ return set;
184
+ }
185
+
186
+ @Override
187
+ public List<Field> getFields()
188
+ {
189
+ return schema.getFields();
190
+ }
191
+
192
+ @Override
193
+ public Act get(int i)
194
+ {
195
+ return data.get(i);
196
+ }
197
+
198
+ @Override
199
+ public int size()
200
+ {
201
+ return data.size();
202
+ }
203
+
204
+ @Override
205
+ public Iterator<Act> iterator()
206
+ {
207
+ return data.iterator();
208
+ }
209
+
210
+ @Override
211
+ public List<String> getFieldKeys()
212
+ {
213
+ return schema.getKeys();
214
+ }
215
+
216
+ @Override
217
+ public void setNewFieldOrder(List<Field> newOrder)
218
+ {
219
+ schema.setNewFieldOrder(newOrder);
220
+ }
221
+
222
+ class IndexedAct implements Act
223
+ {
224
+
225
+ Object[] data;
226
+ ActDB db;
227
+
228
+ IndexedAct(ActDB db, int numFields)
229
+ {
230
+ this.db = db;
231
+ data = new Object[numFields];
232
+ }
233
+
234
+ @Override
235
+ public String getString(Field field)
236
+ {
237
+
238
+ Object obj = data[field.index];
239
+
240
+ if (obj == null)
241
+ return null;
242
+
243
+ if (obj instanceof String[])
244
+ {
245
+ String[] strings = (String[]) obj;
246
+ String string = "";
247
+ for (String s : strings)
248
+ {
249
+ string += s + ", ";
250
+ }
251
+ return string;
252
+ } else
253
+ {
254
+ return obj.toString();
255
+ }
256
+ }
257
+
258
+ public void setText(Field field, String text)
259
+ {
260
+ data[field.index] = text;
261
+ }
262
+
263
+ @Override
264
+ public String[] getTextList(Field field)
265
+ {
266
+ Object obj = data[field.index];
267
+
268
+ if (obj == null)
269
+ return null;
270
+
271
+ if (obj instanceof String[])
272
+ {
273
+ return (String[]) obj;
274
+ } else
275
+ {
276
+ return new String[]
277
+ {
278
+ obj.toString()
279
+ };
280
+ }
281
+ }
282
+
283
+ public void setTextList(Field field, String[] list)
284
+ {
285
+ data[field.index] = list;
286
+ }
287
+
288
+ @Override
289
+ public double getValue(Field field)
290
+ {
291
+ Double d = (Double) data[field.index];
292
+ return d == null ? Double.NaN : d.doubleValue();
293
+ }
294
+
295
+ public void setValue(Field field, double value)
296
+ {
297
+ data[field.index] = value;
298
+ }
299
+
300
+ @Override
301
+ public Object get(Field field)
302
+ {
303
+ return data[field.index];
304
+ }
305
+
306
+ @Override
307
+ public ActDB getDB()
308
+ {
309
+ return db;
310
+ }
311
+
312
+ @Override
313
+ public void set(Field field, Object value)
314
+ {
315
+ data[field.index] = value;
316
+ }
317
+
318
+ @Override
319
+ public RoughTime getTime(Field field)
320
+ {
321
+ return (RoughTime) data[field.index];
322
+ }
323
+
324
+ @Override
325
+ public void setTime(Field field, RoughTime time)
326
+ {
327
+ data[field.index] = time;
328
+ }
329
+
330
+ @Override
331
+ public URL getURL(Field field)
332
+ {
333
+ return (URL) data[field.index];
334
+ }
335
+
336
+ @Override
337
+ public void setURL(Field field, URL url)
338
+ {
339
+ data[field.index] = url;
340
+ }
341
+ }
342
+
343
+ @Override
344
+ public void renameField(Field field, String name)
345
+ {
346
+ schema.renameField(field, name);
347
+ }
348
+}
timeflow/data/db/BasicAct.java
....@@ -0,0 +1,89 @@
1
+package timeflow.data.db;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+
6
+import java.net.URL;
7
+import java.util.*;
8
+
9
+public class BasicAct implements Act {
10
+
11
+ private HashMap data=new HashMap();
12
+ private ActDB db;
13
+
14
+ public BasicAct(ActDB db)
15
+ {
16
+ this.db=db;
17
+ }
18
+
19
+
20
+ @Override
21
+ public String getString(Field field) {
22
+ return (String)data.get(field.getName());
23
+ }
24
+
25
+ public void setText(Field field, String text)
26
+ {
27
+ data.put(field.getName(), text);
28
+ }
29
+
30
+ @Override
31
+ public String[] getTextList(Field field) {
32
+ return (String[])data.get(field.getName());
33
+ }
34
+
35
+ public void setTextList(Field field, String[] list){
36
+ data.put(field.getName(), list);
37
+ }
38
+
39
+ @Override
40
+ public double getValue(Field field) {
41
+ return (Double)data.get(field.getName());
42
+ }
43
+
44
+ public void setValue(Field field, double value)
45
+ {
46
+ data.put(field.getName(), value);
47
+ }
48
+
49
+ @Override
50
+ public Object get(Field field) {
51
+ return data.get(field.getName());
52
+ }
53
+
54
+ @Override
55
+ public ActDB getDB() {
56
+ return db;
57
+ }
58
+
59
+ @Override
60
+ public void set(Field field, Object value) {
61
+ data.put(field.getName(), value);
62
+ }
63
+
64
+
65
+ @Override
66
+ public RoughTime getTime(Field field) {
67
+ return (RoughTime)data.get(field.getName());
68
+ }
69
+
70
+
71
+ @Override
72
+ public void setTime(Field field, RoughTime time) {
73
+ data.put(field.getName(), time);
74
+
75
+ }
76
+
77
+
78
+ @Override
79
+ public URL getURL(Field field) {
80
+ return (URL)data.get(field.getName());
81
+ }
82
+
83
+
84
+ @Override
85
+ public void setURL(Field field, URL url) {
86
+ data.put(field.getName(), url);
87
+ }
88
+
89
+}
timeflow/data/db/BasicDB.java
....@@ -0,0 +1,129 @@
1
+package timeflow.data.db;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.db.*;
6
+import timeflow.data.db.filter.ActFilter;
7
+
8
+public class BasicDB implements ActDB {
9
+
10
+ private Schema schema;
11
+ private List<Act> data=new ArrayList<Act>();
12
+ private String source="[unknown]";
13
+ private String description="";
14
+ public String getDescription() {
15
+ return description;
16
+ }
17
+
18
+ public void setDescription(String description) {
19
+ this.description = description;
20
+ }
21
+
22
+ public String getSource() {
23
+ return source;
24
+ }
25
+
26
+ public void setSource(String source) {
27
+ this.source = source;
28
+ }
29
+
30
+ public BasicDB(String source)
31
+ {
32
+ this(new Schema(), source);
33
+ }
34
+
35
+ public BasicDB(Schema schema, String source)
36
+ {
37
+ this.schema=schema;
38
+ this.source=source;
39
+ }
40
+
41
+ @Override
42
+ public Field addField(String name, Class type) {
43
+ Field field=new Field(name, type);
44
+ schema.add(field);
45
+ return field;
46
+ }
47
+
48
+ public Field getField(String name)
49
+ {
50
+ return schema.getField(name);
51
+ }
52
+
53
+ @Override
54
+ public ActList all() {
55
+ return select(null);
56
+ }
57
+
58
+ @Override
59
+ public Act createAct() {
60
+ BasicAct act=new BasicAct(this);
61
+ data.add(act);
62
+ return act;
63
+ }
64
+
65
+ @Override
66
+ public void delete(Act act) {
67
+ data.remove(act);
68
+ }
69
+
70
+ @Override
71
+ public void deleteField(Field field) {
72
+ schema.delete(field);
73
+ }
74
+
75
+ @Override
76
+ public List<Field> getFields(Class type) {
77
+ return schema.getFields(type);
78
+ }
79
+
80
+ @Override
81
+ public ActList select(ActFilter filter) {
82
+ ActList set=new ActList(this);
83
+ for (Act a: data)
84
+ if (filter==null || filter.accept(a))
85
+ set.add(a);
86
+ return set;
87
+ }
88
+
89
+ @Override
90
+ public List<Field> getFields() {
91
+ return schema.getFields();
92
+ }
93
+
94
+ @Override
95
+ public Act get(int i) {
96
+ return data.get(i);
97
+ }
98
+
99
+ @Override
100
+ public int size() {
101
+ return data.size();
102
+ }
103
+
104
+ @Override
105
+ public Iterator<Act> iterator() {
106
+ return data.iterator();
107
+ }
108
+
109
+ @Override
110
+ public void setAlias(Field field, String name) {
111
+ schema.addAlias(field,name);
112
+ }
113
+
114
+ @Override
115
+ public List<String> getFieldKeys() {
116
+ return schema.getKeys();
117
+ }
118
+
119
+ @Override
120
+ public void setNewFieldOrder(List<Field> newOrder) {
121
+ schema.setNewFieldOrder(newOrder);
122
+ }
123
+
124
+ @Override
125
+ public void renameField(Field field, String name) {
126
+ schema.renameField(field, name);
127
+ }
128
+
129
+}
timeflow/data/db/DBUtils.java
....@@ -0,0 +1,234 @@
1
+package timeflow.data.db;
2
+
3
+import timeflow.data.db.filter.*;
4
+import timeflow.data.time.*;
5
+import timeflow.util.*;
6
+
7
+import java.util.*;
8
+
9
+public class DBUtils
10
+{
11
+
12
+ public static void dump(Act act)
13
+ {
14
+ List<Field> fields = act.getDB().getFields();
15
+ for (Field f : fields)
16
+ {
17
+ System.out.println(f.getName() + " = " + act.get(f));
18
+ }
19
+ }
20
+
21
+ public static Object get(Act act, String field)
22
+ {
23
+ return act.get(act.getDB().getField(field));
24
+ }
25
+
26
+ public static List<String> getFieldAliases(ActDB db)
27
+ {
28
+ ArrayList<String> list = new ArrayList<String>();
29
+ for (String s : db.getFieldKeys())
30
+ {
31
+ if (!db.getField(s).getName().equals(s))
32
+ {
33
+ list.add(s);
34
+ }
35
+ }
36
+ return list;
37
+ }
38
+
39
+ public static Interval range(ActList a, Field[] fields)
40
+ {
41
+ if (fields == null || fields.length == 0)
42
+ {
43
+ return new Interval(0, 0);
44
+ }
45
+ Interval t = null;
46
+ for (Act act : a)
47
+ {
48
+ for (Field f : fields)
49
+ {
50
+ RoughTime d = act.getTime(f);
51
+ if (d != null && d.isDefined())
52
+ {
53
+ if (t == null)
54
+ {
55
+ t = new Interval(d.getTime(), d.getTime());
56
+ } else
57
+ {
58
+ t.include(d.getTime());
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return t != null ? t : new Interval(RoughTime.UNKNOWN, RoughTime.UNKNOWN);
64
+ }
65
+
66
+ public static Interval range(ActList a, String fieldName)
67
+ {
68
+
69
+ Field field = a.getDB().getField(fieldName);
70
+ if (field == null || a.size() == 0)
71
+ {
72
+ return new Interval(0, 0);
73
+ }
74
+ Interval t = null;
75
+ for (Act act : a)
76
+ {
77
+ RoughTime d = act.getTime(field);
78
+ if (d != null && d.isDefined())
79
+ {
80
+ if (t == null)
81
+ {
82
+ t = new Interval(d.getTime(), d.getTime());
83
+ } else
84
+ {
85
+ t.include(d.getTime());
86
+ }
87
+ }
88
+ }
89
+ return t != null ? t : new Interval(RoughTime.UNKNOWN, RoughTime.UNKNOWN);
90
+ }
91
+
92
+ public static List<Field> categoryFields(ActDB db)
93
+ {
94
+ List<Field> list = new ArrayList<Field>();
95
+ list.addAll(db.getFields());
96
+// list.addAll(db.getFields(String.class));
97
+// list.addAll(db.getFields(String[].class));
98
+ return list;
99
+ }
100
+
101
+ public static int count(Iterable<Act> acts, Interval i, Field field)//String fieldName)
102
+ {
103
+ return count(acts, new TimeIntervalFilter(i, field));
104
+ }
105
+
106
+ public static int count(Iterable<Act> acts, ActFilter filter)
107
+ {
108
+ int num = 0;
109
+ for (Act a : acts)
110
+ {
111
+ if (filter.accept(a))
112
+ {
113
+ num++;
114
+ }
115
+ }
116
+ return num;
117
+ }
118
+
119
+ public static double[] minmax(Iterable<Act> acts, Field field)
120
+ {
121
+ double min = Double.NaN;
122
+ double max = min;
123
+ for (Act a : acts)
124
+ {
125
+ double x = a.getValue(field);
126
+ if (Double.isNaN(min))
127
+ {
128
+ min = x;
129
+ max = x;
130
+ } else if (!Double.isNaN(x))
131
+ {
132
+ min = Math.min(x, min);
133
+ max = Math.max(x, max);
134
+ }
135
+ }
136
+ return new double[]
137
+ {
138
+ min, max
139
+ };
140
+ }
141
+
142
+ public static double[] getValues(Iterable<Act> acts, Field field)
143
+ {
144
+ ArrayList<Double> list = new ArrayList<Double>();
145
+ if (field.getType() == Double.class)
146
+ {
147
+ for (Act a : acts)
148
+ {
149
+ list.add(a.getValue(field));
150
+ }
151
+ } else if (field.getType() == RoughTime.class)
152
+ {
153
+ for (Act a : acts)
154
+ {
155
+ RoughTime r = a.getTime(field);
156
+ if (r != null)
157
+ {
158
+ list.add(new Double(r.getTime()));
159
+ }
160
+ }
161
+ }
162
+ int n = list.size();
163
+ double[] x = new double[n];
164
+ for (int i = 0; i < n; i++)
165
+ {
166
+ x[i] = list.get(i);
167
+ }
168
+ return x;
169
+ }
170
+
171
+ public static Bag<String> countValues(Iterable<Act> acts, Field field)
172
+ {
173
+ Bag<String> bag = new Bag<String>();
174
+ if (field.getType() != String[].class)
175
+ {
176
+ for (Act a : acts)
177
+ {
178
+ bag.add(a.getString(field));
179
+ }
180
+ }
181
+ else // if (field.getType() == String[].class)
182
+ {
183
+ for (Act a : acts)
184
+ {
185
+ String[] s = a.getTextList(field);
186
+ if (s != null)
187
+ {
188
+ for (int i = 0; i < s.length; i++)
189
+ {
190
+ bag.add(s[i]);
191
+ }
192
+ }
193
+ }
194
+// } else
195
+// {
196
+// throw new IllegalArgumentException("Asked to count values for non-text field: " + field);
197
+ }
198
+ return bag;
199
+ }
200
+
201
+ public static void setRecSizesFromCurrent(ActDB db)
202
+ {
203
+ // for String fields.
204
+ for (Field f : db.getFields(String.class))
205
+ {
206
+ int max = 0;
207
+ for (Act a : db)
208
+ {
209
+ String s = a.getString(f);
210
+ if (s != null)
211
+ {
212
+ max = Math.max(s.length(), max);
213
+ }
214
+ }
215
+ f.setRecommendedSize(max);
216
+ }
217
+ }
218
+
219
+ public static Field ensureField(ActDB db, String name, Class type)
220
+ {
221
+ Field f = db.getField(name);
222
+ if (f == null)
223
+ {
224
+ return db.addField(name, type);
225
+ } else
226
+ {
227
+ if (f.getType() != type)
228
+ {
229
+ throw new IllegalArgumentException("Mismatched types: got " + type + ", expected " + f.getType());
230
+ }
231
+ }
232
+ return f;
233
+ }
234
+}
timeflow/data/db/Field.java
....@@ -0,0 +1,50 @@
1
+package timeflow.data.db;
2
+
3
+
4
+public class Field {
5
+ private String name;
6
+ private Class type;
7
+ int index;
8
+ private int recommendedSize=-1;
9
+
10
+ public Field(String name, Class type)
11
+ {
12
+ this.name=name;
13
+ this.type=type;
14
+ }
15
+
16
+ public Field(String name, Class type, int recommendedSize)
17
+ {
18
+ this.name=name;
19
+ this.type=type;
20
+ this.recommendedSize=recommendedSize;
21
+ }
22
+
23
+ public int getRecommendedSize() {
24
+ return recommendedSize;
25
+ }
26
+
27
+ public void setRecommendedSize(int recommendedSize) {
28
+ this.recommendedSize = recommendedSize;
29
+ }
30
+
31
+ void setName(String name)
32
+ {
33
+ this.name=name;
34
+ }
35
+
36
+ public String getName()
37
+ {
38
+ return name;
39
+ }
40
+
41
+ public Class getType()
42
+ {
43
+ return type;
44
+ }
45
+
46
+ public String toString()
47
+ {
48
+ return "[Field: name='"+name+"', type="+type+", index="+index+"]";
49
+ }
50
+}
timeflow/data/db/Schema.java
....@@ -0,0 +1,130 @@
1
+package timeflow.data.db;
2
+
3
+import java.util.*;
4
+
5
+// methods are public for testing purposes.
6
+public class Schema implements Iterable<Field>
7
+{
8
+
9
+ private Map<String, Field> schema = new HashMap<String, Field>();
10
+ private List<Field> fieldList = new ArrayList<Field>(); // so we preserve field order.
11
+
12
+ public Iterator<Field> iterator()
13
+ {
14
+ return fieldList.iterator();
15
+ }
16
+
17
+ public Field getField(String key)
18
+ {
19
+ return schema.get(key);
20
+ }
21
+
22
+ public List<String> getKeys()
23
+ {
24
+ return new ArrayList(schema.keySet());
25
+ }
26
+
27
+ public List<Field> getFields(Class type)
28
+ {
29
+ List<Field> a = new ArrayList<Field>();
30
+ for (Field s : fieldList)
31
+ {
32
+ if (type == null || s.getType() == type)
33
+ {
34
+ a.add(s);
35
+ }
36
+ }
37
+ return a;
38
+ }
39
+
40
+ public List<Field> getFields()
41
+ {
42
+ return getFields(null);
43
+ }
44
+
45
+ // not sure this actually works! removing things while iterating? to-do: test!
46
+ public void delete(Field field)
47
+ {
48
+ if (schema.get(field.getName()) == null)
49
+ {
50
+ throw new IllegalArgumentException("No field exists: " + field);
51
+ }
52
+
53
+ Set<String> keys = new HashSet<String>(schema.keySet());
54
+ for (String s : keys)
55
+ {
56
+ Field f = schema.get(s);
57
+ if (f == field)
58
+ {
59
+ schema.remove(s);
60
+ }
61
+ }
62
+
63
+ fieldList.remove(field);
64
+ }
65
+
66
+ public void addAlias(Field field, String name)
67
+ {
68
+ if (field == null)
69
+ {
70
+ schema.remove(name);
71
+ return;
72
+ }
73
+ if (!schema.values().contains(field))
74
+ {
75
+ throw new IllegalArgumentException("Field does not exist in schema: " + field);
76
+ }
77
+ schema.put(name, field);
78
+ }
79
+
80
+ public Field add(String name, Class type)
81
+ {
82
+ return add(new Field(name, type));
83
+ }
84
+
85
+ public Field add(Field field)
86
+ {
87
+ if (schema.get(field.getName()) != null)
88
+ {
89
+ throw new IllegalArgumentException("Schema already has field named '" + field.getName()
90
+ + "', type=" + field.getType());
91
+ }
92
+ schema.put(field.getName(), field);
93
+ fieldList.add(field);
94
+ return field;
95
+ }
96
+
97
+ public void setNewFieldOrder(List<Field> newOrder)
98
+ {
99
+ // first, we go through and check that this really is a new ordering!
100
+ if (newOrder.size() != fieldList.size())
101
+ {
102
+ throw new IllegalArgumentException("Field lists have different sizes");
103
+ }
104
+ for (Field f : newOrder)
105
+ {
106
+ if (!fieldList.contains(f))
107
+ {
108
+ throw new IllegalArgumentException("New field list has unexpected field: " + f);
109
+ }
110
+ }
111
+ fieldList = newOrder;
112
+ }
113
+
114
+ public void print()
115
+ {
116
+ System.out.println(schema);
117
+ }
118
+
119
+ public void renameField(Field field, String name)
120
+ {
121
+ Field old = schema.get(name);
122
+ if (old != null && old != field)
123
+ {
124
+ throw new IllegalArgumentException("Can't rename a field to a name that already exists: " + name);
125
+ }
126
+ schema.remove(field);
127
+ field.setName(name);
128
+ schema.put(name, field);
129
+ }
130
+}
timeflow/data/db/filter/ActFilter.java
....@@ -0,0 +1,14 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.Act;
4
+
5
+public abstract class ActFilter {
6
+ public abstract boolean accept(Act act);
7
+
8
+ // in earlier versions we've wanted the UI to count the number of filters applied.
9
+ // because of the hierarchical way filters are defined, we need this method.
10
+ public int countFilters()
11
+ {
12
+ return 1;
13
+ }
14
+}
timeflow/data/db/filter/AndFilter.java
....@@ -0,0 +1,50 @@
1
+package timeflow.data.db.filter;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.db.Act;
6
+
7
+public class AndFilter extends ActFilter {
8
+ private List<ActFilter> filters;
9
+
10
+ public AndFilter()
11
+ {
12
+ }
13
+
14
+ public AndFilter(ActFilter a, ActFilter b)
15
+ {
16
+ filters=new ArrayList<ActFilter>();
17
+ and(a);
18
+ and(b);
19
+ }
20
+
21
+ public void and(ActFilter a)
22
+ {
23
+ if (a==null)
24
+ return;
25
+ if (filters==null)
26
+ filters=new ArrayList<ActFilter>();
27
+ filters.add(a);
28
+ }
29
+
30
+ @Override
31
+ public boolean accept(Act act) {
32
+ if (filters!=null)
33
+ for (ActFilter f: filters)
34
+ if (!f.accept(act))
35
+ return false;
36
+ return true;
37
+ }
38
+
39
+ public int countFilters()
40
+ {
41
+ int sum=0;
42
+ if (filters!=null)
43
+ for (ActFilter f: filters)
44
+ if (f!=null)
45
+ sum+=f.countFilters();
46
+ return sum;
47
+ }
48
+
49
+
50
+}
timeflow/data/db/filter/ConstFilter.java
....@@ -0,0 +1,24 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.Act;
4
+
5
+public class ConstFilter extends ActFilter {
6
+
7
+ boolean result;
8
+
9
+ public ConstFilter(boolean result)
10
+ {
11
+ this.result=result;
12
+ }
13
+
14
+ @Override
15
+ public boolean accept(Act act) {
16
+ return result;
17
+ }
18
+
19
+ public int countFilters()
20
+ {
21
+ return result ? 0 : 1;
22
+ }
23
+
24
+}
timeflow/data/db/filter/FieldValueFilter.java
....@@ -0,0 +1,37 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.*;
4
+
5
+public class FieldValueFilter extends ActFilter implements ValueFilter {
6
+
7
+ private Field field;
8
+ private Object value;
9
+
10
+ public FieldValueFilter(Field field, Object value)
11
+ {
12
+ this.field=field;
13
+ this.value=value;
14
+ }
15
+
16
+ public boolean ok(Object o)
17
+ {
18
+ if (o==null)
19
+ return value==null;
20
+ if (o.equals(value))
21
+ return true;
22
+ if (o instanceof Object[])
23
+ {
24
+ Object[] s=(Object[] )o;
25
+ for (int i=0; i<s.length; i++)
26
+ if (s[i].equals(value))
27
+ return true;
28
+ }
29
+ return false;
30
+ }
31
+
32
+ @Override
33
+ public boolean accept(Act act) {
34
+ return ok(act.get(field));
35
+ }
36
+
37
+}
timeflow/data/db/filter/FieldValueSetFilter.java
....@@ -0,0 +1,40 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.*;
4
+
5
+import java.util.*;
6
+
7
+public class FieldValueSetFilter extends ActFilter implements ValueFilter {
8
+
9
+ private Field field;
10
+ private Set valueSet;
11
+
12
+ public FieldValueSetFilter(Field field)
13
+ {
14
+ this.field=field;
15
+ valueSet=new HashSet();
16
+ }
17
+
18
+ public void addValue(Object value)
19
+ {
20
+ valueSet.add(value);
21
+ }
22
+
23
+ @Override
24
+ public boolean ok(Object o) {
25
+ if (o instanceof Object[])
26
+ {
27
+ Object[] s=(Object[] )o;
28
+ for (int i=0; i<s.length; i++)
29
+ if (valueSet.contains(s[i]))
30
+ return true;
31
+ }
32
+ return valueSet.contains(o);
33
+ }
34
+
35
+ @Override
36
+ public boolean accept(Act act) {
37
+ return ok(act.get(field));
38
+ }
39
+
40
+}
timeflow/data/db/filter/MissingValueFilter.java
....@@ -0,0 +1,27 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.Act;
4
+import timeflow.data.db.Field;
5
+
6
+public class MissingValueFilter extends ActFilter {
7
+ private Field field;
8
+ private boolean text, array, number;
9
+
10
+ public MissingValueFilter(Field field)
11
+ {
12
+ this.field=field;
13
+ text=field.getType()==String.class;
14
+ array=field.getType()==String[].class;
15
+ number=field.getType()==Double.class;
16
+ }
17
+
18
+ @Override
19
+ public boolean accept(Act act) {
20
+ Object o=act.get(field);
21
+ return o==null ||
22
+ number && Double.isNaN(((Double)o).doubleValue()) ||
23
+ text && "".equals(o) ||
24
+ array && ((String[])o).length==0;
25
+ }
26
+
27
+}
timeflow/data/db/filter/NotFilter.java
....@@ -0,0 +1,24 @@
1
+package timeflow.data.db.filter;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.db.Act;
6
+
7
+public class NotFilter extends ActFilter {
8
+ private ActFilter f;
9
+
10
+ public NotFilter(ActFilter f)
11
+ {
12
+ this.f=f;
13
+ }
14
+
15
+ @Override
16
+ public boolean accept(Act act) {
17
+ return f!=null && !f.accept(act);
18
+ }
19
+
20
+ public int countFilters()
21
+ {
22
+ return 1+f.countFilters();
23
+ }
24
+}
timeflow/data/db/filter/NumericRangeFilter.java
....@@ -0,0 +1,28 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+
6
+public class NumericRangeFilter extends ActFilter {
7
+
8
+ double low, high;
9
+ Field field;
10
+ boolean acceptNull;
11
+
12
+ public NumericRangeFilter(Field field, double low, double high, boolean acceptNull)
13
+ {
14
+ this.low=low;
15
+ this.high=high;
16
+ this.field=field;
17
+ this.acceptNull=acceptNull;
18
+ }
19
+
20
+ @Override
21
+ public boolean accept(Act act) {
22
+ if (field==null)
23
+ return false;
24
+ double x=act.getValue(field);
25
+ return Double.isNaN(x) && acceptNull || x>=low && x<=high;
26
+ }
27
+
28
+}
timeflow/data/db/filter/OrFilter.java
....@@ -0,0 +1,38 @@
1
+package timeflow.data.db.filter;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.db.Act;
6
+
7
+public class OrFilter extends ActFilter {
8
+ private List<ActFilter> filters=new ArrayList<ActFilter>();
9
+
10
+ public OrFilter(ActFilter a, ActFilter b)
11
+ {
12
+ or(a);
13
+ or(b);
14
+ }
15
+
16
+ public void or(ActFilter a)
17
+ {
18
+ filters.add(a);
19
+ }
20
+
21
+ @Override
22
+ public boolean accept(Act act) {
23
+ for (ActFilter f: filters)
24
+ if (f.accept(act))
25
+ return true;
26
+ return false;
27
+ }
28
+ public int countFilters()
29
+ {
30
+ int sum=0;
31
+ if (filters!=null)
32
+ for (ActFilter f: filters)
33
+ if (f!=null)
34
+ sum+=f.countFilters();
35
+ return sum;
36
+ }
37
+
38
+}
timeflow/data/db/filter/StringMatchFilter.java
....@@ -0,0 +1,69 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+
6
+import java.util.regex.*;
7
+
8
+public class StringMatchFilter extends ActFilter {
9
+
10
+ private Field[] textFields;
11
+ private Field[] listFields;
12
+ private String query="";
13
+ private boolean isRegex=false;
14
+ private Pattern pattern;
15
+
16
+ public StringMatchFilter(ActDB db, boolean isRegex)
17
+ {
18
+ this(db,"", isRegex);
19
+ }
20
+
21
+ public StringMatchFilter(ActDB db, String query, boolean isRegex)
22
+ {
23
+ textFields=(Field[])db.getFields(String.class).toArray(new Field[0]);
24
+ listFields=(Field[])db.getFields(String[].class).toArray(new Field[0]);
25
+ this.isRegex=isRegex;
26
+ setQuery(query);
27
+ }
28
+
29
+ public String getQuery()
30
+ {
31
+ return query;
32
+ }
33
+
34
+ public void setQuery(String query)
35
+ {
36
+ this.query=query;
37
+ if (isRegex)
38
+ {
39
+ pattern=Pattern.compile(query, Pattern.CASE_INSENSITIVE+Pattern.MULTILINE+Pattern.DOTALL);
40
+ }
41
+ else
42
+ this.query=query.toLowerCase();
43
+ }
44
+
45
+ @Override
46
+ public boolean accept(Act act) {
47
+ // check text fields
48
+ for (int i=0; i<textFields.length; i++)
49
+ {
50
+ String s=act.getString(textFields[i]);
51
+ if (s==null) continue;
52
+ if (isRegex ? pattern.matcher(s).find() : s.toLowerCase().contains(query))
53
+ return true;
54
+ }
55
+ // check list fields
56
+ for (int j=0; j<listFields.length; j++)
57
+ {
58
+ String[] m=act.getTextList(listFields[j]);
59
+ if (m!=null)
60
+ for (int i=0; i<m.length; i++)
61
+ {
62
+ String s=m[i];
63
+ if (isRegex ? pattern.matcher(s).find() : s.toLowerCase().contains(query))
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+}
timeflow/data/db/filter/TimeIntervalFilter.java
....@@ -0,0 +1,35 @@
1
+package timeflow.data.db.filter;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+
6
+public class TimeIntervalFilter extends ActFilter {
7
+
8
+ Interval interval;
9
+ Field timeField;
10
+ boolean acceptNull;
11
+
12
+ public TimeIntervalFilter(long start, long end, boolean acceptNull, Field timeField)
13
+ {
14
+ this.interval=new Interval(start, end);
15
+ this.acceptNull=acceptNull;
16
+ this.timeField=timeField;
17
+ }
18
+
19
+ public TimeIntervalFilter(Interval interval, Field timeField)
20
+ {
21
+ this.interval=interval;
22
+ this.timeField=timeField;
23
+ }
24
+
25
+ @Override
26
+ public boolean accept(Act act) {
27
+ if (timeField==null)
28
+ return false;
29
+ RoughTime t=act.getTime(timeField);
30
+ if (t==null)
31
+ return acceptNull;
32
+ return interval.contains(t.getTime());
33
+ }
34
+
35
+}
timeflow/data/db/filter/ValueFilter.java
....@@ -0,0 +1,5 @@
1
+package timeflow.data.db.filter;
2
+
3
+public interface ValueFilter {
4
+ public boolean ok(Object o);
5
+}
timeflow/data/time/Interval.java
....@@ -0,0 +1,114 @@
1
+package timeflow.data.time;
2
+
3
+import java.util.*;
4
+
5
+public class Interval
6
+{
7
+
8
+ public long start;
9
+ public long end;
10
+
11
+ public Interval(long start, long end)
12
+ {
13
+ this.start = start;
14
+ this.end = end;
15
+ }
16
+
17
+ public Interval copy()
18
+ {
19
+ return new Interval(start, end);
20
+ }
21
+
22
+ public boolean contains(long x)
23
+ {
24
+ return x >= start && x <= end;
25
+ }
26
+
27
+ public boolean intersects(Interval x)
28
+ {
29
+ return intersects(x.start, x.end);
30
+ }
31
+
32
+ public boolean intersects(long start1, long end1)
33
+ {
34
+ return start1 <= end && end1 >= start;
35
+ }
36
+
37
+ public Interval subinterval(double startFraction, double endFraction)
38
+ {
39
+ return new Interval((long) (start + startFraction * length()),
40
+ (long) (start + endFraction * length()));
41
+ }
42
+
43
+ public void setTo(long start, long end)
44
+ {
45
+ this.start = start;
46
+ this.end = end;
47
+ }
48
+
49
+ public void setTo(Interval t)
50
+ {
51
+ start = t.start;
52
+ end = t.end;
53
+ }
54
+
55
+ public void include(long time)
56
+ {
57
+ start = Math.min(start, time);
58
+ end = Math.max(end, time);
59
+ }
60
+
61
+ public void include(Interval t)
62
+ {
63
+ include(t.start);
64
+ include(t.end);
65
+ }
66
+
67
+ public void expand(long amount)
68
+ {
69
+ start -= amount;
70
+ end += amount;
71
+ }
72
+
73
+ public void add(long amount)
74
+ {
75
+ start += amount;
76
+ end += amount;
77
+ }
78
+
79
+ public long length()
80
+ {
81
+ return end - start;
82
+ }
83
+
84
+ public void translateTo(long newStart)
85
+ {
86
+ add(newStart - start);
87
+ }
88
+
89
+ public Interval intersection(Interval i)
90
+ {
91
+ start = Math.max(i.start, start);
92
+ end = Math.min(i.end, end);
93
+ return this;
94
+ }
95
+
96
+ public void clampInside(Interval container)
97
+ {
98
+ if (length() > container.length())
99
+ {
100
+ throw new IllegalArgumentException("Containing interval too small: " + container + " < " + this);
101
+ }
102
+ if (start >= container.start && end <= container.end)
103
+ {
104
+ return;
105
+ }
106
+ add(Math.max(0, container.start - start));
107
+ add(Math.min(0, container.end - end));
108
+ }
109
+
110
+ public String toString()
111
+ {
112
+ return "[Interval: From " + new Date(start) + " to " + new Date(end) + "]";
113
+ }
114
+}
timeflow/data/time/RoughTime.java
....@@ -0,0 +1,128 @@
1
+package timeflow.data.time;
2
+
3
+import java.util.Calendar;
4
+import java.util.Date;
5
+
6
+public class RoughTime implements Comparable {
7
+
8
+ public static final long UNKNOWN=Long.MIN_VALUE;
9
+ private TimeUnit units;
10
+ private long time;
11
+
12
+ public RoughTime(TimeUnit units)
13
+ {
14
+ time=UNKNOWN;
15
+ this.units=units;
16
+ }
17
+
18
+ public RoughTime(long time, TimeUnit units)
19
+ {
20
+ this.time=time;
21
+ this.units=units;
22
+ }
23
+
24
+ public boolean isDefined()
25
+ {
26
+ return time!=UNKNOWN;
27
+ }
28
+
29
+ public long getTime()
30
+ {
31
+ return time;
32
+ }
33
+
34
+ public void setTime(long time)
35
+ {
36
+ this.time=time;
37
+ }
38
+
39
+ public Date toDate()
40
+ {
41
+ return new Date(time);
42
+ }
43
+
44
+ public boolean after(RoughTime t)
45
+ {
46
+ return t.time<time;
47
+ }
48
+
49
+ public boolean before(RoughTime t)
50
+ {
51
+ return t.time>time;
52
+ }
53
+
54
+ public RoughTime plus(int numUnits)
55
+ {
56
+ return plus(units, numUnits);
57
+ }
58
+
59
+ public RoughTime plus(TimeUnit unit, int times)
60
+ {
61
+ RoughTime r=copy();
62
+ unit.addTo(r,times);
63
+ return r;
64
+ }
65
+
66
+ public String toString()
67
+ {
68
+ if (isKnown())
69
+ return new Date(time).toString();
70
+ return "unknown";
71
+ }
72
+
73
+ public boolean isKnown()
74
+ {
75
+ return time!=UNKNOWN;
76
+ }
77
+
78
+ public boolean equals(Object o)
79
+ {
80
+ if (!(o instanceof RoughTime))
81
+ return false;
82
+ RoughTime t=(RoughTime)o;
83
+ return t.units==units && t.time==time;
84
+ }
85
+
86
+ public RoughTime copy()
87
+ {
88
+ RoughTime t=new RoughTime(time, units);
89
+ return t;
90
+ }
91
+
92
+ public void setUnits(TimeUnit units)
93
+ {
94
+ this.units=units;
95
+ }
96
+
97
+ public TimeUnit getUnits() {
98
+ return units;
99
+ }
100
+
101
+ public String format()
102
+ {
103
+ return units.formatFull(time);
104
+ }
105
+
106
+ public static int compare(RoughTime t1, RoughTime t2)
107
+ {
108
+ if (t1==t2)
109
+ return 0;
110
+ if (t1==null)
111
+ return -1;
112
+ if (t2==null)
113
+ return 1;
114
+ long dt= t1.time-t2.time;
115
+ if (dt==0)
116
+ return 0;
117
+ if (dt>0)
118
+ return 1;
119
+ return -1;
120
+ }
121
+
122
+ @Override
123
+ public int compareTo(Object o) {
124
+ return compare(this, (RoughTime)o);
125
+ }
126
+
127
+
128
+}
timeflow/data/time/TimeUnit.java
....@@ -0,0 +1,243 @@
1
+package timeflow.data.time;
2
+
3
+import java.util.*;
4
+import java.text.*;
5
+
6
+public class TimeUnit {
7
+
8
+ public static final TimeUnit YEAR=new TimeUnit("Years", Calendar.YEAR, 365*24*60*60*1000L, "yyyy", "yyyy");
9
+ public static final TimeUnit MONTH=new TimeUnit("Months", Calendar.MONTH, 30*24*60*60*1000L, "MMM", "MMM yyyy");
10
+ public static final TimeUnit WEEK=new TimeUnit("Weeks", Calendar.WEEK_OF_YEAR, 7*24*60*60*1000L, "d", "MMM d yyyy");
11
+ public static final TimeUnit DAY=new TimeUnit("Days", Calendar.DAY_OF_MONTH, 24*60*60*1000L, "d", "MMM d yyyy");
12
+ public static final TimeUnit DAY_OF_WEEK=new TimeUnit("Days", Calendar.DAY_OF_WEEK, 24*60*60*1000L, "d", "MMM d yyyy");
13
+ public static final TimeUnit HOUR=new TimeUnit("Hours", Calendar.HOUR_OF_DAY, 60*60*1000L, "kk:mm", "MMM d yyyy kk:mm");
14
+ public static final TimeUnit MINUTE=new TimeUnit("Minutes", Calendar.MINUTE, 60*1000L, ":mm", "MMM d yyyy kk:mm");
15
+ public static final TimeUnit SECOND=new TimeUnit("Seconds", Calendar.SECOND, 1000L, ":ss", "MMM d yyyy kk:mm:ss");
16
+ public static final TimeUnit DECADE=multipleYears(10);
17
+ public static final TimeUnit CENTURY=multipleYears(100);
18
+
19
+ private static final double DAY_SIZE=24*60*60*1000L;
20
+
21
+ private int quantity;
22
+ private long roughSize;
23
+ private SimpleDateFormat format, fullFormat;
24
+ private String name;
25
+ private int calendarCode;
26
+
27
+ private TimeUnit()
28
+ {
29
+ }
30
+
31
+ private TimeUnit(String name, int calendarCode, long roughSize, String formatPattern, String fullFormatPattern)
32
+ {
33
+ this.name=name;
34
+ this.calendarCode=calendarCode;
35
+ this.roughSize=roughSize;
36
+ format=new SimpleDateFormat(formatPattern);
37
+ fullFormat=new SimpleDateFormat(fullFormatPattern);
38
+ quantity=1;
39
+ }
40
+
41
+ public String toString()
42
+ {
43
+ return "[TimeUnit: "+name+"]";
44
+ }
45
+
46
+ public static TimeUnit multipleYears(int numYears)
47
+ {
48
+ TimeUnit t=new TimeUnit();
49
+ t.name=numYears+" Years";
50
+ t.calendarCode=Calendar.YEAR;
51
+ t.roughSize=YEAR.roughSize*numYears;
52
+ t.format=YEAR.format;
53
+ t.fullFormat=YEAR.fullFormat;
54
+ t.quantity=numYears;
55
+ return t;
56
+ }
57
+
58
+ public static TimeUnit multipleWeeks(int num)
59
+ {
60
+ TimeUnit t=new TimeUnit();
61
+ t.name=num+" Weeks";
62
+ t.calendarCode=Calendar.WEEK_OF_YEAR;
63
+ t.roughSize=WEEK.roughSize*num;
64
+ t.format=WEEK.format;
65
+ t.fullFormat=WEEK.fullFormat;
66
+ t.quantity=num;
67
+ return t;
68
+ }
69
+
70
+ public TimeUnit times(int quantity)
71
+ {
72
+ TimeUnit t=new TimeUnit();
73
+ t.name=quantity+" "+this.name;
74
+ t.calendarCode=this.calendarCode;
75
+ t.roughSize=this.roughSize*quantity;
76
+ t.format=this.format;
77
+ t.fullFormat=this.fullFormat;
78
+ t.quantity=quantity;
79
+ return t;
80
+
81
+ }
82
+
83
+
84
+ public int numUnitsIn(TimeUnit u)
85
+ {
86
+ return (int)Math.round(u.getRoughSize()/(double)getRoughSize());
87
+ }
88
+
89
+ public boolean isDayOrLess()
90
+ {
91
+ return roughSize <= 24*60*60*1000L;
92
+ }
93
+
94
+ public RoughTime roundDown(long timestamp)
95
+ {
96
+ return round(timestamp, false);
97
+ }
98
+
99
+ public RoughTime roundUp(long timestamp)
100
+ {
101
+ return round(timestamp, true);
102
+ }
103
+
104
+ private static final int[] calendarUnits={Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR};
105
+ public RoughTime round(long timestamp, boolean up)
106
+ {
107
+ Calendar c=TimeUtils.cal(timestamp);
108
+
109
+ if (calendarCode==Calendar.WEEK_OF_YEAR )
110
+ {
111
+ c.set(Calendar.DAY_OF_WEEK, c.getMinimum(Calendar.DAY_OF_WEEK));
112
+ }
113
+ else
114
+ {
115
+
116
+ // set to minimum all fields of finer granularity.
117
+ int roundingCode=calendarCode;
118
+ if (calendarCode==Calendar.WEEK_OF_YEAR || calendarCode==Calendar.DAY_OF_WEEK)
119
+ roundingCode=Calendar.DAY_OF_MONTH;
120
+ for (int i=0; i<calendarUnits.length; i++)
121
+ {
122
+ if (calendarUnits[i]==roundingCode)
123
+ break;
124
+ if (i==calendarUnits.length-1)
125
+ throw new IllegalArgumentException("Unsupported Calendar Unit: "+calendarCode);
126
+ c.set(calendarUnits[i], c.getMinimum(calendarUnits[i]));
127
+ }
128
+ if (quantity>1)
129
+ {
130
+ c.set(calendarCode, quantity*(c.get(calendarCode)/quantity));
131
+ }
132
+ }
133
+
134
+ // if rounding up, then add a unit at current granularity.
135
+ if (up)
136
+ c.add(calendarCode, quantity);
137
+
138
+ return new RoughTime(c.getTimeInMillis(), this);
139
+ }
140
+
141
+ public int get(long timestamp)
142
+ {
143
+ Calendar c= TimeUtils.cal(timestamp);
144
+ int n=c.get(calendarCode);
145
+ return quantity==1 ? n : n%quantity;
146
+ }
147
+
148
+ public void addTo(RoughTime r)
149
+ {
150
+ addTo(r,1);
151
+ }
152
+
153
+ public void addTo(RoughTime r, int times)
154
+ {
155
+ Calendar c=TimeUtils.cal(r.getTime());
156
+ c.add(calendarCode, quantity*times);
157
+ r.setTime(c.getTimeInMillis());
158
+ }
159
+
160
+ // Finding the difference between two dates, in a given unit of time,
161
+ // is much subtler than you'd think! And annoyingly, the Calendar class does not do
162
+ // this for you, even though it actually "knows" how to do so since it
163
+ // can add fields.
164
+ //
165
+ // The most vexing problem is dealing with daylight savings time,
166
+ // which means that one day a year has 23 hours and one day has 25 hours.
167
+ // We also have to handle the fact that months and years aren't constant lengths.
168
+ //
169
+ // Rather than write all this ourselves, in this code we
170
+ // use the Calendar class to do the heavy lifting.
171
+ public long difference(long x, long y)
172
+ {
173
+ // If this is not one of the hard cases,
174
+ // just divide the timespan by the length of time unit.
175
+ // Note that we're not worrying about hours and daylight savings time.
176
+ if (calendarCode!=Calendar.YEAR && calendarCode!=Calendar.MONTH &&
177
+ calendarCode!=Calendar.DAY_OF_MONTH && calendarCode!=Calendar.DAY_OF_WEEK &&
178
+ calendarCode!=Calendar.WEEK_OF_YEAR)
179
+ {
180
+ return (x-y)/roughSize;
181
+ }
182
+
183
+ Calendar c1=TimeUtils.cal(x), c2=TimeUtils.cal(y);
184
+ int diff=0;
185
+ switch (calendarCode)
186
+ {
187
+ case Calendar.YEAR:
188
+ return (c1.get(Calendar.YEAR)-c2.get(Calendar.YEAR))/quantity;
189
+
190
+ case Calendar.MONTH:
191
+ diff= 12*(c1.get(Calendar.YEAR)-c2.get(Calendar.YEAR))+
192
+ c1.get(Calendar.MONTH)-c2.get(Calendar.MONTH);
193
+ return diff/quantity;
194
+
195
+ case Calendar.DAY_OF_MONTH:
196
+ case Calendar.DAY_OF_WEEK:
197
+ case Calendar.DAY_OF_YEAR:
198
+ case Calendar.WEEK_OF_MONTH:
199
+ case Calendar.WEEK_OF_YEAR:
200
+ // This is ugly, but believe me, it beats the alternative methods :-)
201
+ // We use the Calendar class's knowledge of daylight savings time.
202
+ // and also the fact that if we calculate this naively, then we aren't going
203
+ // to be off by more than one in either direction.
204
+ int naive=(int)Math.round((x-y)/(double)roughSize);
205
+ c2.add(calendarCode, naive*quantity);
206
+ if (c1.get(calendarCode)==c2.get(calendarCode))
207
+ return naive/quantity;
208
+ c2.add(calendarCode, quantity);
209
+ if (c1.get(calendarCode)==c2.get(calendarCode))
210
+ return naive/quantity+1;
211
+ return naive/quantity-1;
212
+ }
213
+ throw new IllegalArgumentException("Unexpected calendar code: "+calendarCode);
214
+ }
215
+
216
+ public long approxNumInRange(long start, long end)
217
+ {
218
+ return 1+(end-start)/roughSize;
219
+ }
220
+
221
+ public long getRoughSize() {
222
+ return roughSize;
223
+ }
224
+
225
+ public String format(Date date)
226
+ {
227
+ return format.format(date);
228
+ }
229
+
230
+ public String formatFull(Date date)
231
+ {
232
+ return fullFormat.format(date);
233
+ }
234
+
235
+ public String formatFull(long timestamp)
236
+ {
237
+ return fullFormat.format(new Date(timestamp));
238
+ }
239
+
240
+ public String getName() {
241
+ return name;
242
+ }
243
+}
timeflow/data/time/TimeUtils.java
....@@ -0,0 +1,21 @@
1
+package timeflow.data.time;
2
+
3
+import java.util.*;
4
+
5
+public class TimeUtils {
6
+
7
+ public static Calendar cal(long time)
8
+ {
9
+ Calendar c=new GregorianCalendar();
10
+ c.setTimeInMillis(time);
11
+ return c;
12
+ }
13
+
14
+ public static Calendar cal(Date date)
15
+ {
16
+ Calendar c=new GregorianCalendar();
17
+ c.setTime(date);
18
+ return c;
19
+ }
20
+
21
+}
timeflow/format/field/DateTimeGuesser.java
....@@ -0,0 +1,115 @@
1
+package timeflow.format.field;
2
+
3
+import java.text.*;
4
+import java.util.*;
5
+
6
+import timeflow.data.time.*;
7
+
8
+public class DateTimeGuesser {
9
+
10
+ private DateTimeParser lastGoodFormat;
11
+
12
+ private static List<DateTimeParser> parsers=new ArrayList<DateTimeParser>();
13
+
14
+ /*
15
+
16
+ HANDY REFERENCE FOR SIMPLEDATEFORMAT:
17
+ (quoted from Java documentation)
18
+
19
+ Letter Date or Time Component Presentation Examples
20
+ G Era designator Text AD
21
+ y Year Year 1996; 96
22
+ M Month in year Month July; Jul; 07
23
+ w Week in year Number 27
24
+ W Week in month Number 2
25
+ D Day in year Number 189
26
+ d Day in month Number 10
27
+ F Day of week in month Number 2
28
+ E Day in week Text Tuesday; Tue
29
+ a Am/pm marker Text PM
30
+ H Hour in day (0-23) Number 0
31
+ k Hour in day (1-24) Number 24
32
+ K Hour in am/pm (0-11) Number 0
33
+ h Hour in am/pm (1-12) Number 12
34
+ m Minute in hour Number 30
35
+ s Second in minute Number 55
36
+ S Millisecond Number 978
37
+ z Time zone General time zone Pacific Standard Time; PST; GMT-08:00
38
+ Z Time zone RFC 822 time zone -0800
39
+
40
+ */
41
+
42
+
43
+ // the order of the list below matters--better not to put the year-only ones at the top,
44
+ // because then the guesser succeeds before it has a chance to try parsing days.
45
+ static
46
+ {
47
+ parsers.add(new DateTimeParser("yyyy-MM-ddzzzzzzzzzz", TimeUnit.DAY));
48
+ parsers.add(new DateTimeParser("MMM dd yyyy HH:mm", TimeUnit.SECOND));
49
+ parsers.add(new DateTimeParser("MMM/dd/yyyy HH:mm", TimeUnit.SECOND));
50
+ parsers.add(new DateTimeParser("MM/dd/yy HH:mm", TimeUnit.SECOND));
51
+ parsers.add(new DateTimeParser("MMM dd yyyy HH:mm:ss", TimeUnit.SECOND));
52
+ parsers.add(new DateTimeParser("MM/dd/yyyy HH:mm:ss", TimeUnit.SECOND));
53
+ parsers.add(new DateTimeParser("MMM dd yyyy HH:mm:ss zzzzzzzz", TimeUnit.SECOND));
54
+ parsers.add(new DateTimeParser("EEE MMM dd HH:mm:ss zzzzzzzz yyyy", TimeUnit.SECOND));
55
+ parsers.add(new DateTimeParser("EEE MMM dd HH:mm:ss zzzzzzzz yyyy", TimeUnit.SECOND));
56
+ parsers.add(new DateTimeParser("MM-dd-yyyy", TimeUnit.DAY));
57
+ parsers.add(new DateTimeParser("yyyy-MM-dd", TimeUnit.DAY));
58
+ parsers.add(new DateTimeParser("yyyyMMdd", TimeUnit.DAY));
59
+ parsers.add(new DateTimeParser("MM-dd-yy", TimeUnit.DAY));
60
+ parsers.add(new DateTimeParser("dd-MMM-yy", TimeUnit.DAY));
61
+ parsers.add(new DateTimeParser("MM-dd-yyyy", TimeUnit.DAY));
62
+ parsers.add(new DateTimeParser("MM/dd/yy", TimeUnit.DAY));
63
+ parsers.add(new DateTimeParser("MM/dd/yyyy", TimeUnit.DAY));
64
+ parsers.add(new DateTimeParser("dd MMM yyyy", TimeUnit.DAY));
65
+ parsers.add(new DateTimeParser("dd MMM, yyyy", TimeUnit.DAY));
66
+ parsers.add(new DateTimeParser("MMM dd yyyy", TimeUnit.DAY));
67
+ parsers.add(new DateTimeParser("MMM dd, yyyy", TimeUnit.DAY));
68
+ parsers.add(new DateTimeParser("EEE MMM dd zzzzzzzz yyyy", TimeUnit.DAY));
69
+ parsers.add(new DateTimeParser("EEE MMM dd yyyy", TimeUnit.DAY));
70
+ parsers.add(new DateTimeParser("MMM-yy", TimeUnit.MONTH));
71
+ parsers.add(new DateTimeParser("MMM yy", TimeUnit.MONTH));
72
+ parsers.add(new DateTimeParser("MMM/yy", TimeUnit.MONTH));
73
+ parsers.add(new DateTimeParser("yyyy", TimeUnit.YEAR));
74
+ parsers.add(new DateTimeParser("yyyy GG", TimeUnit.YEAR));
75
+ }
76
+
77
+ public DateTimeParser getLastGoodFormat()
78
+ {
79
+ return lastGoodFormat;
80
+ }
81
+
82
+ public RoughTime guess(String s)
83
+ {
84
+ // old code for trying the last good parser.
85
+ // we took this out because if the last good one was for a single year,
86
+ // but a new one is for years and days,
87
+ // all you get is the year.
88
+
89
+ //if (lastGoodFormat!=null)
90
+ //try { return lastGoodFormat.parse(s); }
91
+ //catch (ParseException e) {}
92
+ if (s==null || s.trim().length()==0)
93
+ return null;
94
+ for (DateTimeParser d: parsers)
95
+ {
96
+ try
97
+ {
98
+ RoughTime date= d.parse(s);
99
+ lastGoodFormat=d;
100
+ return date;
101
+ }
102
+ catch (ParseException e) {}
103
+ }
104
+ throw new IllegalArgumentException("Couldn't guess date: '"+s+"'");
105
+ }
106
+
107
+ public static void main(String[] args)
108
+ {
109
+ DateTimeGuesser g=new DateTimeGuesser();
110
+ System.out.println(g.guess("2009-03-04"));
111
+ System.out.println(g.guess("June 10, 2010"));
112
+ System.out.println(g.guess("2010"));
113
+ System.out.println(g.guess("3/17/10"));
114
+ }
115
+}
timeflow/format/field/DateTimeParser.java
....@@ -0,0 +1,39 @@
1
+package timeflow.format.field;
2
+
3
+import timeflow.data.time.*;
4
+
5
+import java.util.*;
6
+import java.text.*;
7
+
8
+
9
+// Guesses date format and returns result
10
+public class DateTimeParser {
11
+
12
+ private DateFormat format;
13
+ private TimeUnit units;
14
+ private String pattern;
15
+
16
+ public DateTimeParser(String pattern, TimeUnit units)
17
+ {
18
+ this.pattern=pattern;
19
+ format=new SimpleDateFormat(pattern);
20
+ this.units=units;
21
+ }
22
+
23
+ public RoughTime parse(String s) throws ParseException
24
+ {
25
+ RoughTime f= new RoughTime(format.parse(s).getTime(), units);
26
+ return f;
27
+ }
28
+
29
+ public TimeUnit getUnits()
30
+ {
31
+ return units;
32
+ }
33
+
34
+ public String toString()
35
+ {
36
+ return "DateParser: pattern="+pattern+", units="+units;
37
+ }
38
+
39
+}
timeflow/format/field/FieldFormat.java
....@@ -0,0 +1,64 @@
1
+package timeflow.format.field;
2
+
3
+import java.net.URL;
4
+
5
+import timeflow.data.time.*;
6
+
7
+public abstract class FieldFormat {
8
+ protected String lastInput;
9
+ protected Object lastValue;
10
+ protected boolean understood=true;
11
+
12
+ double value;
13
+
14
+ void add(double x)
15
+ {
16
+ value+=x;
17
+ }
18
+
19
+ void note(String s)
20
+ {
21
+ add(scoreFormatMatch(s));
22
+ }
23
+
24
+
25
+ protected abstract Object _parse(String s) throws Exception;
26
+ public abstract String format(Object o);
27
+ public abstract Class getType();
28
+ public abstract double scoreFormatMatch(String s);
29
+ public abstract String getHumanName();
30
+
31
+
32
+ public void setValue(Object o)
33
+ {
34
+ lastValue=o;
35
+ lastInput=o==null ? "" : format(o);
36
+ }
37
+
38
+ public Object parse(String s) throws Exception
39
+ {
40
+ lastInput=s;
41
+ lastValue=null;
42
+ understood=false;
43
+ lastValue=_parse(s);
44
+ understood=true;
45
+ return lastValue;
46
+ }
47
+
48
+ public Object getLastValue()
49
+ {
50
+ return lastValue;
51
+ }
52
+
53
+ public String feedback()
54
+ {
55
+ if (!understood)
56
+ return "Couldn't understand";
57
+ return lastValue==null ? "(missing)" : "Read: "+format(lastValue);
58
+ }
59
+
60
+ public boolean isUnderstood()
61
+ {
62
+ return understood;
63
+ }
64
+}
timeflow/format/field/FieldFormatCatalog.java
....@@ -0,0 +1,54 @@
1
+package timeflow.format.field;
2
+
3
+import timeflow.data.time.*;
4
+import timeflow.util.*;
5
+
6
+import java.net.URL;
7
+import java.util.*;
8
+
9
+public class FieldFormatCatalog {
10
+
11
+ private static Map<String, FieldFormat> formatTable=new HashMap<String, FieldFormat>();
12
+ private static Map<Class, FieldFormat> classTable=new HashMap<Class, FieldFormat>();
13
+
14
+ static
15
+ {
16
+ for (FieldFormat f: listFormats())
17
+ {
18
+ formatTable.put(f.getHumanName(), f);
19
+ classTable.put(f.getType(), f);
20
+ }
21
+ }
22
+
23
+ static FieldFormat[] listFormats()
24
+ {
25
+ return new FieldFormat[] {new FormatDateTime(), new FormatString(),
26
+ new FormatStringArray(), new FormatDouble(), new FormatURL()};
27
+ }
28
+
29
+ public static Iterable<String> classNames()
30
+ {
31
+ return formatTable.keySet();
32
+ }
33
+
34
+ public static String humanName(Class c){
35
+ return getFormat(c).getHumanName();
36
+ }
37
+
38
+
39
+ public static FieldFormat getFormat(Class c) {
40
+ FieldFormat f= classTable.get(c);
41
+ if (f==null)
42
+ System.out.println("Warning: no FieldFormat for "+c);
43
+ return f;
44
+ }
45
+
46
+
47
+ public static Class javaClass(String humanName)
48
+ {
49
+ Class c=formatTable.get(humanName).getType();
50
+ if (c==null)
51
+ System.out.println("Warning: no class for "+humanName);
52
+ return c;
53
+ }
54
+}
timeflow/format/field/FieldFormatGuesser.java
....@@ -0,0 +1,50 @@
1
+package timeflow.format.field;
2
+
3
+import timeflow.util.*;
4
+
5
+public class FieldFormatGuesser {
6
+
7
+ FieldFormat[] scores;
8
+
9
+ private FieldFormatGuesser()
10
+ {
11
+ scores=FieldFormatCatalog.listFormats();
12
+ }
13
+ public static Class[] analyze(String[][] data, int startRow, int numRows)
14
+ {
15
+ int n=data[0].length;
16
+ FieldFormatGuesser[] g=new FieldFormatGuesser[n];
17
+ for (int i=0; i<n; i++)
18
+ g[i]=new FieldFormatGuesser();
19
+ for (int i=startRow; i<startRow+numRows && i<data.length; i++)
20
+ {
21
+ for (int j=0; j<n; j++)
22
+ g[j].add(data[i][j]);
23
+ }
24
+ Class[] c=new Class[n];
25
+ for (int i=0; i<n; i++)
26
+ c[i]=g[i].best();
27
+ return c;
28
+ }
29
+
30
+ private void add(String s)
31
+ {
32
+ for (int i=0; i<scores.length; i++)
33
+ scores[i].note(s);
34
+ }
35
+
36
+ private Class best()
37
+ {
38
+ double max=scores[0].value;
39
+ Class best=scores[0].getType();
40
+ for (int i=1; i<scores.length; i++)
41
+ {
42
+ if (scores[i].value>max)
43
+ {
44
+ max=scores[i].value;
45
+ best=scores[i].getType();
46
+ }
47
+ }
48
+ return best;
49
+ }
50
+}
timeflow/format/field/FormatDateTime.java
....@@ -0,0 +1,79 @@
1
+/**
2
+ *
3
+ */
4
+package timeflow.format.field;
5
+
6
+import java.text.ParseException;
7
+import java.util.Calendar;
8
+
9
+import timeflow.data.time.RoughTime;
10
+import timeflow.data.time.TimeUnit;
11
+import timeflow.data.time.TimeUtils;
12
+
13
+public class FormatDateTime extends FieldFormat
14
+{
15
+ DateTimeGuesser dateGuesser=new DateTimeGuesser();
16
+
17
+ @Override
18
+ public String format(Object o) {
19
+ return ((RoughTime)o).format();
20
+ }
21
+
22
+ @Override
23
+ public Object _parse(String s) throws Exception {
24
+ if (s.length()==0)
25
+ return null;
26
+ Object o= readTime(s);
27
+ if (o==null)
28
+ throw new IllegalArgumentException();
29
+ return o;
30
+ }
31
+
32
+
33
+ public RoughTime readTime(Object o) throws ParseException
34
+ {
35
+ if (!(o instanceof String))
36
+ throw new IllegalArgumentException("Expected String, got: "+o);
37
+ return dateGuesser.guess((String)o);
38
+ }
39
+ DateTimeGuesser g=new DateTimeGuesser();
40
+
41
+ @Override
42
+ public double scoreFormatMatch(String s) {
43
+ if (s==null || s.length()==0)
44
+ return -.05;
45
+ try
46
+ {
47
+ RoughTime f=g.guess(s);
48
+ if (f==null)
49
+ return -1;
50
+ if (g.getLastGoodFormat().getUnits()==TimeUnit.YEAR)
51
+ {
52
+ int year=TimeUtils.cal(f.getTime()).get(Calendar.YEAR);
53
+ if (year>2100)
54
+ return -1;
55
+ if (year>1900 && year<2050)
56
+ return 1;
57
+ if (year>2050 || year<1600)
58
+ return .1;
59
+ return .5;
60
+ }
61
+ return 2;
62
+ }
63
+ catch (Exception e)
64
+ {
65
+ return -1;
66
+ }
67
+ }
68
+
69
+
70
+ @Override
71
+ public Class getType() {
72
+ return RoughTime.class;
73
+ }
74
+
75
+ @Override
76
+ public String getHumanName() {
77
+ return "Date/Time";
78
+ }
79
+}
timeflow/format/field/FormatDouble.java
....@@ -0,0 +1,103 @@
1
+/**
2
+ *
3
+ */
4
+package timeflow.format.field;
5
+
6
+public class FormatDouble extends FieldFormat
7
+{
8
+ @Override
9
+ public String format(Object o) {
10
+ return o.toString();
11
+ }
12
+
13
+ @Override
14
+ public Object _parse(String s) {
15
+ if (s==null || s.trim().length()==0)
16
+ return null;
17
+ return parseDouble(s);
18
+ }
19
+
20
+ public static double parseDouble(String s)
21
+ {
22
+ int n=s.length();
23
+ if (n>3)
24
+ {
25
+ if (s.charAt(0)=='(' && s.charAt(n-1)==')')
26
+ {
27
+ s='-'+s.substring(1,n-1);
28
+ n--;
29
+ }
30
+ }
31
+ // remove $,%, etc.
32
+ StringBuffer b=new StringBuffer();
33
+ for (int i=0; i<n; i++)
34
+ {
35
+ char c=s.charAt(i);
36
+ if (Character.isDigit(c) || c=='-' || c=='.')
37
+ b.append(c);
38
+ }
39
+
40
+ try
41
+ {
42
+ return Double.parseDouble(b.toString());
43
+ }
44
+ catch (RuntimeException e)
45
+ {
46
+ System.out.println("b="+b);
47
+ throw e;
48
+ }
49
+ }
50
+
51
+
52
+ @Override
53
+ public Class getType() {
54
+ return Double.class;
55
+ }
56
+ @Override
57
+ public double scoreFormatMatch(String s) {
58
+ s=s.trim();
59
+ int n=s.length();
60
+ if (n==5) // possible zip code
61
+ {
62
+ if (s.charAt(0)=='0')
63
+ return -3; // gotta be a zip code!
64
+ return 0;
65
+ }
66
+ if (n==9) // possible zip+4, but really, who knows...
67
+ return 0;
68
+
69
+ if (n==4) // possible date.
70
+ {
71
+ try
72
+ {
73
+ int x=Integer.parseInt(s);
74
+ if (x>1900 && x<2030)
75
+ return -1; // that's very likely a date.
76
+ if (x>1700 && x<2100)
77
+ return 0; // you don't know.
78
+
79
+ }
80
+ catch (Exception e) {} // evidently not a date :-)
81
+ }
82
+
83
+ if (n==0)
84
+ return -.1;
85
+ int ok=0;
86
+ int bad=0;
87
+ for (int i=0; i<n; i++)
88
+ {
89
+ char c=s.charAt(i);
90
+ if (Character.isDigit(c) || c=='.' || c==',' || c=='-' || c=='$' || c=='%')
91
+ ok++;
92
+ else
93
+ bad++;
94
+ }
95
+ return 4-5*bad;
96
+ }
97
+
98
+ @Override
99
+ public String getHumanName() {
100
+ return "Number";
101
+ }
102
+
103
+}
timeflow/format/field/FormatString.java
....@@ -0,0 +1,44 @@
1
+/**
2
+ *
3
+ */
4
+package timeflow.format.field;
5
+
6
+public class FormatString extends FieldFormat
7
+{
8
+ @Override
9
+ public String format(Object o) {
10
+ return o.toString();
11
+ }
12
+
13
+ @Override
14
+ public Object _parse(String s) {
15
+ return s;
16
+ }
17
+
18
+
19
+ public String feedback()
20
+ {
21
+ if (lastValue==null)
22
+ return "Couldn't understand";
23
+ if (((String)lastValue).length()==0)
24
+ return "Blank";
25
+ return "";
26
+ }
27
+
28
+ @Override
29
+ public Class getType() {
30
+ return String.class;
31
+ }
32
+
33
+ @Override
34
+ public double scoreFormatMatch(String s) {
35
+ return s!=null && s.length()>0 ? .1 : 0;
36
+ }
37
+
38
+ @Override
39
+ public String getHumanName() {
40
+ return "Text";
41
+ }
42
+
43
+
44
+}
timeflow/format/field/FormatStringArray.java
....@@ -0,0 +1,59 @@
1
+/**
2
+ *
3
+ */
4
+package timeflow.format.field;
5
+
6
+import timeflow.model.Display;
7
+
8
+public class FormatStringArray extends FieldFormat
9
+{
10
+ @Override
11
+ public String format(Object o) {
12
+ return Display.arrayToString((String[])o);
13
+ }
14
+
15
+ @Override
16
+ public Object _parse(String s) {
17
+ return parseList(s);
18
+ }
19
+
20
+ public static String[] parseList(String s)
21
+ {
22
+ String[] t= s.length()==0 ? new String[0] : s.split(",");
23
+ for (int i=0; i<t.length; i++)
24
+ t[i]=t[i].trim();
25
+ return t;
26
+ }
27
+
28
+ public String feedback()
29
+ {
30
+ if (lastValue==null)
31
+ return "Couldn't understand";
32
+ String[] s=(String[])lastValue;
33
+ if (s.length==0)
34
+ return "Empty list";
35
+ if (s.length==1)
36
+ return "One item";
37
+ return s.length+" items";
38
+ }
39
+
40
+ @Override
41
+ public Class getType() {
42
+ return String[].class;
43
+ }
44
+
45
+ @Override
46
+ public double scoreFormatMatch(String s) {
47
+ double commas=-1;
48
+ for (int i=s.length()-1; i>=0; i--)
49
+ if (s.charAt(i)==',')
50
+ commas++;
51
+ return commas/s.length();
52
+ }
53
+
54
+ @Override
55
+ public String getHumanName() {
56
+ return "List";
57
+ }
58
+
59
+}
timeflow/format/field/FormatURL.java
....@@ -0,0 +1,41 @@
1
+/**
2
+ *
3
+ */
4
+package timeflow.format.field;
5
+
6
+import java.net.URL;
7
+
8
+public class FormatURL extends FieldFormat
9
+{
10
+ @Override
11
+ public String format(Object o) {
12
+ return o.toString();
13
+ }
14
+
15
+ @Override
16
+ public Object _parse(String s) throws Exception {
17
+ if (s.length()==0)
18
+ return null;
19
+ return new URL(s);
20
+ }
21
+
22
+ @Override
23
+ public Class getType() {
24
+ return URL.class;
25
+ }
26
+
27
+ @Override
28
+ public double scoreFormatMatch(String s) {
29
+ if (s==null || s.length()==0)
30
+ return 0;
31
+ if (s.startsWith("http") || s.startsWith("file://"))
32
+ return 5;
33
+ return -1;
34
+ }
35
+
36
+ @Override
37
+ public String getHumanName() {
38
+ return "URL";
39
+ }
40
+
41
+}
timeflow/format/file/DelimitedFormat.java
....@@ -0,0 +1,217 @@
1
+package timeflow.format.file;
2
+
3
+import java.io.BufferedOutputStream;
4
+import java.io.File;
5
+import java.io.FileOutputStream;
6
+import java.io.IOException;
7
+import java.io.PrintStream;
8
+import java.io.PrintWriter;
9
+import java.util.ArrayList;
10
+import java.util.Arrays;
11
+import java.util.Iterator;
12
+import java.util.List;
13
+
14
+import timeflow.data.db.Act;
15
+import timeflow.data.db.ActDB;
16
+import timeflow.data.db.DBUtils;
17
+import timeflow.data.db.Field;
18
+import timeflow.data.time.RoughTime;
19
+import timeflow.model.Display;
20
+
21
+import timeflow.util.*;
22
+
23
+public class DelimitedFormat {
24
+
25
+ char delimiter;
26
+ DelimitedText delimitedText;
27
+
28
+ public DelimitedFormat(char delimiter)
29
+ {
30
+ this.delimiter=delimiter;
31
+ delimitedText=new DelimitedText(delimiter);
32
+ }
33
+
34
+ public static String[][] readArrayGuessDelim(String fileName, PrintStream messages) throws Exception
35
+ {
36
+ //messages.println("DelimitedFormat: reading "+fileName);
37
+ String text=IO.read(fileName);
38
+ return readArrayFromString(text, messages);
39
+ }
40
+
41
+ public static String[][] readArrayFromString(String text, PrintStream messages) throws Exception
42
+ {
43
+ //messages.println("DelimitedFormat: reading string, length="+text.length());
44
+ int n=Math.min(text.length(), 1000);
45
+ String beginning=text.substring(0,n);
46
+ char c=count(beginning, '\t')>count(beginning, ',') ? '\t' : ',';
47
+ return new DelimitedFormat(c).readTokensFromString(text, messages);
48
+ }
49
+
50
+ private static String[] removeBlankLines(String[] lines)
51
+ {
52
+ List<String> good=new ArrayList<String>();
53
+ for (int i=0; i<lines.length; i++)
54
+ {
55
+ if (!(lines[i]==null || lines[i].trim().length()==0))
56
+ good.add(lines[i]);
57
+ }
58
+ return (String[])good.toArray(new String[0]);
59
+ }
60
+
61
+ private static int count(String s, char c)
62
+ {
63
+ int n=0;
64
+ for (int i=0; i<s.length(); i++)
65
+ {
66
+ if (s.charAt(i)==c)
67
+ n++;
68
+ }
69
+ return n;
70
+ }
71
+
72
+ public String[][] readTokensFromString(String text, PrintStream messages) throws Exception
73
+ {
74
+
75
+ ArrayList<String[]> resultList=new ArrayList<String[]>();
76
+ Iterator<String[]> lines=delimitedText.read(text).iterator();
77
+ int numCols=-1;
78
+ while(lines.hasNext())
79
+ {
80
+ String[] r=lines.next();
81
+ int ri=r.length;
82
+ if (numCols==-1)
83
+ numCols=r.length;
84
+ else
85
+ {
86
+ if (ri>numCols)
87
+ {
88
+ messages.println("Line too long: "+ri+" > "+numCols);
89
+ messages.println("line="+Display.arrayToString(r));
90
+ }
91
+ else if (ri<numCols)
92
+ {
93
+ String[] old=r;
94
+ r=new String[numCols];
95
+ System.arraycopy(old,0,r,0,ri);
96
+ for (int j=ri; j<numCols; j++)
97
+ r[j]="";
98
+ }
99
+ }
100
+ resultList.add(r);
101
+ }
102
+ //messages.println("# lines read: "+resultList.size());
103
+ return (String[][]) resultList.toArray(new String[0][]);
104
+ }
105
+
106
+ public void write(ActDB db, File file) throws IOException
107
+ {
108
+ write(db, db, file);
109
+ }
110
+
111
+ public void write(ActDB db, Iterable<Act> acts, File file) throws IOException
112
+ {
113
+ FileOutputStream fos=new FileOutputStream(file);
114
+ BufferedOutputStream b=new BufferedOutputStream(fos);
115
+ PrintStream out=new PrintStream(b);
116
+
117
+ // Write data!
118
+ writeDelimited(db, acts, out);
119
+
120
+ out.flush();
121
+ out.close();
122
+ b.close();
123
+ fos.close();
124
+ }
125
+
126
+
127
+ void writeDelimited(ActDB db, PrintStream out)
128
+ {
129
+ writeDelimited(db, db, out);
130
+ }
131
+
132
+ public void writeDelimited(ActDB db, Iterable<Act> acts, PrintWriter out)
133
+ {
134
+ // Write headers
135
+ List<String> names=new ArrayList<String>();
136
+ List<Field> fields=db.getFields();
137
+ for (Field f: fields)
138
+ names.add(f.getName());
139
+ print(names, out);
140
+
141
+ // Write data
142
+ for (Act a: acts)
143
+ {
144
+ List<String> data=new ArrayList<String>();
145
+ for (Field f: fields)
146
+ data.add(format(a.get(f)));
147
+ print(data, out);
148
+ }
149
+ }
150
+
151
+
152
+ public void writeDelimited(ActDB db, Iterable<Act> acts, PrintStream out)
153
+ {
154
+ // Write headers
155
+ List<String> names=new ArrayList<String>();
156
+ List<Field> fields=db.getFields();
157
+ for (Field f: fields)
158
+ names.add(f.getName());
159
+ print(names, out);
160
+
161
+ // Write data
162
+ for (Act a: acts)
163
+ {
164
+ List<String> data=new ArrayList<String>();
165
+ for (Field f: fields)
166
+ data.add(format(a.get(f)));
167
+ print(data, out);
168
+ }
169
+ }
170
+
171
+
172
+ static String format(Object o)
173
+ {
174
+ if (o==null)
175
+ return "";
176
+ if (o instanceof String)
177
+ return (String)o;
178
+ if (o instanceof RoughTime)
179
+ return ((RoughTime)o).format();
180
+ if (o instanceof Number)
181
+ return o.toString();
182
+ if (o instanceof String[])
183
+ {
184
+ return writeArray((String[])o);
185
+ }
186
+ return o.toString();
187
+ }
188
+
189
+ public static String writeArray(Object[] s)
190
+ {
191
+ if (s==null || s.length==0)
192
+ {
193
+ return "";
194
+ }
195
+ StringBuffer b=new StringBuffer();
196
+ for (int i=0; i<s.length; i++)
197
+ {
198
+ if (i>0)
199
+ b.append(",");
200
+ b.append(s[i]);
201
+
202
+ }
203
+ return b.toString();
204
+ }
205
+
206
+ void print(List<String> list, PrintStream out)
207
+ {
208
+ out.println(delimitedText.write((String[])list.toArray(new String[0])));
209
+ }
210
+
211
+ void print(List<String> list, PrintWriter out)
212
+ {
213
+ out.println(delimitedText.write((String[])list.toArray(new String[0])));
214
+ }
215
+
216
+}
217
+
timeflow/format/file/DelimitedText.java
....@@ -0,0 +1,214 @@
1
+package timeflow.format.file;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.util.*;
6
+
7
+import timeflow.model.Display;
8
+
9
+public class DelimitedText {
10
+ private char delimiter;
11
+
12
+ public DelimitedText(char delimiter)
13
+ {
14
+ if (delimiter=='"')
15
+ throw new IllegalArgumentException("Can't use quote as delimiter.");
16
+ this.delimiter=delimiter;
17
+ }
18
+
19
+ private static boolean isBreak(char c)
20
+ {
21
+ return c=='\n' || c=='\r';
22
+ }
23
+
24
+ public List<String[]> read(String text)
25
+ {
26
+ ArrayList<String[]> results=new ArrayList<String[]>();
27
+ int n=text.length();
28
+ StringBuffer currentToken=new StringBuffer();
29
+ ArrayList<String> currentList=new ArrayList<String>();
30
+
31
+ boolean quoted=false;
32
+ for (int i=0; i<n; i++)
33
+ {
34
+ char c=text.charAt(i);
35
+ if (quoted)
36
+ {
37
+ if (c=='"')
38
+ {
39
+ if (i==n-1) // end of file, ignore quote.
40
+ {
41
+ quoted=false;
42
+ continue;
43
+ }
44
+ char next=text.charAt(i+1);
45
+ if (next=='"') // a quoted quote.
46
+ {
47
+ currentToken.append('"');
48
+ i++;
49
+
50
+ // Alas, there is a weird special case here
51
+ // if the user has pasted from Excel.
52
+ // If a field starts with a quote, and ends with two quotes,
53
+ // it turns out to be ambiguous!
54
+ // Excel doesn't do any escaping on: "blah blah""
55
+ // But, it does escape: blah "\n
56
+ // turning it into: "blah blah""\n
57
+ // So if "blah blah"" occurs at the end of the line,
58
+ // you actually do not know which it is!
59
+ // In practice, our first bug report was for a literal of "blah blah""
60
+ // so that is what we will choose.
61
+
62
+ //System.out.println("next++: '"+text.charAt(i+1)+"'="+(int)text.charAt(i+1));
63
+ if (i<n-1 && isBreak(text.charAt(i+1)))
64
+ {
65
+ quoted=false;
66
+ }
67
+
68
+ continue;
69
+ }
70
+ if (isBreak(next)) // end of line
71
+ {
72
+ quoted=false;
73
+ currentList.add(currentToken.toString());
74
+ currentToken.setLength(0);
75
+ results.add((String[])currentList.toArray(new String[0]));
76
+ currentList=new ArrayList<String>();
77
+ i++;
78
+ if (i<n-1 && isBreak(text.charAt(i+1)))
79
+ i++;
80
+ continue;
81
+ }
82
+ if (next==delimiter)
83
+ {
84
+ quoted=false;
85
+ continue;
86
+ }
87
+ System.out.println("a bad quote from excel: next char="+(int)next);
88
+ quoted=false;
89
+ }
90
+ currentToken.append(c);
91
+ continue;
92
+ }
93
+
94
+ // ok, not quoted.
95
+ if (c==delimiter)
96
+ {
97
+ currentList.add(currentToken.toString());
98
+ currentToken.setLength(0);
99
+ quoted=false;
100
+ continue;
101
+ }
102
+
103
+ // not delimiter, not in the middle of a quote.
104
+ if (c=='"')
105
+ {
106
+ if (currentToken.length()==0) // we are at beginning of a token, so this is a quote.
107
+ {
108
+ quoted=true;
109
+ continue;
110
+ }
111
+ }
112
+
113
+ // is it a line feed? we're not in the middle of a quote, so this means a new line.
114
+ if (c=='\n' || c=='\r' || c=='\f')
115
+ {
116
+ currentList.add(currentToken.toString());
117
+ currentToken.setLength(0);
118
+ results.add((String[])currentList.toArray(new String[0]));
119
+ currentList=new ArrayList<String>();
120
+ if (i<n-1 && (text.charAt(i+1)=='\n' || text.charAt(i+1)=='\r'))
121
+ i++;
122
+ continue;
123
+ }
124
+
125
+ // by golly, just a normal character!
126
+ currentToken.append(c);
127
+ }
128
+
129
+ // did it just end in a blank line?
130
+
131
+ if (currentList.size()>0 || currentToken.toString().trim().length()>0)
132
+ {
133
+ currentList.add(currentToken.toString());
134
+ results.add((String[])currentList.toArray(new String[0]));
135
+ }
136
+ return results;
137
+ }
138
+
139
+ public String write(String s)
140
+ {
141
+ return write(new String[] {s});
142
+ }
143
+
144
+ public String write(String[] data)
145
+ {
146
+ StringBuffer b=new StringBuffer();
147
+ for (int i=0; i<data.length; i++)
148
+ {
149
+ // add a delimiter if necessary.
150
+ if (i>0)
151
+ b.append(delimiter);
152
+
153
+ // if null, just don't write anything.
154
+ if (data[i]==null)
155
+ continue;
156
+
157
+ // does it have weird characters in it?
158
+ boolean weird=false;
159
+ int n=data[i].length();
160
+ for (int j=0; j<n; j++)
161
+ {
162
+ char c=data[i].charAt(j);
163
+ if (c==delimiter || isBreak(c))
164
+ {
165
+ weird=true;
166
+ break;
167
+ }
168
+ }
169
+
170
+ if (weird)
171
+ {
172
+ b.append('"');
173
+ for (int j=0; j<n; j++)
174
+ {
175
+ char c=data[i].charAt(j);
176
+ if (c=='"')
177
+ b.append('"');
178
+ b.append(c);
179
+ }
180
+ b.append('"');
181
+ }
182
+ else
183
+ b.append(data[i]);
184
+ }
185
+ return b.toString();
186
+ }
187
+
188
+ public static String[] split(String s, char delimiter)
189
+ {
190
+ DelimitedText t= new DelimitedText(delimiter);
191
+ List<String[]> lines=t.read(s);
192
+ return lines.get(0);
193
+ }
194
+
195
+ public static void main(String[] args) throws Exception
196
+ {
197
+ String bad=IO.read("test/bad-all.txt");
198
+ String[][] s=DelimitedFormat.readArrayFromString(bad, System.out);
199
+ System.out.println("len="+s.length);
200
+
201
+ /*
202
+ //DelimitedText c=new DelimitedText(';');
203
+ //List<String[]> arrays=c.read(IO.read("test/bad.txt"));
204
+ //List<String[]> arrays=c.read("a;b;\"x;y\";c");
205
+ //List<String[]> arrays=c.read("a;\"a\n\rq\";b;\"x;y\";c");
206
+ //List<String[]> arrays=c.read("a;b;\"with a \"\"blah\";c\nd;e;f\ng;h;i");
207
+ //List<String[]> arrays=c.read("a,\"b\",\"c\r\nd\"\r\ne,f,g\nh,i,j");
208
+ for (String[] s:arrays)
209
+ {
210
+ System.out.println("["+Display.arrayToString(s)+"]");
211
+ }
212
+ */
213
+ }
214
+}
timeflow/format/file/Export.java
....@@ -0,0 +1,10 @@
1
+package timeflow.format.file;
2
+
3
+import timeflow.model.*;
4
+
5
+import java.io.BufferedWriter;
6
+
7
+public interface Export {
8
+ public String getName();
9
+ public void export(TFModel model, BufferedWriter out) throws Exception;
10
+}
timeflow/format/file/FileExtensionCatalog.java
....@@ -0,0 +1,28 @@
1
+package timeflow.format.file;
2
+
3
+// This is meant to be a repository for different
4
+// types of import functions, arranged by file extension.
5
+
6
+// We currently do not import anything but the standard file type.
7
+// There actually is some code that will import from JSON/XML SIMILE
8
+// timelines, but we have removed it from this release to simplify
9
+// both the application and because it would mean redistributing additional
10
+// third-party libraries.
11
+public class FileExtensionCatalog {
12
+
13
+ public static Import get(String fileName)
14
+ {
15
+ /*
16
+ // not in this release...
17
+ // but contact us if you'd like to see this.
18
+ // we took out the SIMILE import material as too "techie"
19
+ // for the first release!
20
+
21
+ if (fileName.endsWith("xml"))
22
+ return new SimileXMLFormat();
23
+ if (fileName.endsWith("json"))
24
+ return new SimileJSONFormat();
25
+ */
26
+ return new TimeflowFormat();
27
+ }
28
+}
timeflow/format/file/HtmlFormat.java
....@@ -0,0 +1,143 @@
1
+package timeflow.format.file;
2
+
3
+import java.awt.Color;
4
+import java.io.BufferedWriter;
5
+import java.net.URL;
6
+
7
+import timeflow.model.*;
8
+import timeflow.data.db.*;
9
+
10
+public class HtmlFormat implements Export
11
+{
12
+ TFModel model;
13
+ java.util.List<Field> fields;
14
+ Field title;
15
+
16
+ public HtmlFormat() {}
17
+
18
+ public HtmlFormat(TFModel model)
19
+ {
20
+ setModel(model);
21
+ }
22
+
23
+ public void setModel(TFModel model)
24
+ {
25
+ this.model=model;
26
+ fields=model.getDB().getFields();
27
+ title=model.getDB().getField(VirtualField.LABEL);
28
+ }
29
+
30
+ @Override
31
+ public void export(TFModel model, BufferedWriter out) throws Exception {
32
+ setModel(model);
33
+ out.write(makeHeader());
34
+ for (Act a: model.getDB())
35
+ out.write(makeItem(a));
36
+ out.write(makeFooter());
37
+ out.flush();
38
+ }
39
+
40
+ public void append(ActList acts, int start, int end, StringBuffer b)
41
+ {
42
+ for (int i=start; i<end; i++)
43
+ {
44
+ Act a=acts.get(i);
45
+ b.append(makeItem(a,i));
46
+ }
47
+ }
48
+
49
+ private String makeItem(Act act)
50
+ {
51
+ return makeItem(act, -1);
52
+ }
53
+
54
+ public String makeItem(Act act, int id)
55
+ {
56
+ StringBuffer page=new StringBuffer();
57
+
58
+
59
+ page.append("<tr><td valign=top align=left width=200><b>");
60
+ if (title!=null)
61
+ {
62
+ Field f=model.getColorField();
63
+ Color c=Color.black;
64
+ if (f!=null)
65
+ {
66
+ if (f.getType()==String.class)
67
+ c=model.getDisplay().makeColor(act.getString(f));
68
+ else
69
+ {
70
+ String[] tags=act.getTextList(f);
71
+ if (tags.length==0)
72
+ c=Color.gray;
73
+ else
74
+ c=model.getDisplay().makeColor(tags[0]);
75
+ }
76
+ }
77
+
78
+ page.append("<font size=+1 color="+htmlColor(c)+">"+act.getString(title)+"</font><br>");
79
+ }
80
+
81
+ Field startField=model.getDB().getField(VirtualField.START);
82
+
83
+ if (startField!=null)
84
+ {
85
+ page.append("<font color=#999999>"+model.getDisplay().format(
86
+ act.getTime(startField))+"</font>");
87
+ }
88
+ page.append("</b><br>");
89
+ if (id>=0)
90
+ page.append("<a href=\"e"+id+"\">EDIT</a>");
91
+ page.append("<br></td><td valign=top>");
92
+ for (Field f: fields)
93
+ {
94
+ page.append("<b><font color=#003399>"+f.getName()+"</font></b>&nbsp;&nbsp;");
95
+ Object val=act.get(f);
96
+ if (val instanceof URL)
97
+ {
98
+ page.append("<a href=\""+val+"\">"+val+"</a>");
99
+ }
100
+ else
101
+ page.append(model.getDisplay().toString(val));
102
+ page.append("<br>");
103
+
104
+ }
105
+ page.append("<br></td></tr>");
106
+
107
+ return page.toString();
108
+ }
109
+
110
+ public String makeHeader()
111
+ {
112
+ StringBuffer page=new StringBuffer();
113
+ page.append("<html><body><blockquote>");
114
+ page.append("<br>File: "+model.getDbFile()+"<br>");
115
+ page.append("Source: "+model.getDB().getSource()+"<br><br>");
116
+ page.append("<br><br>");
117
+ page.append("<table border=0>");
118
+
119
+ return page.toString();
120
+ }
121
+
122
+ public String makeFooter()
123
+ {
124
+ return "</table></blockquote></body></html>";
125
+ }
126
+
127
+
128
+ static String htmlColor(Color c)
129
+ {
130
+ return '#'+hex2(c.getRed())+hex2(c.getGreen())+hex2(c.getBlue());
131
+ }
132
+
133
+ private static final String hexDigits="0123456789ABCDEF";
134
+ private static String hex2(int n)
135
+ {
136
+ return hexDigits.charAt((n/16)%16)+""+hexDigits.charAt(n%16);
137
+ }
138
+ @Override
139
+ public String getName() {
140
+ return "HTML List";
141
+ }
142
+
143
+}
timeflow/format/file/Import.java
....@@ -0,0 +1,9 @@
1
+package timeflow.format.file;
2
+
3
+import java.io.File;
4
+import timeflow.data.db.ActDB;
5
+
6
+public interface Import {
7
+ public String getName();
8
+ public ActDB importFile(File file) throws Exception;
9
+}
timeflow/format/file/TimeflowFormat.java
....@@ -0,0 +1,163 @@
1
+package timeflow.format.file;
2
+
3
+import timeflow.model.*;
4
+import timeflow.data.db.*;
5
+import timeflow.data.time.*;
6
+import timeflow.format.field.*;
7
+
8
+import timeflow.util.*;
9
+
10
+import java.io.*;
11
+import java.net.URL;
12
+import java.util.*;
13
+
14
+public class TimeflowFormat implements Import, Export
15
+{
16
+ private static final String END_OF_SCHEMA="#TIMEFLOW\tend-metadata";
17
+ private static final String END_OF_METADATA="#TIMEFLOW\t====== End of Header. Data below is in tab-delimited format. =====";
18
+ public ActDB readFile(String fileName, PrintStream messages) throws Exception
19
+ {
20
+ return read(new File(fileName), messages);
21
+ }
22
+
23
+ public static ActDB read(File file, PrintStream out) throws Exception
24
+ {
25
+ String text=IO.read(file.getAbsolutePath());
26
+ DelimitedText quote=new DelimitedText('\t');
27
+ Iterator<String[]> lines=quote.read(text).iterator();
28
+
29
+
30
+ ActDB db=null;
31
+ List<String> fieldNames=new ArrayList<String>();
32
+ List<Class> fieldTypes=new ArrayList<Class>();
33
+ List<Integer> fieldSizes=new ArrayList<Integer>();
34
+ String source="[unknown]", description="";
35
+ for (;;)
36
+ {
37
+ String[] t=lines.next();
38
+
39
+ if (t[1].equals("field"))
40
+ {
41
+ fieldNames.add(t[2]);
42
+ fieldTypes.add(FieldFormatCatalog.javaClass(t[3]));
43
+ if (t.length>4)
44
+ {
45
+ fieldSizes.add(Integer.parseInt(t[4]));
46
+ }
47
+ else
48
+ fieldSizes.add(-1);
49
+ }
50
+ else if (t[1].equals("source"))
51
+ {
52
+ source=t[2];
53
+ }
54
+ else if (t[1].equals("description"))
55
+ {
56
+ description=t[2];
57
+
58
+ }
59
+ else if (t[1].equals("end-metadata"))
60
+ break;
61
+ }
62
+ db=new ArrayDB((String[])fieldNames.toArray(new String[0]),
63
+ (Class[])fieldTypes.toArray(new Class[0]), source);
64
+ db.setDescription(description);
65
+ for (int i=0; i<fieldNames.size(); i++)
66
+ if (fieldSizes.get(i)>0)
67
+ db.getField(fieldNames.get(i)).setRecommendedSize(fieldSizes.get(i));
68
+ for (;;)
69
+ {
70
+ String[] t=lines.next();
71
+ if (t[1].startsWith("==="))
72
+ break;
73
+ if (t[1].equals("alias"))
74
+ db.setAlias(db.getField(t[3]), t[2]);
75
+ }
76
+
77
+ // note: in some cases headers may be in a different order than in
78
+ // metadata section, so we will read these.
79
+ String[] headers=lines.next();
80
+ if (headers.length!=fieldNames.size())
81
+ throw new IllegalArgumentException("Different number of headers than fields!");
82
+
83
+
84
+ while (lines.hasNext())
85
+ {
86
+ String[] t=lines.next();
87
+ Act a=db.createAct();
88
+ for (int i=0; i<t.length; i++)
89
+ {
90
+ Field f=db.getField(headers[i]);
91
+ FieldFormat format=FieldFormatCatalog.getFormat(f.getType());
92
+ a.set(f, format.parse(t[i]));
93
+ }
94
+ }
95
+
96
+ return db;
97
+ }
98
+
99
+ public static void write(ActList acts, BufferedWriter bw) throws IOException
100
+ {
101
+ ActDB db=acts.getDB();
102
+
103
+ PrintWriter out=new PrintWriter(bw);
104
+
105
+ DelimitedText tab=new DelimitedText('\t');
106
+
107
+ // Write version
108
+ out.println("#TIMEFLOW\tformat version\t1");
109
+
110
+ // Write source of data.
111
+ out.println("#TIMEFLOW\tsource\t"+tab.write(db.getSource()));
112
+
113
+ // Write description of data.
114
+ out.println("#TIMEFLOW\tdescription\t"+tab.write(db.getDescription()));
115
+
116
+ // Write schema.
117
+ List<Field> fields=db.getFields();
118
+ for (Field f: fields)
119
+ {
120
+ String recSize=f.getRecommendedSize()<=0 ? "" : "\t"+f.getRecommendedSize();
121
+ out.println("#TIMEFLOW\tfield\t"+tab.write(f.getName())+
122
+ "\t"+FieldFormatCatalog.humanName(f.getType())+recSize);
123
+ }
124
+
125
+ out.println(END_OF_SCHEMA);
126
+
127
+ // Write column mappings.
128
+ List<String> aliases=DBUtils.getFieldAliases(db);
129
+ for (String a:aliases)
130
+ out.println("#TIMEFLOW\talias\t"+a+"\t"+tab.write(db.getField(a).getName()));
131
+
132
+ // Write end of header indicator
133
+ out.println(END_OF_METADATA);
134
+
135
+ // Write data!
136
+ new DelimitedFormat('\t').writeDelimited(db, acts, out);
137
+
138
+ out.flush();
139
+ out.close();
140
+ }
141
+
142
+ public static void main(String[] args) throws Exception
143
+ {
144
+ System.out.println("Reading");
145
+ ActDB db=read(new File("test/monet.txt"), System.out);
146
+ System.out.println("# lines: "+db.size());
147
+ }
148
+
149
+ @Override
150
+ public String getName() {
151
+ return "TimeFlow Format";
152
+ }
153
+
154
+ @Override
155
+ public ActDB importFile(File file) throws Exception {
156
+ return read(file, System.out);
157
+ }
158
+
159
+ @Override
160
+ public void export(TFModel model, BufferedWriter out) throws Exception {
161
+ write(model.getDB().all(), out);
162
+ }
163
+}
timeflow/model/Display.java
....@@ -0,0 +1,373 @@
1
+package timeflow.model;
2
+
3
+import timeflow.data.db.filter.ValueFilter;
4
+import timeflow.data.time.*;
5
+
6
+import java.awt.*;
7
+import java.util.*;
8
+import java.net.URI;
9
+import java.text.*;
10
+import javax.swing.*;
11
+
12
+// to do: read from a properties file!
13
+public class Display
14
+{
15
+
16
+ HashMap<String, String> strings = new HashMap<String, String>();
17
+ HashMap<String, Integer> ints = new HashMap<String, Integer>();
18
+ HashMap<String, Color> colors = new HashMap<String, Color>();
19
+ HashMap<Class, String> classLabel = new HashMap<Class, String>();
20
+ Color fallback = new Color(0, 53, 153, 128);
21
+ String fontName = "Verdana";
22
+ Font tinyFont = new Font(fontName, Font.BOLD, 9);
23
+ Font smallFont = new Font(fontName, Font.PLAIN, 11);
24
+ Font boldFont = new Font(fontName, Font.BOLD, 12);
25
+ Font plainFont = UIManager.getFont("Label.font");
26
+ Font bigFont = plainFont.deriveFont(Font.BOLD);
27
+ Font hugeFont = new Font(fontName, Font.BOLD, 16);
28
+ Font timeLabelFont = tinyFont;
29
+ FontMetrics hugeFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(hugeFont);
30
+ FontMetrics bigFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(bigFont);
31
+ FontMetrics plainFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(plainFont);
32
+ FontMetrics boldFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(boldFont);
33
+ FontMetrics tinyFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(tinyFont);
34
+ FontMetrics timeLabelFontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(timeLabelFont);
35
+ static DecimalFormat df = new DecimalFormat("###,###,###,###.##");
36
+ static DecimalFormat roundFormat = new DecimalFormat("###,###,###,###");
37
+ public static final String MISC_CODE = "Misc. ";
38
+ public static final double MAX_DOT_SIZE = 10;
39
+ public static final int CALENDAR_CELL_HEIGHT = 80;
40
+ public static Color barColor = new Color(150, 170, 200);
41
+ private static final double PHI = (1 + Math.sqrt(5)) / 2;
42
+ HashMap<String, Color> topColors = new HashMap<String, Color>();
43
+ static Color[] handPalette =
44
+ {
45
+ new Color(203, 31, 23),
46
+ new Color(237, 131, 0),
47
+ new Color(71, 175, 13),
48
+ new Color(6, 119, 207),
49
+ new Color(0, 188, 184),
50
+ new Color(209, 80, 174),
51
+ new Color(146, 6, 0),
52
+ new Color(175, 103, 0),
53
+ new Color(76, 124, 0),
54
+ new Color(0, 80, 143),
55
+ new Color(0, 128, 124),
56
+ new Color(153, 56, 126)
57
+ };
58
+ ValueFilter grayFilter;
59
+
60
+ public Display()
61
+ {
62
+
63
+ strings.put("other.label", "Other");
64
+ strings.put("null.label", "[none]");
65
+
66
+ classLabel.put(RoughTime.class, "Date/Time");
67
+ classLabel.put(String.class, "Text");
68
+ classLabel.put(Number.class, "Number");
69
+ classLabel.put(Double.class, "Number");
70
+ classLabel.put(Integer.class, "Number");
71
+ classLabel.put(String[].class, "List");
72
+
73
+ colors.put("chart.background", Color.white);
74
+ Color ui = new Color(240, 240, 240);
75
+ colors.put("splash.background", Color.white);
76
+ colors.put("splash.text", new Color(50, 50, 50));
77
+ colors.put("filterpanel.background", ui);
78
+ colors.put("visualpanel.background", ui);
79
+ colors.put("text.prominent", Color.black);
80
+ colors.put("text.normal", Color.gray);
81
+ colors.put("timeline.label", new Color(0, 53, 153));
82
+ colors.put("timeline.label.lesser", new Color(110, 153, 200));
83
+ colors.put("timeline.grid", new Color(240, 240, 240));
84
+ colors.put("timeline.grid.vertical", new Color(240, 240, 240));
85
+ colors.put("timeline.zebra", new Color(245, 245, 245));
86
+ colors.put("null.color", new Color(230, 230, 230));
87
+ colors.put("timeline.unspecified.color", new Color(0, 53, 153));
88
+ colors.put("highlight.color", new Color(0, 53, 153));
89
+
90
+ ints.put("timeline.datelabel.height", 20);
91
+ ints.put("timeline.item.height.min", 16);
92
+ }
93
+
94
+ public static void launchBrowser(String urlString)
95
+ {
96
+ if (Desktop.isDesktopSupported())
97
+ {
98
+ Desktop desktop = Desktop.getDesktop();
99
+ try
100
+ {
101
+ desktop.browse(new URI(urlString));
102
+ } catch (Exception e2)
103
+ {
104
+ e2.printStackTrace();
105
+ }
106
+ } else
107
+ {
108
+ System.out.println("Desktop not supported!");
109
+ }
110
+ }
111
+
112
+ public static String arrayToString(Object[] s)
113
+ {
114
+ if (s == null || s.length == 0)
115
+ {
116
+ return "";
117
+ }
118
+ StringBuffer b = new StringBuffer();
119
+ for (int i = 0; i < s.length; i++)
120
+ {
121
+ if (i > 0)
122
+ {
123
+ b.append(", ");
124
+ }
125
+ b.append(s[i]);
126
+
127
+ }
128
+ return b.toString();
129
+ }
130
+
131
+ public static String version()
132
+ {
133
+ return "TimeFlow 0.5";
134
+ }
135
+
136
+ public Font bold()
137
+ {
138
+ return boldFont;
139
+ }
140
+
141
+ public Font plain()
142
+ {
143
+ return plainFont;
144
+ }
145
+
146
+ public Font small()
147
+ {
148
+ return smallFont;
149
+ }
150
+
151
+ public FontMetrics hugeFontMetrics()
152
+ {
153
+ return hugeFontMetrics;
154
+ }
155
+
156
+ public FontMetrics plainFontMetrics()
157
+ {
158
+ return plainFontMetrics;
159
+ }
160
+
161
+ public FontMetrics boldFontMetrics()
162
+ {
163
+ return boldFontMetrics;
164
+ }
165
+
166
+ public FontMetrics tinyFontMetrics()
167
+ {
168
+ return tinyFontMetrics;
169
+ }
170
+
171
+ public FontMetrics timeLabelFontMetrics()
172
+ {
173
+ return timeLabelFontMetrics;
174
+ }
175
+
176
+ public Font huge()
177
+ {
178
+ return hugeFont;
179
+ }
180
+
181
+ public Font big()
182
+ {
183
+ return bigFont;
184
+ }
185
+
186
+ public Font tiny()
187
+ {
188
+ return tinyFont;
189
+ }
190
+
191
+ public Font timeLabel()
192
+ {
193
+ return timeLabelFont;
194
+ }
195
+
196
+ public String getString(String s)
197
+ {
198
+ return strings.get(s);
199
+ }
200
+
201
+ public int getInt(String s)
202
+ {
203
+ return ints.get(s);
204
+ }
205
+
206
+ public Color getColor(String key)
207
+ {
208
+ if (colors.get(key) == null)
209
+ {
210
+ throw new IllegalArgumentException("No color for " + key);
211
+ }
212
+ return colors.get(key);
213
+ }
214
+
215
+ public JLabel label(String s)
216
+ {
217
+ return new JLabel(s);
218
+ }
219
+
220
+ public String format(String s, int maxLength, boolean tryNoDots)
221
+ {
222
+ if (s == null)
223
+ {
224
+ return "";
225
+ }
226
+ int n = s.length();
227
+ if (n <= maxLength)
228
+ {
229
+ return s;
230
+ }
231
+ if (maxLength < 4)
232
+ {
233
+ return "...";
234
+ }
235
+ if (!tryNoDots)
236
+ {
237
+ return s.substring(0, maxLength - 3) + "...";
238
+ }
239
+ // find last space before maxLength and after maxLength/2
240
+ for (int j = maxLength - 1; j > maxLength / 2; j--)
241
+ {
242
+ if (s.charAt(j) == ' ')
243
+ {
244
+ return s.substring(0, j);
245
+ }
246
+ }
247
+ return s.length() <= maxLength ? s : (maxLength < 6 ? "" : s.substring(0, maxLength - 3) + "...");
248
+ }
249
+
250
+ public void refreshColors(Iterable<String> list)
251
+ {
252
+ topColors = new HashMap<String, Color>();
253
+ double x = .1;
254
+ int i = 0;
255
+ for (String s : list)
256
+ {
257
+ topColors.put(s, i < handPalette.length ? handPalette[i] : palette(x));
258
+ i++;
259
+ x += PHI;
260
+ }
261
+ }
262
+
263
+ public Color makeColor(String text)
264
+ {
265
+ if (grayFilter != null && !grayFilter.ok(text))
266
+ {
267
+ return new Color(200, 200, 200);//"null.color");
268
+ }
269
+ Color c = topColors.get(text);
270
+ return c == null ? _makeColor(text) : c;
271
+ }
272
+
273
+ private Color _makeColor(String text)
274
+ {
275
+ if (text == null)
276
+ {
277
+ return getColor("null.color");
278
+ }
279
+
280
+ int c = Math.abs(text.hashCode());
281
+ double h = ((c >> 8) % 255) / 255.;
282
+ return palette(h);
283
+ }
284
+
285
+ public static Color palette(double x)
286
+ {
287
+ float h = (float) (Math.abs(x) % 1);
288
+ float s = .8f - .25f * h;
289
+ float b = .8f - .25f * h;
290
+ return new Color(Color.HSBtoRGB(h, s, b));
291
+ }
292
+
293
+ public String getMiscLabel()
294
+ {
295
+ return getString("other.label");
296
+ }
297
+
298
+ public String getNullLabel()
299
+ {
300
+ return getString("null.label");
301
+ }
302
+
303
+ public String toString(Object o)
304
+ {
305
+ if (o == null)
306
+ {
307
+ return getNullLabel();
308
+ }
309
+ if (o instanceof Object[])
310
+ {
311
+ return arrayToString((Object[]) o);
312
+ }
313
+ if (o instanceof RoughTime)
314
+ {
315
+ return ((RoughTime) o).format();//UnitOfTime.format((RoughTime)o);
316
+ }
317
+ if (o instanceof Double)
318
+ {
319
+ return df.format((Double) o);
320
+ }
321
+ return o.toString();
322
+ }
323
+
324
+ public static String format(double x)
325
+ {
326
+ return Math.abs(x) > 999 ? roundFormat.format(x) : df.format(x);
327
+ }
328
+
329
+ public String format(RoughTime time)
330
+ {
331
+ if (time == null)
332
+ {
333
+ return getString("null.label");
334
+ }
335
+ return time.format();//UnitOfTime.format(time);
336
+ }
337
+
338
+ public static ArrayList<String> breakLines(String s, int lineChars, int firstOffset)
339
+ {
340
+ ArrayList<String> lines = new ArrayList<String>();
341
+ String[] words = s.split(" ");
342
+ String line = "";
343
+
344
+ for (int i = 0; i < words.length; i++)
345
+ {
346
+ // is the word just too, too long?
347
+ int n = words[i].length();
348
+ if (n > lineChars - 5)
349
+ {
350
+ words[i] = words[i].substring(0, lineChars / 2 - 2) + "..." + words[i].substring(n - lineChars / 2 + 2, n);
351
+ }
352
+ if (line.length() + words[i].length() > (lines.size() == 0 ? lineChars - firstOffset : lineChars))
353
+ {
354
+ lines.add(line);
355
+ line = "";
356
+ }
357
+ line += " " + words[i];
358
+ }
359
+ lines.add(line);
360
+ return lines;
361
+ }
362
+
363
+ public boolean emptyMessage(Graphics g, TFModel model)
364
+ {
365
+ if (model.getActs() == null || model.getActs().size() == 0)
366
+ {
367
+ g.setColor(getColor("text.prominent"));
368
+ g.drawString(model.getDB() == null || model.getDB().size() == 0 ? "Empty Database" : "No items found.", 10, 25);
369
+ return true;
370
+ }
371
+ return false;
372
+ }
373
+}
timeflow/model/ModelPanel.java
....@@ -0,0 +1,36 @@
1
+package timeflow.model;
2
+
3
+import javax.swing.*;
4
+
5
+public abstract class ModelPanel extends JPanel implements TFListener {
6
+
7
+ TFModel model;
8
+
9
+ public ModelPanel(TFModel model)
10
+ {
11
+ this.model=model;
12
+ }
13
+
14
+ @Override
15
+ public void addNotify()
16
+ {
17
+ super.addNotify();
18
+ model.addListener(this);
19
+ }
20
+
21
+
22
+ @Override
23
+ public void removeNotify()
24
+ {
25
+ super.removeNotify();
26
+ model.removeListener(this);
27
+ }
28
+
29
+ public TFModel getModel()
30
+ {
31
+ return model;
32
+ }
33
+
34
+ @Override
35
+ public abstract void note(TFEvent e);
36
+}
timeflow/model/TFEvent.java
....@@ -0,0 +1,43 @@
1
+package timeflow.model;
2
+
3
+public class TFEvent {
4
+ public enum Type {DATABASE_CHANGE, ACT_ADD, ACT_DELETE, ACT_CHANGE, ERROR, SOURCE_CHANGE, DESCRIPTION_CHANGE,
5
+ FIELD_ADD, FIELD_DELETE, FIELD_CHANGE, SELECTION_CHANGE, FILTER_CHANGE, VIEW_CHANGE};
6
+ public Type type;
7
+ public String message="[]";
8
+ public Object info;
9
+ public Object origin;
10
+
11
+ public TFEvent(Type type, Object origin)
12
+ {
13
+ this.type=type;
14
+ this.origin=origin;
15
+ }
16
+
17
+ public String toString()
18
+ {
19
+ return "[TimelineEvent: type="+type+", info="+info+", message="+message+", origin="+origin+"]";
20
+ }
21
+
22
+ public boolean affectsSchema()
23
+ {
24
+ switch (type){
25
+ case DATABASE_CHANGE:
26
+ case FIELD_ADD:
27
+ case FIELD_DELETE:
28
+ case FIELD_CHANGE: return true;
29
+ }
30
+ return false;
31
+ }
32
+
33
+ public boolean affectsRowSet()
34
+ {
35
+ return affectsSchema() || type==Type.ACT_CHANGE || type== Type.ACT_ADD || type== Type.ACT_DELETE
36
+ || type==Type.FILTER_CHANGE;
37
+ }
38
+
39
+ public boolean affectsData()
40
+ {
41
+ return type!=Type.SELECTION_CHANGE && type!=Type.VIEW_CHANGE && type!=Type.ERROR;
42
+ }
43
+}
timeflow/model/TFListener.java
....@@ -0,0 +1,5 @@
1
+package timeflow.model;
2
+
3
+public interface TFListener {
4
+ public void note(TFEvent e);
5
+}
timeflow/model/TFModel.java
....@@ -0,0 +1,303 @@
1
+package timeflow.model;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.db.filter.*;
5
+import timeflow.data.time.*;
6
+
7
+
8
+import java.util.*;
9
+
10
+// encapsulates all properties of a timeline model:
11
+// data, display properties, etc.
12
+// also does listening, etc.
13
+public class TFModel
14
+{
15
+
16
+ private ActDB db;
17
+ private ActList acts;
18
+ private ActFilter filter = new ConstFilter(true);
19
+ private ArrayList<TFListener> listeners = new ArrayList<TFListener>();
20
+ private Display display = new Display();
21
+ private String[] labelGuesses =
22
+ {
23
+ "label", "LABEL", "Label", "title", "TITLE", "Title",
24
+ "name", "Name", "NAME"
25
+ };
26
+ private String[] startGuesses =
27
+ {
28
+ "start", "Start", "START"
29
+ };
30
+ private String dbFile = "[unknown source]";
31
+ private boolean changedSinceSave;
32
+ private boolean readOnly;
33
+ private double minSize, maxSize;
34
+ private Interval viewInterval;
35
+
36
+ public ValueFilter getGrayFilter()
37
+ {
38
+ return display.grayFilter;
39
+ }
40
+
41
+ public void setGrayFilter(ValueFilter grayFilter, Object origin)
42
+ {
43
+ display.grayFilter = grayFilter;
44
+ fireEvent(new TFEvent(TFEvent.Type.FILTER_CHANGE, origin));
45
+ }
46
+
47
+ public ActFilter getFilter()
48
+ {
49
+ return filter;
50
+ }
51
+
52
+ public Interval getViewInterval()
53
+ {
54
+ return viewInterval;
55
+ }
56
+
57
+ public void setViewInterval(Interval viewInterval)
58
+ {
59
+ this.viewInterval = viewInterval;
60
+ }
61
+
62
+ public double getMinSize()
63
+ {
64
+ return minSize;
65
+ }
66
+
67
+ public void setMinSize(double minSize)
68
+ {
69
+ this.minSize = minSize;
70
+ }
71
+
72
+ public double getMaxSize()
73
+ {
74
+ return maxSize;
75
+ }
76
+
77
+ public void setMaxSize(double maxSize)
78
+ {
79
+ this.maxSize = maxSize;
80
+ }
81
+
82
+ public String getDbFile()
83
+ {
84
+ return dbFile;
85
+ }
86
+
87
+ public boolean getReadOnly()
88
+ {
89
+ return readOnly;
90
+ }
91
+
92
+ public void setDbFile(String dbFile, boolean readOnly, Object origin)
93
+ {
94
+ this.dbFile = dbFile;
95
+ fireEvent(new TFEvent(TFEvent.Type.SOURCE_CHANGE, origin));
96
+ }
97
+
98
+ public boolean isChangedSinceSave()
99
+ {
100
+ return changedSinceSave;
101
+ }
102
+
103
+ public void setChangedSinceSave(boolean changedSinceSave)
104
+ {
105
+ this.changedSinceSave = changedSinceSave;
106
+ }
107
+
108
+ public Display getDisplay()
109
+ {
110
+ return display;
111
+ }
112
+
113
+ public ActDB getDB()
114
+ {
115
+ return db;
116
+ }
117
+
118
+ public ActList getActs()
119
+ {
120
+ return acts;
121
+ }
122
+
123
+ public void addListener(TFListener t)
124
+ {
125
+ listeners.add(t);
126
+ }
127
+
128
+ public void removeListener(TFListener t)
129
+ {
130
+ listeners.remove(t);
131
+ }
132
+
133
+ public void noteNewDescription(Object origin)
134
+ {
135
+ setChangedSinceSave(true);
136
+ fireEvent(new TFEvent(TFEvent.Type.DESCRIPTION_CHANGE, origin));
137
+ }
138
+
139
+ public void noteNewSource(Object origin)
140
+ {
141
+ setChangedSinceSave(true);
142
+ fireEvent(new TFEvent(TFEvent.Type.SOURCE_CHANGE, origin));
143
+ }
144
+
145
+ public void noteRecordChange(Object origin)
146
+ {
147
+ setChangedSinceSave(true);
148
+ fireEvent(new TFEvent(TFEvent.Type.ACT_CHANGE, origin));
149
+ }
150
+
151
+ public void noteAddField(Object origin)
152
+ {
153
+ setChangedSinceSave(true);
154
+ fireEvent(new TFEvent(TFEvent.Type.FIELD_ADD, origin));
155
+ }
156
+
157
+ public void noteError(Object origin)
158
+ {
159
+ fireEvent(new TFEvent(TFEvent.Type.ERROR, origin));
160
+ }
161
+
162
+ public void noteDelete(Object origin)
163
+ {
164
+ setChangedSinceSave(true);
165
+ updateActs();
166
+ fireEvent(new TFEvent(TFEvent.Type.ACT_DELETE, origin));
167
+ }
168
+
169
+ public void noteSchemaChange(Object origin)
170
+ {
171
+ setChangedSinceSave(true);
172
+ updateActs();
173
+ fireEvent(new TFEvent(TFEvent.Type.DATABASE_CHANGE, origin)); // @TODO: make schema change?
174
+ }
175
+
176
+ public void noteAdd(Object origin)
177
+ {
178
+ setChangedSinceSave(true);
179
+ updateActs();
180
+ fireEvent(new TFEvent(TFEvent.Type.ACT_ADD, origin));
181
+ }
182
+
183
+ public void setFilter(ActFilter filter, Object origin)
184
+ {
185
+ this.filter = filter;
186
+ updateActs();
187
+ fireEvent(new TFEvent(TFEvent.Type.FILTER_CHANGE, origin));
188
+ }
189
+
190
+ private void updateActs()
191
+ {
192
+ acts = db.select(filter);
193
+ }
194
+
195
+ public void setDB(ActDB db, String dbFile, boolean readOnly, Object origin)
196
+ {
197
+ this.db = db;
198
+ this.dbFile = dbFile;
199
+ this.readOnly = readOnly;
200
+ setChangedSinceSave(false);
201
+ this.filter = new ConstFilter(true);
202
+ this.acts = db.all();
203
+ initVisualEncodings();
204
+ refreshColors();
205
+ fireEvent(new TFEvent(TFEvent.Type.DATABASE_CHANGE, origin));
206
+ }
207
+
208
+ private void initVisualEncodings()
209
+ {
210
+ guessField(VirtualField.LABEL, labelGuesses, String.class);
211
+ guessField(VirtualField.START, startGuesses, RoughTime.class);
212
+ viewInterval = null;
213
+ }
214
+
215
+ public void refreshColors()
216
+ {
217
+ display.grayFilter = null;
218
+ Field colorField = getColorField();
219
+ if (colorField == null)
220
+ {
221
+ return;
222
+ }
223
+ List<String> top25 = DBUtils.countValues(db, colorField).listTop(25);
224
+ display.refreshColors(top25);
225
+ }
226
+
227
+ private void guessField(String name, String[] guesses, Class type)
228
+ {
229
+ Field field = db.getField(name);
230
+ if (field == null)
231
+ {
232
+ for (int i = 0; i < guesses.length; i++)
233
+ {
234
+ Field f = db.getField(guesses[i]);
235
+ if (f != null && f.getType() == type)
236
+ {
237
+ field = f;
238
+ break;
239
+ }
240
+ }
241
+ if (field == null)
242
+ {
243
+ List<Field> f = db.getFields(type);
244
+ if (f.size() > 0)
245
+ {
246
+ field = f.get(0);
247
+ }
248
+ }
249
+ if (field != null)
250
+ {
251
+ db.setAlias(field, name);
252
+ }
253
+ }
254
+ }
255
+
256
+ private void fireEvent(TFEvent e)
257
+ {
258
+ // clone list before going through it, because some events can cause
259
+ // listeners to be added or removed.
260
+
261
+ for (TFListener t : (List<TFListener>) listeners.clone())
262
+ {
263
+ if (t != e.origin)
264
+ {
265
+ t.note(e);
266
+ }
267
+ }
268
+ }
269
+
270
+ public void setFieldAlias(Field field, String alias, Object origin)
271
+ {
272
+ db.setAlias(field, alias);
273
+ if (db.size() > 0 && field == getColorField())
274
+ {
275
+ refreshColors();
276
+ }
277
+ fireEvent(new TFEvent(TFEvent.Type.VIEW_CHANGE, origin));
278
+ }
279
+
280
+ public Field getColorField()
281
+ {
282
+ if (db == null)
283
+ {
284
+ return null;
285
+ }
286
+ Field f = db.getField(VirtualField.COLOR);
287
+ if (f != null)
288
+ {
289
+ return f;
290
+ }
291
+ return db.getField(VirtualField.TRACK);
292
+ }
293
+
294
+ public void setReadOnly(boolean readOnly)
295
+ {
296
+ this.readOnly = readOnly;
297
+ }
298
+
299
+ public boolean isReadOnly()
300
+ {
301
+ return readOnly;
302
+ }
303
+}
timeflow/model/VirtualField.java
....@@ -0,0 +1,39 @@
1
+package timeflow.model;
2
+
3
+import java.util.*;
4
+
5
+public class VirtualField {
6
+ public static final String LABEL="TIMEFLOW_LABEL";
7
+ public static final String COLOR="TIMEFLOW_COLOR";
8
+ public static final String SIZE="TIMEFLOW_SIZE";
9
+ public static final String TRACK="TIMEFLOW_TRACK";
10
+ public static final String START="TIMEFLOW_START";
11
+ public static final String LATEST_START="TIMEFLOW_LATEST_START";
12
+ public static final String END="TIMEFLOW_END";
13
+ public static final String EARLIEST_END="TIMEFLOW_EARLIEST_END";
14
+
15
+ private static HashMap<String, String> humanNames=new HashMap<String, String>();
16
+ private static void tie(String a, String b) {humanNames.put(a,b);}
17
+
18
+ static
19
+ {
20
+ tie(LABEL, "Label");
21
+ tie(COLOR, "Color");
22
+ tie(SIZE, "Size");
23
+ tie(TRACK, "Track");
24
+ tie(START, "Start");
25
+ tie(LATEST_START, "Latest Start");
26
+ tie(END, "End");
27
+ tie(EARLIEST_END, "Earliest End");
28
+ }
29
+
30
+ public static String humanName(String s)
31
+ {
32
+ return humanNames.get(s);
33
+ }
34
+
35
+ public static Iterable<String> list()
36
+ {
37
+ return humanNames.keySet();
38
+ }
39
+}
timeflow/util/Bag.java
....@@ -0,0 +1,136 @@
1
+package timeflow.util;
2
+
3
+import java.util.*;
4
+
5
+public class Bag<T> implements Iterable<T> {
6
+ HashMap<T, Count> table;
7
+ int max;
8
+
9
+ public Bag()
10
+ {
11
+ table=new HashMap<T, Count>();
12
+ }
13
+
14
+ public Bag(Iterable<T> i)
15
+ {
16
+ for (T x:i)
17
+ add(x);
18
+ }
19
+
20
+ public Bag(T[] array)
21
+ {
22
+ for (int i=0; i<array.length; i++)
23
+ add(array[i]);
24
+ }
25
+
26
+ public int getMax()
27
+ {
28
+ return max;
29
+ }
30
+
31
+ public List<T> listTop(int n)
32
+ {
33
+ int count=0;
34
+ Iterator<T> i=list().iterator();
35
+ List<T> top=new ArrayList<T>();
36
+ while (count<n && i.hasNext())
37
+ {
38
+ top.add(i.next());
39
+ count++;
40
+ }
41
+ return top;
42
+ }
43
+
44
+ public List<T> unordered()
45
+ {
46
+ List<T> result=new ArrayList<T>();
47
+ result.addAll(table.keySet());
48
+ return result;
49
+ }
50
+
51
+ public List<T> list()
52
+ {
53
+ List<T> result=new ArrayList<T>();
54
+ result.addAll(table.keySet());
55
+
56
+ Collections.sort(result, new Comparator<T>()
57
+ {
58
+ public int compare(T x, T y)
59
+ {
60
+ return num(y)-num(x);
61
+ }
62
+ });
63
+ return result;
64
+ }
65
+
66
+ public int num(T x)
67
+ {
68
+ Count c=table.get(x);
69
+ if (c!=null)
70
+ return c.num;
71
+ else
72
+ return 0;
73
+ }
74
+
75
+ public int add(T x)
76
+ {
77
+ Count c=table.get(x);
78
+ int n=0;
79
+ if (c!=null)
80
+ n=++c.num;
81
+ else
82
+ {
83
+ table.put(x, new Count(1));
84
+ n=1;
85
+ }
86
+ max=Math.max(n,max);
87
+ return n;
88
+ }
89
+
90
+ class Count
91
+ {
92
+ int num;
93
+ public Count(int num)
94
+ {
95
+ this.num=num;
96
+ }
97
+ }
98
+
99
+ public int size()
100
+ {
101
+ return table.size();
102
+ }
103
+
104
+ public int removeLessThan(int cut)
105
+ {
106
+
107
+ Set<T> small=new HashSet<T>();
108
+ for (T x: table.keySet())
109
+ {
110
+ if (num(x)<cut)
111
+ small.add(x);
112
+ }
113
+ for (T x:small)
114
+ table.remove(x);
115
+ return small.size();
116
+ }
117
+
118
+ public static void main(String[] args)
119
+ {
120
+ Bag<String> b=new Bag<String>();
121
+ b.add("a");
122
+ b.add("b");
123
+ b.add("a");
124
+ System.out.println(b.num("a"));
125
+ System.out.println(b.num("b"));
126
+ System.out.println(b.num("c"));
127
+ List<String> s=b.list();
128
+ for (int i=0; i<s.size(); i++)
129
+ System.out.println(s.get(i)+": "+b.num(s.get(i)));
130
+ }
131
+
132
+ @Override
133
+ public Iterator<T> iterator() {
134
+ return table.keySet().iterator();
135
+ }
136
+}
timeflow/util/ColorUtils.java
....@@ -0,0 +1,31 @@
1
+package timeflow.util;
2
+
3
+import java.awt.*;
4
+import java.awt.image.*;
5
+
6
+public class ColorUtils
7
+{
8
+
9
+ public static Color alpha(Color c, int a)
10
+ {
11
+ return new Color(c.getRed(), c.getGreen(), c.getBlue(),a);
12
+ }
13
+
14
+ public static Color interpolate(Color x, Color y, double u)
15
+ {
16
+ return new Color(interp(x.getRed(), y.getRed(), u),
17
+ interp(x.getGreen(), y.getGreen(), u),
18
+ interp(x.getBlue(), y.getBlue(), u));
19
+ }
20
+
21
+ private static int interp(int x, int y, double u)
22
+ {
23
+ return (int)(y*u+x*(1-u));
24
+ }
25
+
26
+ public static float[] hsb(Color c)
27
+ {
28
+ return Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), new float[3]);
29
+ }
30
+
31
+}
timeflow/util/DoubleBag.java
....@@ -0,0 +1,145 @@
1
+package timeflow.util;
2
+
3
+import java.util.*;
4
+
5
+public class DoubleBag<T> implements Iterable<T> {
6
+ private HashMap<T, Count> table;
7
+ private double max;
8
+
9
+ public DoubleBag()
10
+ {
11
+ table=new HashMap<T, Count>();
12
+ }
13
+
14
+ public double getMax()
15
+ {
16
+ return max;
17
+ }
18
+
19
+ public List<T> listTop(int n, boolean useSum)
20
+ {
21
+ int count=0;
22
+ Iterator<T> i=list(useSum).iterator();
23
+ List<T> top=new ArrayList<T>();
24
+ while (count<n && i.hasNext())
25
+ {
26
+ top.add(i.next());
27
+ count++;
28
+ }
29
+ return top;
30
+ }
31
+
32
+ public List<T> list(final boolean useSum)
33
+ {
34
+ List<T> result=new ArrayList<T>();
35
+ result.addAll(table.keySet());
36
+
37
+ Collections.sort(result, new Comparator<T>()
38
+ {
39
+ public int compare(T x, T y)
40
+ {
41
+ double d= useSum ? num(y)-num(x) : average(y)-average(x);
42
+ return d>0 ? 1 : (d<0 ? -1 : 0);
43
+ }
44
+ });
45
+ return result;
46
+ }
47
+
48
+ public double num(T x)
49
+ {
50
+ Count c=table.get(x);
51
+ if (c!=null)
52
+ return c.num;
53
+ else
54
+ return 0;
55
+ }
56
+
57
+ public double average(T x)
58
+ {
59
+ Count c=table.get(x);
60
+ return c.num/c.vals;
61
+ }
62
+
63
+ public void add(T x, double z)
64
+ {
65
+ if (Double.isNaN(z))
66
+ return;
67
+ Count c=table.get(x);
68
+ double sum=z;
69
+ if (c!=null)
70
+ {
71
+ c.add(z);
72
+ sum=c.num;
73
+ }
74
+ else
75
+ {
76
+ table.put(x, new Count(z));
77
+ }
78
+ max=Math.max(sum, max);
79
+ }
80
+
81
+ class Count
82
+ {
83
+ double num;
84
+ int vals;
85
+
86
+ public Count(double num)
87
+ {
88
+ this.num=num;
89
+ vals=1;
90
+ }
91
+
92
+ public double add(double x)
93
+ {
94
+ vals++;
95
+ return num+=x;
96
+ }
97
+ }
98
+
99
+ public int size()
100
+ {
101
+ return table.size();
102
+ }
103
+
104
+
105
+ public List<T> unordered()
106
+ {
107
+ List<T> result=new ArrayList<T>();
108
+ result.addAll(table.keySet());
109
+ return result;
110
+ }
111
+
112
+
113
+ public int removeLessThan(int cut)
114
+ {
115
+
116
+ Set<T> small=new HashSet<T>();
117
+ for (T x: table.keySet())
118
+ {
119
+ if (num(x)<cut)
120
+ small.add(x);
121
+ }
122
+ for (T x:small)
123
+ table.remove(x);
124
+ return small.size();
125
+ }
126
+
127
+ public static void main(String[] args)
128
+ {
129
+ DoubleBag<String> b=new DoubleBag<String>();
130
+ b.add("a",1);
131
+ b.add("b",2);
132
+ b.add("a",3);
133
+ System.out.println(b.num("a"));
134
+ System.out.println(b.num("b"));
135
+ System.out.println(b.num("c"));
136
+ List<String> s=b.list(true);
137
+ for (int i=0; i<s.size(); i++)
138
+ System.out.println(s.get(i)+": "+b.num(s.get(i)));
139
+ }
140
+
141
+ @Override
142
+ public Iterator<T> iterator() {
143
+ return table.keySet().iterator();
144
+ }
145
+}
timeflow/util/IO.java
....@@ -0,0 +1,54 @@
1
+package timeflow.util;
2
+
3
+import java.io.*;
4
+import java.util.*;
5
+
6
+public class IO {
7
+
8
+ public static ArrayList<String> lines(String fileName) throws IOException
9
+ {
10
+ ArrayList<String> a=new ArrayList<String>();
11
+ String line=null;
12
+ FileReader fr=new FileReader(fileName);
13
+ BufferedReader in=new BufferedReader(fr);
14
+ while (null != (line=in.readLine()))
15
+ a.add(line);
16
+ in.close();
17
+ fr.close();
18
+ return a;
19
+ }
20
+
21
+ public static String[] lineArray(String fileName) throws IOException
22
+ {
23
+ ArrayList<String> a=lines(fileName);
24
+ return (String[])a.toArray(new String[0]);
25
+ }
26
+
27
+ public static String read(File file) throws IOException
28
+ {
29
+ char[] buffer = new char[1024];
30
+ int n = 0;
31
+ StringBuilder builder = new StringBuilder();
32
+ FileReader reader = new FileReader(file);
33
+ BufferedReader b = new BufferedReader(reader);
34
+ while ((n = b.read(buffer, 0, buffer.length)) != -1)
35
+ builder.append(buffer, 0, n);
36
+ b.close();
37
+ reader.close();
38
+ return builder.toString();
39
+ }
40
+
41
+ public static String read(String fileName) throws IOException
42
+ {
43
+ char[] buffer = new char[1024];
44
+ int n = 0;
45
+ StringBuilder builder = new StringBuilder();
46
+ FileReader reader = new FileReader(fileName);
47
+ BufferedReader b = new BufferedReader(reader);
48
+ while ((n = b.read(buffer, 0, buffer.length)) != -1)
49
+ builder.append(buffer, 0, n);
50
+ b.close();
51
+ reader.close();
52
+ return builder.toString();
53
+ }
54
+}
timeflow/util/Pad.java
....@@ -0,0 +1,19 @@
1
+package timeflow.util;
2
+
3
+import java.awt.*;
4
+import javax.swing.*;
5
+
6
+public class Pad extends JPanel {
7
+ Dimension pref;
8
+
9
+ public Pad(int w, int h)
10
+ {
11
+ pref=new Dimension(w,h);
12
+ setBackground(Color.white);
13
+ }
14
+
15
+ public Dimension getPreferredSize()
16
+ {
17
+ return pref;
18
+ }
19
+}
timeflow/util/TimeIt.java
....@@ -0,0 +1,34 @@
1
+package timeflow.util;
2
+
3
+import java.util.*;
4
+
5
+public class TimeIt {
6
+ public static long last;
7
+ static HashMap<Object, Long> marks=new HashMap<Object, Long>();
8
+
9
+ public static void mark()
10
+ {
11
+ last=System.currentTimeMillis();
12
+ }
13
+
14
+ public static void sinceLast()
15
+ {
16
+ long now=System.currentTimeMillis();
17
+ System.out.println("TimeIt: "+(now-last));
18
+ last=now;
19
+ }
20
+
21
+ public static void since(Object o)
22
+ {
23
+ long now=System.currentTimeMillis();
24
+ System.out.println("TimeIt: "+o+": "+(now-last));
25
+ last=now;
26
+ }
27
+
28
+ public static void mark(Object o)
29
+ {
30
+ long now=System.currentTimeMillis();
31
+ marks.put(o, System.currentTimeMillis());
32
+ last=now;
33
+ }
34
+}
timeflow/views/AbstractView.java
....@@ -0,0 +1,85 @@
1
+package timeflow.views;
2
+
3
+import javax.swing.*;
4
+
5
+import timeflow.data.db.ActDB;
6
+import timeflow.model.*;
7
+
8
+import java.awt.*;
9
+
10
+// superclass of all timeline views
11
+public abstract class AbstractView extends ModelPanel
12
+{
13
+
14
+ protected boolean ignoreEventsWhenInvisible = true;
15
+ JPanel panel;
16
+ ActDB lastDrawn, lastNotified;
17
+
18
+ public AbstractView(TFModel model)
19
+ {
20
+ super(model);
21
+ }
22
+
23
+ public void paintComponent(Graphics g)
24
+ {
25
+ g.drawString(getName(), 10, 30);
26
+ }
27
+
28
+ public final JComponent getControls()
29
+ {
30
+ if (panel != null)
31
+ {
32
+ return panel;
33
+ }
34
+
35
+ panel = new JPanel();
36
+ panel.setLayout(new BorderLayout());
37
+ panel.add(_getControls(), BorderLayout.CENTER);
38
+ JLabel controlLabel = new JLabel(" " + getName() + " Controls")
39
+ {
40
+
41
+ public Dimension getPreferredSize()
42
+ {
43
+ return new Dimension(30, 30);
44
+ }
45
+ };
46
+ controlLabel.setBackground(Color.lightGray);
47
+ controlLabel.setForeground(Color.darkGray);
48
+ panel.add(controlLabel, BorderLayout.NORTH);
49
+
50
+ return panel;
51
+ }
52
+
53
+ protected JComponent _getControls()
54
+ {
55
+ return new JLabel("local: " + getName());
56
+ }
57
+
58
+ public abstract String getName();
59
+
60
+ protected abstract void _note(TFEvent e);
61
+
62
+ protected abstract void onscreen(boolean majorChangeHappened);
63
+
64
+ @Override
65
+ public final void note(TFEvent e)
66
+ {
67
+ lastNotified = getModel().getDB();
68
+ if (isVisible() || !ignoreEventsWhenInvisible)
69
+ {
70
+ _note(e);
71
+ lastDrawn = lastNotified;
72
+ }
73
+ }
74
+
75
+ @Override
76
+ public void setVisible(boolean visible)
77
+ {
78
+ super.setVisible(visible);
79
+ if (visible && getModel().getDB() != null)
80
+ {
81
+ onscreen(lastDrawn != lastNotified);
82
+ lastDrawn = lastNotified;
83
+ }
84
+ }
85
+}
timeflow/views/AbstractVisualizationView.java
....@@ -0,0 +1,244 @@
1
+package timeflow.views;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+import timeflow.model.*;
6
+import timeflow.vis.*;
7
+import timeflow.app.ui.*;
8
+
9
+import java.awt.*;
10
+import java.awt.event.*;
11
+import java.awt.image.BufferedImage;
12
+import java.net.URL;
13
+import java.util.*;
14
+
15
+import javax.swing.*;
16
+
17
+public abstract class AbstractVisualizationView extends JPanel
18
+{
19
+
20
+ Image buffer;
21
+ Graphics2D graphics;
22
+ Point mouse = new Point(-10000, 0), firstMouse = new Point();
23
+ boolean mouseIsDown;
24
+ ArrayList<Mouseover> objectLocations = new ArrayList<Mouseover>();
25
+ TFModel model;
26
+ Act selectedAct;
27
+ RoughTime selectedTime;
28
+ Set<JMenuItem> urlItems = new HashSet<JMenuItem>();
29
+
30
+ public AbstractVisualizationView(TFModel model)
31
+ {
32
+ this.model = model;
33
+
34
+ // deal with mouseovers.
35
+ addMouseMotionListener(new MouseMotionListener()
36
+ {
37
+
38
+ @Override
39
+ public void mouseDragged(MouseEvent e)
40
+ {
41
+ mouse.setLocation(e.getX(), e.getY());
42
+ repaint();
43
+ }
44
+
45
+ @Override
46
+ public void mouseMoved(MouseEvent e)
47
+ {
48
+ mouse.setLocation(e.getX(), e.getY());
49
+ repaint();
50
+ }
51
+ });
52
+
53
+
54
+ final JPopupMenu popup = new JPopupMenu();
55
+ final JMenuItem edit = new JMenuItem("Edit");
56
+ edit.addActionListener(new ActionListener()
57
+ {
58
+
59
+ @Override
60
+ public void actionPerformed(ActionEvent e)
61
+ {
62
+ EditRecordPanel.edit(getModel(), selectedAct);
63
+ }
64
+ });
65
+ popup.add(edit);
66
+
67
+ final JMenuItem delete = new JMenuItem("Delete");
68
+ popup.add(delete);
69
+ delete.addActionListener(new ActionListener()
70
+ {
71
+
72
+ @Override
73
+ public void actionPerformed(ActionEvent e)
74
+ {
75
+ getModel().getDB().delete(selectedAct);
76
+ getModel().noteDelete(this);
77
+ }
78
+ });
79
+
80
+ final JMenuItem add = new JMenuItem("New...");
81
+ popup.add(add);
82
+ add.addActionListener(new ActionListener()
83
+ {
84
+
85
+ @Override
86
+ public void actionPerformed(ActionEvent e)
87
+ {
88
+ EditRecordPanel.add(getModel(), selectedTime);
89
+ }
90
+ });
91
+
92
+ // deal with right-click.
93
+ addMouseListener(new MouseAdapter()
94
+ {
95
+
96
+ public void mousePressed(MouseEvent e)
97
+ {
98
+ pop(e);
99
+ }
100
+
101
+ public void mouseReleased(MouseEvent e)
102
+ {
103
+ pop(e);
104
+ }
105
+
106
+ private void pop(MouseEvent e)
107
+ {
108
+ if (e.isPopupTrigger())
109
+ {
110
+ Point p = new Point(e.getX(), e.getY());
111
+ Mouseover o = find(p);
112
+ boolean onAct = o != null && o.thing instanceof VisualAct;
113
+ if (onAct)
114
+ {
115
+ VisualAct v = (VisualAct) o.thing;
116
+ selectedAct = v.getAct();
117
+ String name = " '" + v.getLabel() + "'";
118
+ edit.setText("Edit" + name + "...");
119
+ delete.setText("Delete" + name);
120
+ edit.setEnabled(true);
121
+ delete.setEnabled(true);
122
+ } else
123
+ {
124
+ edit.setEnabled(false);
125
+ edit.setText("Edit Event");
126
+ delete.setEnabled(false);
127
+ delete.setText("Delete Event");
128
+ }
129
+ selectedTime = getTime(p);
130
+ if (selectedTime != null || onAct)
131
+ {
132
+ add.setEnabled(selectedTime != null);
133
+ add.setText(selectedTime == null ? "Add" : "Add Event At " + selectedTime.format() + "...");
134
+
135
+ java.util.List<Field> urlFields = getModel().getDB().getFields(URL.class);
136
+ if (urlFields.size() > 0)
137
+ {
138
+ // remove any old items.
139
+ for (JMenuItem m : urlItems)
140
+ {
141
+ popup.remove(m);
142
+ }
143
+ urlItems.clear();
144
+
145
+ if (onAct)
146
+ {
147
+ Act a = ((VisualAct) o.thing).getAct();
148
+ for (Field f : urlFields)
149
+ {
150
+ final URL url = a.getURL(f);
151
+ JMenuItem go = new JMenuItem("Go to " + url);
152
+ go.addActionListener(new ActionListener()
153
+ {
154
+
155
+ @Override
156
+ public void actionPerformed(ActionEvent e)
157
+ {
158
+ Display.launchBrowser(url.toString());
159
+ }
160
+ });
161
+ popup.add(go);
162
+ urlItems.add(go);
163
+ }
164
+ }
165
+ }
166
+
167
+ popup.show(e.getComponent(), p.x, p.y);
168
+ }
169
+ }
170
+ }
171
+ });
172
+ }
173
+
174
+ public RoughTime getTime(Point p)
175
+ {
176
+ return null;
177
+ }
178
+
179
+ public TFModel getModel()
180
+ {
181
+ return model;
182
+ }
183
+
184
+ @Override
185
+ public void setBounds(int x, int y, int w, int h)
186
+ {
187
+ super.setBounds(x, y, w, h);
188
+ if (w > 0 && h > 0)
189
+ {
190
+ if (graphics != null)
191
+ {
192
+ graphics.dispose();
193
+ }
194
+ buffer = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
195
+ graphics = (Graphics2D) buffer.getGraphics();
196
+ drawVisualization();
197
+ repaint();
198
+ }
199
+
200
+ }
201
+
202
+ void drawVisualization()
203
+ {
204
+ drawVisualization(graphics);
205
+ }
206
+
207
+ protected abstract void drawVisualization(Graphics2D g);
208
+
209
+ protected boolean paintOnTop(Graphics2D g, int w, int h)
210
+ {
211
+ return false;
212
+ }
213
+
214
+ protected Mouseover find(Point p)
215
+ {
216
+ for (Mouseover o : objectLocations)
217
+ {
218
+ if (o.contains(mouse))
219
+ {
220
+ return o;
221
+ }
222
+ }
223
+ return null;
224
+ }
225
+
226
+ public final void paintComponent(Graphics g1)
227
+ {
228
+ Graphics2D g = (Graphics2D) g1;
229
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
230
+ g.drawImage(buffer, 0, 0, null);
231
+
232
+ int w = getSize().width, h = getSize().height;
233
+ if (paintOnTop(g, w, h))
234
+ {
235
+ return;
236
+ }
237
+
238
+ Mouseover highlight = find(mouse);
239
+ if (highlight != null)
240
+ {
241
+ highlight.draw(g, w, h, getModel().getDisplay());
242
+ }
243
+ }
244
+}
timeflow/views/BarGraphView.java
....@@ -0,0 +1,363 @@
1
+package timeflow.views;
2
+
3
+import timeflow.model.*;
4
+import timeflow.views.ListView.LinkIt;
5
+import timeflow.data.db.*;
6
+import timeflow.data.time.*;
7
+
8
+import javax.swing.*;
9
+
10
+import timeflow.util.*;
11
+
12
+import java.awt.*;
13
+import java.awt.event.*;
14
+import java.util.*;
15
+
16
+public class BarGraphView extends AbstractView
17
+{
18
+
19
+ BarGraph graph = new BarGraph();
20
+ JPanel controls;
21
+ ArrayList<BarData> bars;
22
+
23
+ enum Aggregate
24
+ {
25
+
26
+ TOTAL, AVERAGE, COUNT
27
+ };
28
+ Aggregate agg;
29
+ JComboBox splitFieldChoice, numFieldChoice;
30
+
31
+ public BarGraphView(TFModel model)
32
+ {
33
+ super(model);
34
+
35
+ setLayout(new BorderLayout());
36
+ controls = new JPanel();
37
+ add(controls, BorderLayout.NORTH);
38
+ controls.setLayout(null);
39
+ controls.setBackground(Color.white);
40
+
41
+ JScrollPane scrollPane = new JScrollPane(graph);
42
+ add(scrollPane, BorderLayout.CENTER);
43
+
44
+ makeTop();
45
+ }
46
+
47
+ protected JComponent _getControls()
48
+ {
49
+ return controls;
50
+ }
51
+
52
+ void makeTop()
53
+ {
54
+ int x = 10, y = 10;
55
+ int ch = 25, pad = 5, cw = 160;
56
+
57
+ controls.removeAll();
58
+ TFModel model = getModel();
59
+ if (model.getDB() == null || model.getDB().size() == 0)
60
+ {
61
+ JLabel empty = new JLabel("Empty database");
62
+ controls.add(empty);
63
+ empty.setBounds(x, y, cw, ch);
64
+ return;
65
+ }
66
+
67
+ JLabel top = new JLabel("For each value of");
68
+ controls.add(top);
69
+ top.setBounds(x, y, cw, ch);
70
+ y += ch + pad;
71
+
72
+ splitFieldChoice = new JComboBox();
73
+ String splitSelection = null;
74
+ for (Field f : DBUtils.categoryFields(model.getDB()))
75
+ {
76
+ splitFieldChoice.addItem(f.getName());
77
+ if (f == graph.splitField)
78
+ {
79
+ splitSelection = f.getName();
80
+ }
81
+ }
82
+ controls.add(splitFieldChoice);
83
+ splitFieldChoice.setBounds(x, y, cw, ch);
84
+ y += ch + 3 * pad;
85
+
86
+ if (splitSelection != null)
87
+ {
88
+ splitFieldChoice.setSelectedItem(splitSelection);
89
+ } else if (getModel().getColorField() != null)
90
+ {
91
+ splitFieldChoice.setSelectedItem(getModel().getColorField().getName());
92
+ }
93
+ splitFieldChoice.addActionListener(new ActionListener()
94
+ {
95
+
96
+ @Override
97
+ public void actionPerformed(ActionEvent e)
98
+ {
99
+ graph.redo();
100
+ }
101
+ });
102
+
103
+ JLabel showLabel = new JLabel("show");
104
+ controls.add(showLabel);
105
+ showLabel.setBounds(x, y, cw, ch);
106
+ y += ch + pad;
107
+
108
+ numFieldChoice = new JComboBox();
109
+ numFieldChoice.addItem("Number of events");
110
+ final ArrayList<Field> valueFields = new ArrayList<Field>();
111
+ for (Field f : model.getDB().getFields(Double.class))
112
+ {
113
+ numFieldChoice.addItem("Total: " + f.getName());
114
+ numFieldChoice.addItem("Average: " + f.getName());
115
+ valueFields.add(f);
116
+ }
117
+ controls.add(numFieldChoice);
118
+ numFieldChoice.setBounds(x, y, cw, ch);
119
+
120
+ boolean chosen = false;
121
+ for (int i = 0; i < numFieldChoice.getItemCount(); i++)
122
+ {
123
+ if (numFieldChoice.getItemAt(i).equals(graph.lastValueMenuChoice))
124
+ {
125
+ numFieldChoice.setSelectedIndex(i);
126
+ chosen = true;
127
+ }
128
+ }
129
+ if (!chosen)
130
+ {
131
+ Field size = getModel().getDB().getField(VirtualField.SIZE);
132
+ if (size != null)
133
+ {
134
+ numFieldChoice.setSelectedItem("Total: " + size.getName());
135
+ }
136
+ }
137
+ numFieldChoice.addActionListener(new ActionListener()
138
+ {
139
+
140
+ @Override
141
+ public void actionPerformed(ActionEvent e)
142
+ {
143
+
144
+ graph.redo();
145
+ }
146
+ });
147
+ revalidate();
148
+ repaint();
149
+ }
150
+
151
+ void reset()
152
+ {
153
+ makeTop();
154
+ graph.redo();
155
+ revalidate();
156
+ repaint();
157
+ }
158
+
159
+ @Override
160
+ protected void _note(TFEvent e)
161
+ {
162
+ if (e.affectsSchema())
163
+ {
164
+ reset();
165
+ } else
166
+ {
167
+ graph.redo();
168
+ }
169
+ repaint();
170
+ }
171
+
172
+ @Override
173
+ public String getName()
174
+ {
175
+ return "Bar Graph";
176
+ }
177
+
178
+ @Override
179
+ protected void onscreen(boolean majorChange)
180
+ {
181
+ reset();
182
+ }
183
+
184
+ class BarData
185
+ {
186
+
187
+ Object thing;
188
+ double num;
189
+
190
+ BarData(Object thing, double num)
191
+ {
192
+ this.thing = thing;
193
+ this.num = num;
194
+ }
195
+ }
196
+
197
+ class BarGraph extends JPanel
198
+ {
199
+
200
+ int numVals = 0;
201
+ int rowHeight = 30;
202
+ int barHeight = 20;
203
+ int labelX = 10, barLeft = 300, barRight;
204
+ int topY = 45;
205
+ int numX = 210;
206
+ Field splitField, valueField;
207
+ String lastValueMenuChoice;
208
+ double min, max;
209
+
210
+ void redo()
211
+ {
212
+ bars = new ArrayList<BarData>();
213
+ splitField = getModel().getDB().getField((String) splitFieldChoice.getSelectedItem());
214
+ if (splitField != null)
215
+ {
216
+ int n = numFieldChoice.getSelectedIndex();
217
+
218
+ if (n == 0)
219
+ {
220
+ agg = Aggregate.COUNT;
221
+ } else
222
+ {
223
+ agg = n % 2 == 1 ? Aggregate.TOTAL : Aggregate.AVERAGE;
224
+ }
225
+
226
+ if (agg == Aggregate.COUNT)
227
+ {
228
+ Bag<String> bag = DBUtils.countValues(getModel().getActs(), splitField);
229
+ for (String s : bag.list())
230
+ {
231
+ bars.add(new BarData(s, bag.num(s)));
232
+ }
233
+ } else
234
+ {
235
+ lastValueMenuChoice = (String) numFieldChoice.getSelectedItem();
236
+ int colon = lastValueMenuChoice.indexOf(':');
237
+ valueField = getModel().getDB().getField(lastValueMenuChoice.substring(colon + 2));
238
+ DoubleBag<String> bag = new DoubleBag<String>();
239
+ for (Act a : getModel().getActs())
240
+ {
241
+ if (splitField.getType() == String.class)
242
+ {
243
+ bag.add(a.getString(splitField), a.getValue(valueField));
244
+ } else
245
+ {
246
+ String[] tags = a.getTextList(splitField);
247
+ for (String tag : tags)
248
+ {
249
+ bag.add(tag, a.getValue(valueField));
250
+ }
251
+ }
252
+ }
253
+ boolean isSum = agg == Aggregate.TOTAL;
254
+ for (String s : bag.list(isSum))
255
+ {
256
+ bars.add(new BarData(s, isSum ? bag.num(s) : bag.average(s)));
257
+ }
258
+ }
259
+ }
260
+ revalidate();
261
+ repaint();
262
+ }
263
+
264
+ public void paintComponent(Graphics g1)
265
+ {
266
+ Graphics2D g = (Graphics2D) g1;
267
+ int w = getSize().width, h = getSize().height;
268
+ g.setColor(Color.white);
269
+ g.fillRect(0, 0, w, h);
270
+ TFModel model = getModel();
271
+ Display display = model.getDisplay();
272
+
273
+ if (display.emptyMessage(g, model))
274
+ {
275
+ return;
276
+ }
277
+
278
+ if (bars == null)
279
+ {
280
+ return;
281
+ }
282
+
283
+ if (bars.size() == 0)
284
+ {
285
+ g.setColor(Color.gray);
286
+ g.drawString("(No data selected.)", 10, 30);
287
+ return;
288
+ }
289
+
290
+ int n = bars.size();
291
+ max = bars.get(0).num;
292
+ min = Math.min(0, bars.get(n - 1).num);
293
+
294
+
295
+ barRight = w - 30;
296
+
297
+ int zero = scaleX(0);
298
+ boolean isInColor = (splitField != null && getModel().getColorField() == splitField);
299
+
300
+ // draw header
301
+ int titleY = topY - 15;
302
+ g.setColor(Color.black);
303
+ g.setFont(display.big());
304
+ g.drawString(splitField.getName().toUpperCase(), labelX, titleY);
305
+ String aggLabel = agg.toString();
306
+ if (agg != Aggregate.COUNT)
307
+ {
308
+ aggLabel += " " + valueField.getName().toUpperCase();
309
+ }
310
+ g.drawString(aggLabel, barLeft, titleY);
311
+ g.setFont(display.plain());
312
+ FontMetrics fm = display.plainFontMetrics();
313
+ // draw bars
314
+
315
+ for (int i = 0; i < n; i++)
316
+ {
317
+ int y = topY + i * rowHeight;
318
+ int ty = y + barHeight;
319
+ BarData data = bars.get(i);
320
+
321
+ Color c = null;
322
+
323
+ g.setColor(Color.gray);
324
+
325
+ // label value
326
+ boolean missing = data.thing == null || (data.thing.toString().length() == 0);
327
+ String label = missing ? "[missing]"
328
+ : display.format(display.toString(data.thing), 25, false);
329
+ if (isInColor)
330
+ {
331
+ g.setColor(missing ? Color.gray : display.makeColor(data.thing.toString()));
332
+ display.makeColor(label);
333
+ }
334
+ g.drawString(label, labelX, ty);
335
+
336
+ // label number
337
+ String numLabel = display.format(data.num);
338
+ g.drawString(numLabel, numX + 70 - fm.stringWidth(numLabel), ty);
339
+
340
+ // draw bar.
341
+ g.setColor(missing ? Color.lightGray : (isInColor ? c : Display.barColor));
342
+ int x = scaleX(data.num);
343
+ int a = Math.min(x, zero);
344
+ int b = Math.max(x, zero);
345
+ g.fillRect(a, y + 5, b - a, barHeight);
346
+ }
347
+ }
348
+
349
+ int scaleX(double x)
350
+ {
351
+ if (max == min)
352
+ {
353
+ return barLeft;
354
+ }
355
+ return (int) (barLeft + (barRight - barLeft) * (x - min) / (max - min));
356
+ }
357
+
358
+ public Dimension getPreferredSize()
359
+ {
360
+ return new Dimension(400, 100 + rowHeight * (bars == null ? 0 : bars.size()));
361
+ }
362
+ }
363
+}
timeflow/views/CalendarView.java
....@@ -0,0 +1,304 @@
1
+package timeflow.views;
2
+
3
+import timeflow.app.ui.ComponentCluster;
4
+import timeflow.data.db.*;
5
+import timeflow.data.time.Interval;
6
+import timeflow.data.time.RoughTime;
7
+import timeflow.model.*;
8
+import timeflow.vis.*;
9
+import timeflow.vis.calendar.*;
10
+
11
+import javax.swing.*;
12
+import java.awt.*;
13
+import java.awt.event.ActionEvent;
14
+import java.awt.event.ActionListener;
15
+import java.awt.event.AdjustmentEvent;
16
+import java.awt.event.AdjustmentListener;
17
+import java.awt.image.BufferedImage;
18
+import java.util.ArrayList;
19
+import java.util.Date;
20
+
21
+public class CalendarView extends AbstractView {
22
+
23
+ CalendarPanel calendarPanel;
24
+ ScrollingCalendar scroller;
25
+ CalendarVisuals visuals;
26
+ ActDB lastDB;
27
+ JPanel controls;
28
+
29
+ @Override
30
+ public JComponent _getControls()
31
+ {
32
+ return controls;
33
+ }
34
+
35
+ public CalendarView(TFModel model)
36
+ {
37
+ super(model);
38
+ calendarPanel=new CalendarPanel(model);
39
+ scroller=new ScrollingCalendar();
40
+ setLayout(new GridLayout(1,1));
41
+ add(scroller);
42
+
43
+ controls=new JPanel();
44
+ controls.setLayout(new BorderLayout());
45
+
46
+ ComponentCluster units=new ComponentCluster("Grid");
47
+ controls.add(units, BorderLayout.NORTH);
48
+
49
+ ButtonGroup unitGroup=new ButtonGroup();
50
+
51
+ JRadioButton days=new JRadioButton(new ImageIcon("images/button_days.gif"),true);
52
+ days.setSelectedIcon(new ImageIcon("images/button_days_selected.gif"));
53
+ units.addContent(days);
54
+ days.addActionListener(new LayoutSetter(CalendarVisuals.Layout.DAY));
55
+ unitGroup.add(days);
56
+
57
+ JRadioButton months=new JRadioButton(new ImageIcon("images/button_months.gif"),false);
58
+ months.setSelectedIcon(new ImageIcon("images/button_months_selected.gif"));
59
+ units.addContent(months);
60
+ months.addActionListener(new LayoutSetter(CalendarVisuals.Layout.MONTH));
61
+ unitGroup.add(months);
62
+
63
+ JRadioButton years=new JRadioButton(new ImageIcon("images/button_years.gif"),false);
64
+ years.setSelectedIcon(new ImageIcon("images/button_years_selected.gif"));
65
+ units.addContent(years);
66
+ years.addActionListener(new LayoutSetter(CalendarVisuals.Layout.YEAR));
67
+ unitGroup.add(years);
68
+
69
+
70
+ ComponentCluster showCluster=new ComponentCluster("Show");
71
+ controls.add(showCluster, BorderLayout.CENTER);
72
+
73
+ ButtonGroup group=new ButtonGroup();
74
+ JRadioButton icon=new JRadioButton(new ImageIcon("images/button_dots.gif"),true);
75
+ icon.setSelectedIcon(new ImageIcon("images/button_dots_selected.gif"));
76
+ showCluster.addContent(icon);
77
+ icon.addActionListener(new DrawStyleSetter(CalendarVisuals.DrawStyle.ICON));
78
+ group.add(icon);
79
+
80
+ JRadioButton label=new JRadioButton(new ImageIcon("images/button_labels.gif"),false);
81
+ label.setSelectedIcon(new ImageIcon("images/button_labels_selected.gif"));
82
+ showCluster.addContent(label);
83
+ label.addActionListener(new DrawStyleSetter(CalendarVisuals.DrawStyle.LABEL));
84
+ group.add(label);
85
+
86
+ ComponentCluster layout=new ComponentCluster("Layout");
87
+ controls.add(layout, BorderLayout.SOUTH);
88
+
89
+ ButtonGroup layoutGroup=new ButtonGroup();
90
+ JRadioButton loose=new JRadioButton(new ImageIcon("images/button_expanded.gif"),true);
91
+ loose.setSelectedIcon(new ImageIcon("images/button_expanded_selected.gif"));
92
+ layout.addContent(loose);
93
+ loose.addActionListener(new FitStyleSetter(CalendarVisuals.FitStyle.LOOSE));
94
+ layoutGroup.add(loose);
95
+
96
+ JRadioButton tight=new JRadioButton(new ImageIcon("images/button_compressed.gif"),false);
97
+ tight.setSelectedIcon(new ImageIcon("images/button_compressed_selected.gif"));
98
+ layout.addContent(tight);
99
+ tight.addActionListener(new FitStyleSetter(CalendarVisuals.FitStyle.TIGHT));
100
+ layoutGroup.add(tight);
101
+ }
102
+
103
+ class LayoutSetter implements ActionListener
104
+ {
105
+ CalendarVisuals.Layout layout;
106
+ LayoutSetter(CalendarVisuals.Layout layout)
107
+ {
108
+ this.layout=layout;
109
+ }
110
+ @Override
111
+ public void actionPerformed(ActionEvent e) {
112
+ setLayoutStyle(layout);
113
+ }
114
+ }
115
+
116
+ class DrawStyleSetter implements ActionListener
117
+ {
118
+ CalendarVisuals.DrawStyle style;
119
+ DrawStyleSetter(CalendarVisuals.DrawStyle style)
120
+ {
121
+ this.style=style;
122
+ }
123
+ @Override
124
+ public void actionPerformed(ActionEvent e) {
125
+ setDrawStyle(style);
126
+ }
127
+ }
128
+
129
+ class FitStyleSetter implements ActionListener
130
+ {
131
+ CalendarVisuals.FitStyle style;
132
+ FitStyleSetter(CalendarVisuals.FitStyle style)
133
+ {
134
+ this.style=style;
135
+ }
136
+ @Override
137
+ public void actionPerformed(ActionEvent e) {
138
+ setFitStyle(style);
139
+ }
140
+ }
141
+
142
+ @Override
143
+ public String getName() {
144
+ return "Calendar";
145
+ }
146
+
147
+ private void redraw(boolean fresh)
148
+ {
149
+ visuals.makeGrid(fresh);
150
+ calendarPanel.drawVisualization();
151
+ repaint();
152
+ }
153
+
154
+ void setLayoutStyle(CalendarVisuals.Layout layout)
155
+ {
156
+ visuals.setLayoutStyle(layout);
157
+ calendarPanel.drawVisualization();
158
+ revalidate();
159
+ repaint();
160
+ }
161
+
162
+ void setDrawStyle(CalendarVisuals.DrawStyle style)
163
+ {
164
+ visuals.setDrawStyle(style);
165
+ calendarPanel.drawVisualization();
166
+ revalidate();
167
+ repaint();
168
+ }
169
+
170
+ void setFitStyle(CalendarVisuals.FitStyle style)
171
+ {
172
+ visuals.setFitStyle(style);
173
+ calendarPanel.drawVisualization();
174
+ revalidate();
175
+ repaint();
176
+ }
177
+
178
+ @Override
179
+ protected void onscreen(boolean majorChange)
180
+ {
181
+ visuals.initAllButGrid();
182
+ scroller.calibrate(true);
183
+ revalidate();
184
+ ActDB db=getModel().getDB();
185
+ redraw(majorChange);
186
+ scroller.calibrate(majorChange);
187
+ lastDB=db;
188
+ }
189
+
190
+ @Override
191
+ protected void _note(TFEvent e) {
192
+ int oldHeight=calendarPanel.getPreferredSize().height;
193
+ visuals.note(e);
194
+
195
+ calendarPanel.drawVisualization();
196
+ calendarPanel.repaint();
197
+ if (e.affectsData() || oldHeight!=calendarPanel.getPreferredSize().height)
198
+ {
199
+ SwingUtilities.invokeLater(new Runnable() {
200
+ public void run() {scroller.calibrate(false); revalidate();}
201
+ });
202
+ }
203
+ revalidate();
204
+ }
205
+
206
+ public void setBounds(int x, int y, int w, int h)
207
+ {
208
+ super.setBounds(x,y,w,h);
209
+ if (visuals==null || visuals.grid==null)
210
+ return;
211
+ calendarPanel.drawVisualization();
212
+ calendarPanel.repaint();
213
+ }
214
+
215
+ class ScrollingCalendar extends JPanel
216
+ {
217
+ JScrollBar bar;
218
+ public ScrollingCalendar()
219
+ {
220
+ setLayout(new BorderLayout());
221
+ add(calendarPanel, BorderLayout.CENTER);
222
+ bar=new JScrollBar(JScrollBar.VERTICAL);
223
+ add(bar, BorderLayout.EAST);
224
+ bar.addAdjustmentListener(new AdjustmentListener() {
225
+ @Override
226
+ public void adjustmentValueChanged(AdjustmentEvent e) {
227
+ visuals.grid.setDY(bar.getValue());
228
+
229
+ // set time in model.
230
+ RoughTime startTime=visuals.grid.getFirstDrawnTime();
231
+ Interval viewInterval=getModel().getViewInterval();
232
+ if (viewInterval!=null)
233
+ {
234
+ viewInterval.translateTo(startTime.getTime());
235
+ }
236
+
237
+ calendarPanel.drawVisualization();
238
+ calendarPanel.repaint();
239
+ }
240
+ });
241
+ }
242
+
243
+ public void setBounds(int x, int y, int w, int h)
244
+ {
245
+ if (x==getX() && y==getY() && w==getWidth() && h==getHeight())
246
+ return;
247
+ super.setBounds(x,y,w,h);
248
+ calibrate(false);
249
+ }
250
+
251
+ void calibrate(boolean forceValue)
252
+ {
253
+ if (visuals==null || visuals.grid==null)
254
+ return;
255
+ int height=getSize().height;
256
+ int desired=visuals.grid.getCalendarHeight();
257
+ bar.setVisible(desired>height);
258
+ if (desired>height)
259
+ {
260
+ bar.setMinimum(0);
261
+ bar.setMaximum(desired);
262
+ bar.setVisibleAmount(height);
263
+ Interval view=getModel().getViewInterval();
264
+ if (view!=null && forceValue)
265
+ {
266
+ double s=visuals.grid.getScrollFraction();
267
+ double maxFraction=(desired-height)/(double)desired;
268
+ int value=(int)((s/maxFraction)*desired);
269
+ bar.setValue(value);
270
+ }
271
+
272
+ }
273
+ }
274
+ }
275
+
276
+ class CalendarPanel extends AbstractVisualizationView
277
+ {
278
+
279
+ CalendarPanel(TFModel model)
280
+ {
281
+ super(model);
282
+ setBackground(Color.white);
283
+ visuals=new CalendarVisuals(getModel());
284
+ }
285
+
286
+ public RoughTime getTime(Point p)
287
+ {
288
+ return visuals.grid.getTime(p.x, p.y);
289
+ }
290
+
291
+ protected void drawVisualization(Graphics2D g)
292
+ {
293
+ g.setBackground(Color.white);
294
+ g.fillRect(0,0,getSize().width, getSize().height);
295
+
296
+ getModel().getDisplay().emptyMessage(g, model);
297
+ if (model.getDB()==null)
298
+ return;
299
+ visuals.setBounds(0,0,getSize().width,getSize().height);
300
+ objectLocations=new ArrayList<Mouseover>();
301
+ visuals.render(g, objectLocations);
302
+ }
303
+ }
304
+}
timeflow/views/DescriptionView.java
....@@ -0,0 +1,71 @@
1
+package timeflow.views;
2
+
3
+import timeflow.model.*;
4
+import timeflow.util.Pad;
5
+import timeflow.data.db.*;
6
+
7
+import java.awt.*;
8
+import java.awt.event.*;
9
+
10
+import javax.swing.*;
11
+
12
+public class DescriptionView extends AbstractView {
13
+
14
+ JTextArea content;
15
+ JComponent controls;
16
+
17
+ public DescriptionView(TFModel model) {
18
+ super(model);
19
+ setLayout(new BorderLayout());
20
+ JPanel left=new Pad(5,5);
21
+ left.setBackground(Color.white);
22
+ add(left, BorderLayout.WEST);
23
+ JPanel right=new Pad(5,5);
24
+ right.setBackground(Color.white);
25
+ add(right, BorderLayout.EAST);
26
+ JPanel top=new JPanel();
27
+ add(top, BorderLayout.NORTH);
28
+ top.setLayout(new FlowLayout(FlowLayout.LEFT));
29
+ top.add(new JLabel("Notes & Comments on This Data:"));
30
+ content=new JTextArea();
31
+ content.setLineWrap(true);
32
+ content.setWrapStyleWord(true);
33
+ add(content, BorderLayout.CENTER);
34
+ content.addKeyListener(new KeyAdapter() {
35
+ @Override
36
+ public void keyReleased(KeyEvent e) {
37
+ getModel().getDB().setDescription(content.getText());
38
+ getModel().noteNewDescription(DescriptionView.this);
39
+ }});
40
+ controls=new HtmlControls("Each TimeFlow data set<br> comes with a free-form <br> "+
41
+ "description area. <p>This is a good place to write<br> notes "+
42
+ "about sources, how the data<br>was cleaned, etc.");
43
+ }
44
+
45
+ @Override
46
+ public JComponent _getControls()
47
+ {
48
+ return controls;
49
+ }
50
+
51
+ @Override
52
+ protected void _note(TFEvent e) {
53
+ if (e.type==TFEvent.Type.DESCRIPTION_CHANGE || e.type==TFEvent.Type.DATABASE_CHANGE)
54
+ {
55
+ content.setText(getModel().getDB().getDescription());
56
+ repaint();
57
+ }
58
+ }
59
+
60
+ @Override
61
+ public String getName() {
62
+ return "Notes";
63
+ }
64
+
65
+ @Override
66
+ protected void onscreen(boolean majorChange) {
67
+ ActDB db=getModel().getDB();
68
+ content.setText(db.getDescription());
69
+ content.requestFocus();
70
+ }
71
+}
timeflow/views/HtmlControls.java
....@@ -0,0 +1,21 @@
1
+package timeflow.views;
2
+
3
+import java.awt.Color;
4
+import java.awt.GridLayout;
5
+
6
+import javax.swing.*;
7
+
8
+import timeflow.app.ui.HtmlDisplay;
9
+
10
+public class HtmlControls extends JPanel {
11
+
12
+ public HtmlControls(String text)
13
+ {
14
+ JEditorPane html=HtmlDisplay.create();
15
+ html.setText(text);
16
+ setBackground(Color.white);
17
+ setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
18
+ setLayout(new GridLayout(1,1));
19
+ add(html);
20
+ }
21
+}
timeflow/views/IntroView.java
....@@ -0,0 +1,105 @@
1
+package timeflow.views;
2
+
3
+import timeflow.app.ui.EditRecordPanel;
4
+import timeflow.data.analysis.*;
5
+import timeflow.data.db.*;
6
+import timeflow.data.time.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+import timeflow.model.*;
9
+import timeflow.views.*;
10
+import timeflow.views.ListView.LinkIt;
11
+
12
+import java.awt.*;
13
+import java.io.File;
14
+import java.net.URL;
15
+import java.util.Date;
16
+
17
+import javax.imageio.ImageIO;
18
+import javax.swing.JComponent;
19
+import javax.swing.JEditorPane;
20
+import javax.swing.JScrollPane;
21
+import javax.swing.UIManager;
22
+import javax.swing.event.HyperlinkEvent;
23
+import javax.swing.event.HyperlinkListener;
24
+import javax.swing.table.TableModel;
25
+import javax.swing.text.html.HTMLDocument;
26
+import javax.swing.text.html.StyleSheet;
27
+
28
+import timeflow.util.*;
29
+
30
+public class IntroView extends AbstractView
31
+{
32
+
33
+ private JComponent controls;
34
+ Image image;
35
+ Image repeat;
36
+
37
+ public IntroView(TFModel model)
38
+ {
39
+ super(model);
40
+ setBackground(Color.white);
41
+ try
42
+ {
43
+ image = ImageIO.read(new File("images/intro.gif"));
44
+ repeat = ImageIO.read(new File("images/repeat.gif"));
45
+ } catch (Exception e)
46
+ {
47
+ System.out.println("Couldn't load images.");
48
+ e.printStackTrace(System.out);
49
+ }
50
+ makeHtml();
51
+ }
52
+
53
+ public void paintComponent(Graphics g)
54
+ {
55
+ g.setColor(Color.white);
56
+ int w = getSize().width, h = getSize().height;
57
+ g.fillRect(0, 0, w, h);
58
+ // draw image and extensible background, so it looks cool on a big screen.
59
+ if (image != null && repeat != null)
60
+ {
61
+ int ih = image.getHeight(null);
62
+ int iw = image.getWidth(null);
63
+ int rw = repeat.getWidth(null);
64
+ g.drawImage(image, 0, 0, null);
65
+ for (int x = iw; x < w; x += rw)
66
+ {
67
+ g.drawImage(repeat, x, 0, null);
68
+ }
69
+ }
70
+ }
71
+
72
+ void makeHtml()
73
+ {
74
+ try
75
+ {
76
+ String sidebar = IO.read("settings/sidebar.html");
77
+ controls = new HtmlControls(sidebar);
78
+ } catch (Exception e)
79
+ {
80
+ e.printStackTrace(System.out);
81
+ }
82
+ }
83
+
84
+ @Override
85
+ public JComponent _getControls()
86
+ {
87
+ return controls;
88
+ }
89
+
90
+ @Override
91
+ protected void onscreen(boolean majorChange)
92
+ {
93
+ }
94
+
95
+ protected void _note(TFEvent e)
96
+ {
97
+ // do nothing.
98
+ }
99
+
100
+ @Override
101
+ public String getName()
102
+ {
103
+ return "About";
104
+ }
105
+}
timeflow/views/ListView.java
....@@ -0,0 +1,266 @@
1
+package timeflow.views;
2
+
3
+import timeflow.app.ui.EditRecordPanel;
4
+import timeflow.app.ui.HtmlDisplay;
5
+import timeflow.data.db.*;
6
+import timeflow.format.file.HtmlFormat;
7
+import timeflow.model.*;
8
+
9
+import java.awt.*;
10
+import java.awt.event.*;
11
+
12
+import javax.swing.*;
13
+import javax.swing.event.*;
14
+import javax.swing.table.*;
15
+import javax.swing.text.html.*;
16
+
17
+import timeflow.util.*;
18
+
19
+import java.net.URI;
20
+import java.net.URL;
21
+import java.util.*;
22
+
23
+public class ListView extends AbstractView {
24
+
25
+ private JEditorPane listDisplay;
26
+ private JComboBox sortMenu=new JComboBox();
27
+ private ActComparator sort;//=ActComparator.byTime();
28
+ private int maxPerPage=50;
29
+ private int pageStart=0;
30
+ private int lastSize=0;
31
+ private ActList acts;
32
+ private Field sortField;
33
+
34
+ private JLabel pageLabel=new JLabel("Page", JLabel.LEFT);
35
+ private JComboBox pageMenu=new JComboBox();
36
+ private boolean changing=false;
37
+
38
+ private JPanel controls;
39
+
40
+ public ListView(TFModel model)
41
+ {
42
+ super(model);
43
+
44
+ listDisplay=HtmlDisplay.create();
45
+ listDisplay.addHyperlinkListener(new LinkIt());
46
+ JScrollPane scrollPane = new JScrollPane(listDisplay);
47
+ setLayout(new BorderLayout());
48
+ add(scrollPane, BorderLayout.CENTER);
49
+
50
+
51
+ controls=new JPanel();
52
+ controls.setLayout(null);
53
+ controls.setBackground(Color.white);
54
+
55
+ int x=10, y=10;
56
+ int ch=25, pad=5, cw=160;
57
+ JLabel sortLabel=new JLabel("Sort Order", JLabel.LEFT);
58
+ controls.add(sortLabel);
59
+ sortLabel.setBounds(x,y,cw,ch);
60
+ y+=ch+pad;
61
+
62
+ controls.add(sortMenu);
63
+ sortMenu.setBounds(x,y,cw,ch);
64
+ y+=ch+3*pad;
65
+
66
+ controls.add(pageLabel);
67
+ pageLabel.setBounds(x,y,cw,ch);
68
+ y+=ch+pad;
69
+ controls.add(pageMenu);
70
+ pageMenu.setBounds(x,y,cw,ch);
71
+
72
+ showPageMenu(false);
73
+ pageMenu.addActionListener(pageListener);
74
+ sortMenu.addActionListener(sortListener);
75
+ }
76
+
77
+ protected JComponent _getControls()
78
+ {
79
+ return controls;
80
+ }
81
+
82
+ ActionListener sortListener=new ActionListener() {
83
+ @Override
84
+ public void actionPerformed(ActionEvent e) {
85
+ if (changing || sortMenu.getItemCount()<=0) // this means the action was fired after all items removed.
86
+ return;
87
+ sortField=getModel().getDB().getField((String)sortMenu.getSelectedItem());
88
+ sort=sortField==null ? null : ActComparator.by(sortField);
89
+ setToFirstPage();
90
+ makeList();
91
+ }};
92
+
93
+ ActionListener pageListener=new ActionListener() {
94
+ @Override
95
+ public void actionPerformed(ActionEvent e) {
96
+ if (changing)
97
+ return;
98
+ pageStart=maxPerPage*pageMenu.getSelectedIndex();
99
+ System.out.println(e.getActionCommand());
100
+ makeList();
101
+ }};
102
+
103
+ @Override
104
+ protected void onscreen(boolean majorChange)
105
+ {
106
+ _note(null);
107
+ }
108
+
109
+ public void _note(TFEvent e) {
110
+ changing=true;
111
+ if (e==null || e.affectsSchema() || e.affectsRowSet())
112
+ {
113
+ sortMenu.removeActionListener(sortListener);
114
+ sortMenu.removeAllItems();
115
+ pageStart=0;
116
+ java.util.List<Field> fields=getModel().getDB().getFields();
117
+ Field firstField=null;
118
+ if (fields.size()>0)
119
+ firstField=fields.get(0);
120
+ for (Field f: fields)
121
+ {
122
+ sortMenu.addItem(f.getName());
123
+ }
124
+ sortField=getModel().getDB().getField(VirtualField.START);
125
+ if (sortField!=null)
126
+ sortMenu.setSelectedItem(sortField.getName());
127
+ else
128
+ sortField=firstField;
129
+ sortMenu.addActionListener(sortListener);
130
+ sort=null;
131
+ }
132
+ if (e!=null && e.affectsData())
133
+ {
134
+ setToFirstPage();
135
+ }
136
+ changing=false;
137
+ makeList();
138
+ }
139
+
140
+ private void setToFirstPage()
141
+ {
142
+ pageStart=0;
143
+ if (pageMenu.isVisible())
144
+ {
145
+ pageMenu.removeActionListener(pageListener);
146
+ pageMenu.setSelectedIndex(0);
147
+ pageMenu.addActionListener(pageListener);
148
+ }
149
+ }
150
+
151
+ void showPageMenu(boolean visible)
152
+ {
153
+ pageLabel.setVisible(visible);
154
+ pageMenu.setVisible(visible);
155
+ if (visible)
156
+ {
157
+ pageMenu.removeActionListener(pageListener);
158
+ pageMenu.setSelectedIndex(pageStart/maxPerPage);
159
+ pageMenu.addActionListener(pageListener);
160
+ }
161
+ }
162
+
163
+
164
+ void makeList()
165
+ {
166
+ HtmlFormat html=new HtmlFormat();
167
+ html.setModel(getModel());
168
+ StringBuffer page=new StringBuffer();
169
+
170
+ page.append(html.makeHeader());
171
+
172
+
173
+ ActList as=getModel().getActs();
174
+ if (as==null || as.size()==0 && getModel().getDB().size()==0)
175
+ {
176
+ page.append("<tr><td><h1><font color=#003399>Empty Database</font></h1></td></tr>");
177
+ showPageMenu(false);
178
+ }
179
+ else
180
+ {
181
+
182
+ if (sort==null)
183
+ {
184
+ Field timeField=getModel().getDB().getField(VirtualField.START);
185
+ if (timeField!=null)
186
+ sort=ActComparator.by(timeField);
187
+ }
188
+
189
+ acts=as.copy();
190
+ if (sort!=null)
191
+ Collections.sort(acts, sort);
192
+
193
+ boolean pages=acts.size()>maxPerPage;
194
+ int last=Math.min(acts.size(), pageStart+maxPerPage);
195
+ if (pages)
196
+ {
197
+ int n=acts.size();
198
+ if (lastSize!=n)
199
+ {
200
+ pageMenu.removeActionListener(pageListener);
201
+ pageMenu.removeAllItems();
202
+ for (int i=0; i*maxPerPage<n;i++)
203
+ {
204
+ pageMenu.addItem("Items "+((i*maxPerPage)+1)+" to "+
205
+ Math.min(n, (i+1)*maxPerPage));
206
+ }
207
+ pageMenu.addActionListener(pageListener);
208
+ lastSize=n;
209
+ }
210
+ }
211
+ showPageMenu(pages);
212
+
213
+ page.append("<tr><td><h1><font color=#003399>"+(pages? (pageStart+1)+"-"+(last) +" of ": "")+acts.size()+" Events</font></h1>");
214
+ page.append("<br><br></td></tr>");
215
+
216
+ for (int i=pageStart; i<last; i++)
217
+ {
218
+ Act a=acts.get(i);
219
+ page.append(html.makeItem(a,i));
220
+ }
221
+ }
222
+ page.append(html.makeFooter());
223
+ listDisplay.setText(page.toString());
224
+ listDisplay.setCaretPosition(0);
225
+ repaint();
226
+ }
227
+
228
+
229
+
230
+
231
+ @Override
232
+ public String getName() {
233
+ return "List";
234
+ }
235
+
236
+ static class ArrayRenderer extends DefaultTableCellRenderer {
237
+ public void setValue(Object value) {
238
+ setText(Display.arrayToString((Object[])value));
239
+ }
240
+ }
241
+
242
+ public class LinkIt implements HyperlinkListener
243
+ {
244
+ public void hyperlinkUpdate(HyperlinkEvent e)
245
+ {
246
+ if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
247
+ return;
248
+
249
+ String s=e.getDescription();
250
+ System.out.println(s);
251
+ if (s.length()>0)
252
+ {
253
+ char c=s.charAt(0);
254
+ if (c=='e') // code for "edit"
255
+ {
256
+ int i=Integer.parseInt(s.substring(1));
257
+ EditRecordPanel.edit(getModel(), acts.get(i));
258
+ return;
259
+ }
260
+
261
+ }
262
+ Display.launchBrowser(e.getURL().toString());
263
+
264
+ }
265
+ }
266
+}
timeflow/views/SummaryView.java
....@@ -0,0 +1,181 @@
1
+package timeflow.views;
2
+
3
+import timeflow.app.ui.HtmlDisplay;
4
+import timeflow.data.analysis.*;
5
+import timeflow.data.db.*;
6
+import timeflow.data.time.*;
7
+import timeflow.format.field.FieldFormatCatalog;
8
+import timeflow.model.*;
9
+
10
+
11
+import java.awt.*;
12
+import java.util.Date;
13
+
14
+import javax.swing.*;
15
+
16
+
17
+public class SummaryView extends AbstractView {
18
+
19
+ private JEditorPane analysisDisplay;
20
+ private FieldAnalysis[] fieldAnalyzers=new FieldAnalysis[]
21
+ {
22
+ new MissingValueAnalysis(), new RangeDateAnalysis(),
23
+ new RangeNumberAnalysis(), new FrequencyAnalysis()
24
+ };
25
+ private int numItems;
26
+ private int numFiltered;
27
+ private Interval range;
28
+ private JComponent controls;
29
+
30
+ public SummaryView(TFModel model)
31
+ {
32
+ super(model);
33
+ analysisDisplay=HtmlDisplay.create();
34
+ JScrollPane scrollPane = new JScrollPane(analysisDisplay);
35
+ setLayout(new GridLayout(1,1));
36
+ add(scrollPane);
37
+
38
+ controls=new HtmlControls("This report gives a <br> statistical breakdown<br> "+
39
+ "of your data. <p> Reading the summary often helps<br> you find "+
40
+ "data errors.");
41
+ }
42
+
43
+ @Override
44
+ public JComponent _getControls()
45
+ {
46
+ return controls;
47
+ }
48
+
49
+ void makeHtml()
50
+ {
51
+ Display d=getModel().getDisplay();
52
+ ActDB db=getModel().getDB();
53
+ ActList acts=getModel().getActs();
54
+ StringBuffer page=new StringBuffer();
55
+ page.append("<blockquote>");
56
+ if (getModel().getDB()==null)
57
+ {
58
+ page.append("<h1><font color=#003399>No data loaded.</font></h1>");
59
+ }
60
+ else
61
+ {
62
+ page.append("<BR><BR><BR>File: "+getModel().getDbFile()+"<br>");
63
+ page.append("Source: "+getModel().getDB().getSource()+"<br><br>");
64
+ page.append("Description: "+getModel().getDB().getDescription()+"<br><br>");
65
+ page.append("<br><br>");
66
+ page.append("<table border=0>");
67
+
68
+
69
+ page.append("<tr><td valign=top align=left width=100><b>");
70
+ page.append("<font size=+1>Data</font><br>");
71
+
72
+ page.append("</td><td align=top>");
73
+ append(page, "Total events", ""+numItems);
74
+
75
+ if (numItems>0)
76
+ {
77
+ append(page, "Total selected", ""+numFiltered);
78
+ if (numFiltered>0)
79
+ {
80
+ append(page, "Earliest", new Date(range.start).toString());
81
+ append(page, "Latest", new Date(range.end).toString());
82
+ }
83
+ }
84
+ page.append("<br></td></tr>");
85
+
86
+ page.append("<tr><td valign=top align=left width=100><b>");
87
+ page.append("<font size=+1>Fields</font><br>");
88
+
89
+ page.append("</td><td align=top>");
90
+ for (Field f: getModel().getDB().getFields())
91
+ {
92
+ append(page, f.getName(),FieldFormatCatalog.humanName(f.getType())+fieldLabel(f));
93
+ }
94
+ page.append("<br></td></tr>");
95
+
96
+
97
+ page.append("</table>");
98
+
99
+ if (numFiltered>0)
100
+ {
101
+ page.append("<h1>Statistics (for "+acts.size()+" items)</h1>");
102
+ for (Field field: db.getFields())
103
+ {
104
+ page.append("<h2>"+field.getName()+"</h2>");
105
+ page.append("<ul>");
106
+ for (int i=0; i<fieldAnalyzers.length; i++)
107
+ {
108
+ FieldAnalysis fa=fieldAnalyzers[i];
109
+ if (fa.canHandleType(field.getType()))
110
+ {
111
+ page.append("<li>");
112
+ page.append("<b><font color=#808080>"+fa.getName()+"</font></b><br>");
113
+ fa.perform(acts, field);
114
+ String[] s=fa.getResultDescription();
115
+ for (int j=0; j<s.length; j++)
116
+ {
117
+ page.append(s[j]);
118
+ page.append("<br>");
119
+ }
120
+ page.append("</li>");
121
+ }
122
+ }
123
+ page.append("</ul>");
124
+ }
125
+ }
126
+ }
127
+ page.append("</blockquote>");
128
+ analysisDisplay.setText(page.toString());
129
+ analysisDisplay.setCaretPosition(0);
130
+ }
131
+
132
+ static void append(StringBuffer page, String label, String value)
133
+ {
134
+ page.append("<b><font color=#808080>"+label+"</font></b>&nbsp;&nbsp;&nbsp;&nbsp;");
135
+ page.append(value);
136
+ page.append("<br>");
137
+ }
138
+
139
+ @Override
140
+ protected void onscreen(boolean majorChange)
141
+ {
142
+ _note(null);
143
+ }
144
+
145
+ protected void _note(TFEvent e) {
146
+ recalculate();
147
+ makeHtml();
148
+ repaint();
149
+ }
150
+
151
+ String fieldLabel(Field f)
152
+ {
153
+ StringBuffer b=new StringBuffer("<b>");
154
+ ActDB db=getModel().getDB();
155
+ for (String v: VirtualField.list())
156
+ {
157
+ if (db.getField(v)!=null && db.getField(v).getName().equals(f.getName()))
158
+ b.append(" (Shown in visualization as "+VirtualField.humanName(v)+")");
159
+ }
160
+ b.append("</b>");
161
+ return b.toString();
162
+ }
163
+
164
+ void recalculate()
165
+ {
166
+ ActList acts=getModel().getActs();
167
+ if (acts==null)
168
+ {
169
+ numItems=0;
170
+ return;
171
+ }
172
+ numFiltered=acts.size();
173
+ numItems=getModel().getDB().size();
174
+ range=DBUtils.range(acts, VirtualField.START);
175
+ }
176
+
177
+ @Override
178
+ public String getName() {
179
+ return "Summary";
180
+ }
181
+}
timeflow/views/TableView.java
....@@ -0,0 +1,267 @@
1
+package timeflow.views;
2
+
3
+import timeflow.app.ui.HtmlDisplay;
4
+import timeflow.data.db.*;
5
+import timeflow.data.time.*;
6
+import timeflow.format.field.DateTimeGuesser;
7
+import timeflow.model.*;
8
+
9
+import java.awt.*;
10
+import java.awt.event.ActionEvent;
11
+import java.awt.event.ActionListener;
12
+
13
+import javax.swing.*;
14
+import javax.swing.event.TableModelListener;
15
+import javax.swing.table.*;
16
+import java.util.*;
17
+
18
+public class TableView extends AbstractView {
19
+
20
+ private JTable table=new JTable();
21
+ private int colorColumn=-1, labelColumn=-1;
22
+ private Font font, bold;
23
+ private boolean editable=true;
24
+ private JPanel controls;
25
+
26
+ public TableView(TFModel model)
27
+ {
28
+ super(model);
29
+
30
+ JScrollPane scrollPane = new JScrollPane(table);
31
+ table.setFillsViewportHeight(true);
32
+ table.setAutoCreateRowSorter(true);
33
+ font=model.getDisplay().plain();
34
+ bold=model.getDisplay().bold();
35
+ table.setFont(font);
36
+ final int fh=getFontMetrics(font).getHeight();
37
+ table.setRowHeight(fh+12);
38
+ table.setShowGrid(false);
39
+ table.getTableHeader().setPreferredSize(new Dimension(10, fh+15));
40
+ table.getTableHeader().setFont(font);
41
+ setReorderable(false);
42
+ setLayout(new GridLayout(1,1));
43
+ add(scrollPane);
44
+
45
+ controls=new HtmlControls("Use the table view for<br> a rapid overview<br> "+
46
+ "of your data. <p>You can click<br> on the headers to sort the columns,<br> "+
47
+ "and you can edit data<br> directly in the table cells.");
48
+ }
49
+
50
+ @Override
51
+ public JComponent _getControls()
52
+ {
53
+ return controls;
54
+ }
55
+
56
+ public void setEditable(boolean editable)
57
+ {
58
+ this.editable=editable;
59
+ }
60
+
61
+ public JTable getTable()
62
+ {
63
+ return table;
64
+ }
65
+
66
+ @Override
67
+ public void onscreen(boolean majorChange)
68
+ {
69
+ _note(null);
70
+ }
71
+
72
+ @Override
73
+ protected void _note(TFEvent e) {
74
+ setActs(getModel().getActs());
75
+ }
76
+
77
+ public void setActs(ActList acts)
78
+ {
79
+ TableModel t=new TimelineTableModel(acts);
80
+ table.setModel(t);
81
+ ActTableRenderer r=new ActTableRenderer(acts);
82
+ table.setDefaultRenderer(Object.class, r);
83
+ table.setDefaultRenderer(Double.class, r);
84
+ table.setDefaultEditor(String[].class, new StringArrayEditor());
85
+ table.setDefaultEditor(RoughTime.class, new RoughTimeEditor());
86
+ repaint();
87
+ }
88
+
89
+ @Override
90
+ public String getName() {
91
+ return "Table";
92
+ }
93
+
94
+
95
+ class ActTableRenderer extends DefaultTableCellRenderer {
96
+
97
+ ActList acts;
98
+ Color zebra=new Color(240,240,240);
99
+ boolean color=false;
100
+ Color dataColor;
101
+
102
+ ActTableRenderer(ActList acts)
103
+ {
104
+ this.acts=acts;
105
+ }
106
+
107
+ public void setValue(Object value) {
108
+ if (value==null)
109
+ {
110
+ super.setValue("");
111
+ return;
112
+ }
113
+ setHorizontalAlignment(value instanceof Double ? SwingConstants.RIGHT : SwingConstants.LEFT);
114
+ super.setValue(getModel().getDisplay().toString(value));
115
+ }
116
+
117
+ public Component getTableCellRendererComponent(JTable table, Object value,
118
+ boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {
119
+
120
+ if (vColIndex==labelColumn || vColIndex==colorColumn)
121
+ setFont(bold);
122
+ else
123
+ setFont(font);
124
+ setBackground(rowIndex%2==0 ? Color.white : zebra);
125
+ color=vColIndex==colorColumn || vColIndex==labelColumn;
126
+ Field colorField=null;
127
+ if (color)
128
+ {
129
+ colorField=getModel().getColorField();
130
+ color &= colorField!=null;
131
+ }
132
+ if (color)
133
+ {
134
+ int actIndex=table.convertRowIndexToModel(rowIndex);
135
+ Act act=acts.get(actIndex);
136
+
137
+ if (colorField==null || colorField.getType()!=String.class)
138
+ dataColor=getModel().getDisplay().getColor("timeline.unspecified.color");
139
+ else
140
+ dataColor=getModel().getDisplay().makeColor(act.getString(colorField));
141
+ setForeground(dataColor);
142
+ setValue(value);
143
+ }
144
+ else
145
+ {
146
+ setForeground(Color.black);
147
+ setValue(value);
148
+ }
149
+ return this;
150
+ }
151
+ }
152
+
153
+ class TimelineTableModel implements TableModel
154
+ {
155
+ ActList acts;
156
+ Field[] fields;
157
+
158
+ TimelineTableModel(ActList acts)
159
+ {
160
+ this.acts=acts;
161
+ ArrayList<Field> a=new ArrayList<Field>();
162
+ int i=0;
163
+ Field colorField=getModel().getColorField();
164
+ Field labelField=getModel().getDB().getField(VirtualField.LABEL);
165
+ for (Field f:acts.getDB().getFields())
166
+ {
167
+ a.add(f);
168
+ if (f==colorField)
169
+ colorColumn=i;
170
+ if (f==labelField)
171
+ labelColumn=i;
172
+ i++;
173
+ }
174
+ fields=(Field[])a.toArray(new Field[0]);
175
+ }
176
+
177
+ @Override
178
+ public void addTableModelListener(TableModelListener l) {
179
+ }
180
+
181
+ @Override
182
+ public Class<?> getColumnClass(int columnIndex) {
183
+ return fields[columnIndex].getType();
184
+ }
185
+
186
+ @Override
187
+ public int getColumnCount() {
188
+ return fields.length;
189
+ }
190
+
191
+ @Override
192
+ public String getColumnName(int columnIndex) {
193
+ return fields[columnIndex].getName();
194
+ }
195
+
196
+ @Override
197
+ public int getRowCount() {
198
+ return acts.size();
199
+ }
200
+
201
+ @Override
202
+ public Object getValueAt(int rowIndex, int columnIndex) {
203
+ return acts.get(rowIndex).get(fields[columnIndex]);
204
+ }
205
+
206
+ @Override
207
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
208
+ return editable;
209
+ }
210
+
211
+ @Override
212
+ public void removeTableModelListener(TableModelListener l) {
213
+ }
214
+
215
+ @Override
216
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
217
+ acts.get(rowIndex).set(fields[columnIndex], aValue);
218
+ getModel().noteRecordChange(TableView.this);
219
+ }
220
+ }
221
+
222
+ public class StringArrayEditor extends AbstractCellEditor implements TableCellEditor
223
+ {
224
+ JComponent component = new JTextField();
225
+
226
+ public Component getTableCellEditorComponent(JTable table, Object value,
227
+ boolean isSelected, int rowIndex, int vColIndex) {
228
+ ((JTextField)component).setText(getModel().getDisplay().toString(value));
229
+ return component;
230
+ }
231
+
232
+ public Object getCellEditorValue()
233
+ {
234
+ String s= ((JTextField)component).getText();
235
+ String[] tags=s.split(",");
236
+ for (int i=0; i<tags.length; i++)
237
+ tags[i]=tags[i].trim();
238
+ return tags;
239
+ }
240
+ }
241
+
242
+ public class RoughTimeEditor extends AbstractCellEditor implements TableCellEditor
243
+ {
244
+ JComponent component = new JTextField();
245
+ DateTimeGuesser guesser=new DateTimeGuesser();
246
+
247
+ public Component getTableCellEditorComponent(JTable table, Object value,
248
+ boolean isSelected, int rowIndex, int vColIndex) {
249
+
250
+ RoughTime r=(RoughTime)value;
251
+ JTextField t=((JTextField)component);
252
+ t.setText(r==null ? "" : r.format());
253
+ return component;
254
+ }
255
+
256
+ public Object getCellEditorValue()
257
+ {
258
+ String s= ((JTextField)component).getText();
259
+ return s.trim().length()==0 ? null : guesser.guess(s);
260
+ }
261
+ }
262
+
263
+ public void setReorderable(boolean allow) {
264
+ table.getTableHeader().setReorderingAllowed(allow);
265
+ }
266
+
267
+}
timeflow/views/TimelineView.java
....@@ -0,0 +1,465 @@
1
+package timeflow.views;
2
+
3
+import timeflow.app.ui.ComponentCluster;
4
+import timeflow.data.db.*;
5
+import timeflow.data.time.*;
6
+import timeflow.model.*;
7
+import timeflow.views.CalendarView.CalendarPanel;
8
+import timeflow.views.CalendarView.ScrollingCalendar;
9
+import timeflow.vis.*;
10
+import timeflow.vis.timeline.*;
11
+
12
+import java.awt.*;
13
+import java.awt.event.*;
14
+import java.awt.image.*;
15
+
16
+import javax.swing.*;
17
+
18
+import java.util.*;
19
+
20
+public class TimelineView extends AbstractView
21
+{
22
+ AxisRenderer grid;
23
+ TimelineRenderer timeline;
24
+ TimelineVisuals visuals;
25
+ TimelinePanel timelinePanel;
26
+ JButton fit;
27
+ ScrollingTimeline scroller;
28
+ JPanel controls;
29
+ JScrollBar bar;
30
+
31
+ //JScrollPane scrollpane;
32
+ public JComponent _getControls()
33
+ {
34
+ return controls;
35
+ }
36
+
37
+ public TimelineView(TFModel model)
38
+ {
39
+ super(model);
40
+ visuals = new TimelineVisuals(model);
41
+ grid = new AxisRenderer(visuals);
42
+ timeline = new TimelineRenderer(visuals);
43
+
44
+ timelinePanel = new TimelinePanel(model);
45
+ scroller = new ScrollingTimeline();
46
+ setLayout(new BorderLayout());
47
+ add(scroller, BorderLayout.CENTER);
48
+
49
+ JPanel bottom = new JPanel();
50
+ bottom.setLayout(new BorderLayout());
51
+ add(bottom, BorderLayout.SOUTH);
52
+
53
+ TimelineSlider slider = new TimelineSlider(visuals, 24 * 60 * 60 * 1000L, new Runnable()
54
+ {
55
+
56
+ @Override
57
+ public void run()
58
+ {
59
+ redraw();
60
+ }
61
+ });
62
+ bottom.add(slider, BorderLayout.CENTER);
63
+
64
+ controls = new JPanel();
65
+ controls.setBackground(Color.white);
66
+ controls.setLayout(new BorderLayout());//new GridLayout(2,1));
67
+
68
+ // top part of grid: zoom buttons.
69
+ ComponentCluster buttons = new ComponentCluster("Zoom");
70
+ ImageIcon zoomOutIcon = new ImageIcon("images/zoom_out.gif");
71
+ JButton zoomOut = new JButton(zoomOutIcon);
72
+ buttons.addContent(zoomOut);
73
+ zoomOut.addActionListener(new ActionListener()
74
+ {
75
+
76
+ @Override
77
+ public void actionPerformed(ActionEvent e)
78
+ {
79
+ Interval zoom = visuals.getViewInterval().subinterval(-1, 2).intersection(visuals.getGlobalInterval());
80
+ moveTime(zoom);
81
+ }
82
+ });
83
+
84
+ ImageIcon zoomOut100Icon = new ImageIcon("images/zoom_out_100.gif");
85
+ JButton zoomOutAll = new JButton(zoomOut100Icon);
86
+ buttons.addContent(zoomOutAll);
87
+ zoomOutAll.addActionListener(new ActionListener()
88
+ {
89
+
90
+ @Override
91
+ public void actionPerformed(ActionEvent e)
92
+ {
93
+ moveTime(visuals.getGlobalInterval());
94
+ }
95
+ });
96
+
97
+ /*
98
+ // UI for zooming to precisely fit the visible selection.
99
+ // No one seemed to think this was so important, but we may want it again some day.
100
+ // if you uncomment this, then also uncomment the line in reset().
101
+ ImageIcon zoomSelection=new ImageIcon("images/zoom_selection.gif");
102
+ fit=new JButton(zoomSelection);
103
+ fit.setBackground(Color.white);
104
+ buttons.addContent(fit);
105
+ fit.setEnabled(false);
106
+ fit.addActionListener(new ActionListener() {
107
+ @Override
108
+ public void actionPerformed(ActionEvent e) {
109
+ moveTime(visuals.getFitToVisibleRange());
110
+ }});
111
+ */
112
+ controls.add(buttons, BorderLayout.NORTH);
113
+
114
+ // ok, now do second part of grid: layout style buttons.
115
+ ComponentCluster layoutPanel = new ComponentCluster("Layout");
116
+
117
+ ButtonGroup layoutGroup = new ButtonGroup();
118
+
119
+ ImageIcon diagonalIcon = new ImageIcon("images/layout_diagonal.gif");
120
+ JRadioButton diagonal = new JRadioButton(diagonalIcon, true);
121
+ diagonal.setSelectedIcon(new ImageIcon("images/layout_diagonal_selected.gif"));
122
+ layoutPanel.addContent(diagonal);
123
+ diagonal.addActionListener(new LayoutSetter(TimelineVisuals.Layout.TIGHT));
124
+ layoutGroup.add(diagonal);
125
+
126
+ ImageIcon looseIcon = new ImageIcon("images/layout_loose.gif");
127
+ JRadioButton loose = new JRadioButton(looseIcon, false);
128
+ loose.setSelectedIcon(new ImageIcon("images/layout_loose_selected.gif"));
129
+ layoutPanel.addContent(loose);
130
+ loose.addActionListener(new LayoutSetter(TimelineVisuals.Layout.LOOSE));
131
+ layoutGroup.add(loose);
132
+
133
+ ImageIcon graphIcon = new ImageIcon("images/layout_graph.gif");
134
+ JRadioButton graph = new JRadioButton(graphIcon, false);
135
+ graph.setSelectedIcon(new ImageIcon("images/layout_graph_selected.gif"));
136
+ layoutPanel.addContent(graph);
137
+ graph.addActionListener(new LayoutSetter(TimelineVisuals.Layout.GRAPH));
138
+ layoutGroup.add(graph);
139
+
140
+ controls.add(layoutPanel, BorderLayout.CENTER);
141
+ }
142
+
143
+ class LayoutSetter implements ActionListener
144
+ {
145
+ TimelineVisuals.Layout layout;
146
+
147
+ LayoutSetter(TimelineVisuals.Layout layout)
148
+ {
149
+ this.layout = layout;
150
+ }
151
+
152
+ @Override
153
+ public void actionPerformed(ActionEvent e)
154
+ {
155
+ visuals.setLayoutStyle(layout);
156
+ redraw();
157
+ }
158
+ }
159
+
160
+ void moveTime(Interval interval)
161
+ {
162
+ new TimeAnimator(interval).start();
163
+ }
164
+
165
+ void redraw()
166
+ {
167
+ visuals.layout();
168
+ timelinePanel.drawVisualization();
169
+ repaint();
170
+ }
171
+
172
+ @Override
173
+ protected void onscreen(boolean majorChange)
174
+ {
175
+ visuals.init(majorChange);
176
+ reset(majorChange);
177
+ redraw();
178
+ scroller.calibrate();
179
+ }
180
+
181
+ @Override
182
+ protected void _note(TFEvent e)
183
+ {
184
+ visuals.note(e);
185
+ reset(e.affectsRowSet());
186
+ }
187
+
188
+ void reset(boolean forceViewChange)
189
+ {
190
+ if (forceViewChange || getModel().getViewInterval() == null)
191
+ {
192
+ int numSelected = getModel().getActs().size();
193
+ int numVisible = DBUtils.count(getModel().getActs(), visuals.getViewInterval(),
194
+ getModel().getDB().getField(VirtualField.START));
195
+ if (numVisible < 10 && numSelected > numVisible)
196
+ {
197
+ moveTime(visuals.getFitToVisibleRange());
198
+ }
199
+ }
200
+ // uncomment this if we are using the fit button again.
201
+ //fit.setEnabled(getModel().getActs().size()<getModel().getDB().size());
202
+ redraw();
203
+ scroller.calibrate();
204
+ }
205
+
206
+ class TimeAnimator extends Thread
207
+ {
208
+ Interval i1, i2;
209
+
210
+ TimeAnimator(Interval i2)
211
+ {
212
+ this.i1 = visuals.getViewInterval();
213
+ this.i2 = i2;
214
+ }
215
+
216
+ TimeAnimator(Interval i1, Interval i2)
217
+ {
218
+ this.i1 = i1;
219
+ this.i2 = i2;
220
+ }
221
+
222
+ public void run()
223
+ {
224
+ int n = 15;
225
+ for (int i = 0; i < n; i++)
226
+ {
227
+ long start = ((n - i) * i1.start + i * i2.start) / n;
228
+ long end = ((n - i) * i1.end + i * i2.end) / n;
229
+ try
230
+ {
231
+ visuals.setTimeBounds(start, end);
232
+ redraw();
233
+
234
+ sleep(20);
235
+ } catch (Exception e)
236
+ {
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ class ScrollingTimeline extends JPanel
243
+ {
244
+ //JScrollBar bar;
245
+
246
+ public ScrollingTimeline()
247
+ {
248
+ setLayout(new BorderLayout());
249
+ //scrollpane = new JScrollPane(timelinePanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
250
+ //add(scrollpane, BorderLayout.CENTER);
251
+ add(timelinePanel, BorderLayout.CENTER);
252
+ bar = new JScrollBar(JScrollBar.VERTICAL);
253
+ add(bar, BorderLayout.EAST);
254
+ //bar = scrollpane.getVerticalScrollBar();
255
+ bar.addAdjustmentListener(new AdjustmentListener()
256
+ {
257
+
258
+ @Override
259
+ public void adjustmentValueChanged(AdjustmentEvent e)
260
+ {
261
+ timeline.setDY(bar.getValue());
262
+ timelinePanel.drawVisualization();
263
+ //timelinePanel.repaint();
264
+ }
265
+ });
266
+ }
267
+
268
+ public void setBounds(int x, int y, int w, int h)
269
+ {
270
+ super.setBounds(x, y, w, h);
271
+ calibrate();
272
+ }
273
+
274
+ void calibrate()
275
+ {
276
+// timelinePanel.setBorder(BorderFactory.createLineBorder(Color.red, 2));
277
+ //javax.swing.border.Border b = scrollpane.getViewportBorder();
278
+ //scrollpane.setViewportBorder(BorderFactory.createLineBorder(Color.red, 2));
279
+// scrollpane.getViewport().setBounds(0,0,100,100); //.setViewSize(new Dimension(100, visuals.getFullHeight()));
280
+// if (true) // visuals==null)
281
+// return;
282
+ final int height = getSize().height * 90 / 100; // room for bottom
283
+ final int desired = Math.max(height, visuals.getFullHeight());
284
+// bar.setVisible(desired > height);
285
+// bar.setMinimum(0); // is this double setting necessary?
286
+// bar.setMaximum(desired); // more testing is needed, it solved problems
287
+// bar.setVisibleAmount(height); // on certain Macs are one point.
288
+ SwingUtilities.invokeLater(new Runnable()
289
+ {
290
+
291
+ @Override
292
+ public void run()
293
+ {
294
+ bar.setMinimum(0);
295
+ bar.setMaximum(desired);
296
+ bar.setVisibleAmount(height);
297
+ }
298
+ });
299
+ }
300
+ }
301
+
302
+ class TimelinePanel extends AbstractVisualizationView
303
+ {
304
+ public TimelinePanel(TFModel model)
305
+ {
306
+ super(model);
307
+
308
+ addMouseListener(new MouseAdapter()
309
+ {
310
+ @Override
311
+ public void mouseClicked(MouseEvent e)
312
+ {
313
+ if (e.getClickCount() == 2)
314
+ {
315
+ moveTime(visuals.getViewInterval().subinterval(.333, .667));
316
+ }
317
+ }
318
+
319
+ @Override
320
+ public void mouseExited(MouseEvent e)
321
+ {
322
+ mouse.setLocation(new Point(-1, -1));
323
+ repaint();
324
+ }
325
+
326
+ @Override
327
+ public void mousePressed(MouseEvent e)
328
+ {
329
+ // was this a right-click or ctrl-click? ignore.
330
+ if (e.isPopupTrigger())
331
+ {
332
+ return;
333
+ }
334
+ // did we click on a date label?
335
+ for (Mouseover o : objectLocations)
336
+ {
337
+ if (o.contains(e.getX(), e.getY()) && o.thing instanceof Interval)
338
+ {
339
+ moveTime((Interval) o.thing);
340
+ return;
341
+ }
342
+ }
343
+ // if not, prepare
344
+ firstMouse.setLocation(e.getX(), e.getY());
345
+ mouseIsDown = true;
346
+ repaint();
347
+ }
348
+
349
+ @Override
350
+ public void mouseReleased(MouseEvent event)
351
+ {
352
+ if (!mouseIsDown) // this means we had clicked on a date label.
353
+ {
354
+ return;
355
+ }
356
+
357
+ mouseIsDown = false;
358
+ int a = firstMouse.x;
359
+ int b = mouse.x;
360
+
361
+ long start = visuals.getTimeScale().toTime(a);
362
+ long end = visuals.getTimeScale().toTime(b);
363
+ if (b - a < 0)
364
+ {
365
+ Interval total = visuals.getGlobalInterval();
366
+
367
+ long t = start;
368
+ start = end;
369
+ end = t;
370
+
371
+ // reversed
372
+ long S = visuals.getTimeScale().getInterval().start;
373
+ long E = visuals.getTimeScale().getInterval().end;
374
+
375
+ // start*M + B = S
376
+ // end*M + B = E
377
+ double M = (double)(S - E) / (start - end);
378
+ long B = E - (long)(end * M);
379
+ start = (long)(S*M) + B;
380
+ end = (long)(E*M) + B;
381
+ if (start < total.start)
382
+ {
383
+ start = total.start;
384
+ }
385
+ if (end > total.end)
386
+ {
387
+ end = total.end;
388
+ }
389
+ }
390
+
391
+ moveTime(new Interval(start, end));
392
+ }
393
+ });
394
+
395
+ addMouseWheelListener(new MouseAdapter()
396
+ {
397
+ @Override
398
+ public void mouseWheelMoved(MouseWheelEvent e)
399
+ {
400
+ //bar.setValue(bar.getValue() + e.getWheelRotation() * 10);
401
+ visuals.scale *= Math.exp(e.getWheelRotation()/10.0);
402
+ visuals.scale = visuals.layout();
403
+ //System.out.println(visuals.scale);
404
+ timelinePanel.drawVisualization();
405
+ scroller.calibrate();
406
+ repaint();
407
+ }
408
+ });
409
+ }
410
+
411
+ public RoughTime getTime(Point p)
412
+ {
413
+ TimeScale scale = visuals.getTimeScale();
414
+ long timestamp = scale.toTime(p.x);
415
+ return new RoughTime(timestamp, TimeUnit.DAY);
416
+ }
417
+
418
+ protected void drawVisualization(Graphics2D g)
419
+ {
420
+ if (g == null)
421
+ {
422
+ return;
423
+ }
424
+
425
+ g.setColor(Color.white);
426
+ g.fillRect(0, 0, 2 * getSize().width, getSize().height);
427
+ visuals.setBounds(0, 0, getSize().width, getSize().height);
428
+ objectLocations = new ArrayList<Mouseover>();
429
+ timeline.render(g, objectLocations);
430
+ grid.render(g, objectLocations);
431
+ }
432
+
433
+ protected boolean paintOnTop(Graphics2D g, int w, int h)
434
+ {
435
+ if (!mouseIsDown)
436
+ {
437
+ return false;
438
+ }
439
+
440
+ int a = mouse.x;
441
+ int b = firstMouse.x;
442
+ if (a > b)
443
+ {
444
+ g.setColor(new Color(255, 255, 120, 100));
445
+ g.fillRect(b, 0, a - b, h);
446
+ }
447
+ else
448
+ {
449
+ g.setColor(new Color(255, 120, 255, 100));
450
+ g.fillRect(a, 0, b - a, h);
451
+ }
452
+
453
+ g.setColor(Color.blue);
454
+ g.drawLine(a, 0, a, h);
455
+ g.drawLine(b, 0, b, h);
456
+ return true;
457
+ }
458
+ }
459
+
460
+ @Override
461
+ public String getName()
462
+ {
463
+ return "Timeline";
464
+ }
465
+}
timeflow/vis/GroupVisualAct.java
....@@ -0,0 +1,98 @@
1
+package timeflow.vis;
2
+
3
+import java.awt.*;
4
+import java.util.*;
5
+
6
+import timeflow.data.db.*;
7
+import timeflow.model.Display;
8
+import timeflow.model.VirtualField;
9
+import timeflow.util.*;
10
+
11
+public class GroupVisualAct extends VisualAct
12
+{
13
+ private ArrayList<Act> group=new ArrayList<Act>();
14
+ private boolean mixed=false;
15
+ private DoubleBag<Color> colorBag;
16
+ int numActs=0;
17
+ double total=0;
18
+
19
+ public GroupVisualAct(java.util.List<VisualAct> vacts, boolean mixed, Rectangle bounds)
20
+ {
21
+ super(vacts.get(0).act);
22
+ int n=vacts.size();
23
+
24
+ VisualAct proto=vacts.get(0);
25
+
26
+ this.color=proto.color;
27
+ this.trackString=proto.trackString;
28
+ this.visible=proto.visible;
29
+ this.x=proto.x;
30
+ this.y=proto.y;
31
+
32
+ this.spaceToRight=proto.spaceToRight;
33
+ this.start=proto.start;
34
+ this.group=new ArrayList<Act>();
35
+ this.label="Group of "+n+" events";
36
+ this.mouseOver=this.label;
37
+ this.colorBag=new DoubleBag<Color>();
38
+ Field sizeField=act.getDB().getField(VirtualField.SIZE);
39
+ for (VisualAct v: vacts)
40
+ {
41
+ numActs++;
42
+ if(sizeField!=null)
43
+ total+=v.act.getValue(sizeField);
44
+ this.size+=v.size;
45
+ this.colorBag.add(v.color, v.size);
46
+ }
47
+ this.size=Math.sqrt(this.size);
48
+ this.mixed=mixed;
49
+ }
50
+
51
+ public int getNumActs()
52
+ {
53
+ return numActs;
54
+ }
55
+
56
+ public void add(Act secondAct)
57
+ {
58
+ if (group==null)
59
+ {
60
+ group=new ArrayList<Act>();
61
+ if (act!=null)
62
+ group.add(act);
63
+ }
64
+ group.add(secondAct);
65
+ }
66
+
67
+ public void draw(Graphics2D g, int ox, int oy, int r, Rectangle maxFill, boolean showDuration)
68
+ {
69
+ if (!mixed)
70
+ {
71
+ g.setColor(color);
72
+ g.fillOval(ox,oy-r,2*r,2*r);
73
+ g.drawOval(ox-2,oy-r-2,2*r+3,2*r+3);
74
+ }
75
+ else
76
+ {
77
+ java.util.List<Color> colors=colorBag.listTop(8, true);
78
+ double total=0;
79
+ for (Color c: colors)
80
+ total+=colorBag.num(c);
81
+
82
+ // now draw pie chart thing.
83
+ double angle=0;
84
+ int pieCenterX=ox+r;
85
+ int pieCenterY=oy;
86
+ for (Color c: colors)
87
+ {
88
+ double num=colorBag.num(c);
89
+ double sa=(360*angle)/total;
90
+ int startAngle=(int)(sa);
91
+ int arcAngle=(int)(((360*(angle+num)))/total-sa);
92
+ g.setColor(c);
93
+ g.fillArc(pieCenterX-r,pieCenterY-r,2*r,2*r,startAngle,arcAngle);
94
+ angle+=num;
95
+ }
96
+ }
97
+ }
98
+}
timeflow/vis/Mouseover.java
....@@ -0,0 +1,94 @@
1
+package timeflow.vis;
2
+
3
+import timeflow.model.*;
4
+
5
+import java.awt.*;
6
+import java.util.ArrayList;
7
+
8
+public class Mouseover extends Rectangle {
9
+ public Object thing;
10
+ public Mouseover(Object thing, int x, int y, int w, int h)
11
+ {
12
+ super(x,y,w,h);
13
+ this.thing=thing;
14
+ }
15
+
16
+ public void draw(Graphics2D g, int maxW, int maxH, Display display)
17
+ {
18
+ g.setColor(new Color(0,53,153));
19
+ g.setColor(new Color(255,255,0,100));
20
+ g.fill(this);
21
+ }
22
+
23
+ protected void draw(Graphics2D g, int maxW, int maxH, Display display, ArrayList labels, int numLines)
24
+ {
25
+ if (labels==null || labels.size()==0)
26
+ return;
27
+
28
+ // draw a background box.
29
+
30
+ // find max number of chars, very very roughly!
31
+ int boxW=50;
32
+ for (int i=0; i<labels.size(); i+=2)
33
+ {
34
+ if (labels.get(i) instanceof String[])
35
+ boxW=300;
36
+ else if (labels.get(i) instanceof String)
37
+ {
38
+ boxW=Math.max(boxW, 50+50*((String)labels.get(i)).length());
39
+ }
40
+ }
41
+
42
+
43
+ boxW=Math.min(350, boxW);
44
+ int boxH=18*numLines+10;
45
+ int mx=this.x+this.width+5;
46
+ int my=this.y+this.height+35;
47
+
48
+ // put box in a place where it does not obscure the data
49
+ // or go off screen.
50
+ if (my+boxH>maxH-10)
51
+ {
52
+ my=Math.max(10,this.y-boxH-5);
53
+ }
54
+ if (mx+boxW>maxW-10)
55
+ {
56
+ mx=Math.max(10,this.x-boxW-10);
57
+ }
58
+ int ty=my;
59
+ g.setColor(new Color(0,0,0,70));
60
+ g.fillRoundRect(mx-11, my-16, boxW, boxH,12,12);
61
+ g.setColor(Color.white);
62
+ g.fillRoundRect(mx-15, my-20, boxW, boxH,12,12);
63
+ g.setColor(Color.darkGray);
64
+ g.drawRoundRect(mx-15, my-20, boxW, boxH,12,12);
65
+
66
+ // finally, draw the darn labels.
67
+ for (int i=0; i<labels.size(); i+=2)
68
+ {
69
+ g.setFont(display.bold());
70
+ String field=(String)labels.get(i);
71
+ g.drawString(field,mx,ty);
72
+ int sw=display.boldFontMetrics().stringWidth(field);
73
+ g.setFont(display.plain());
74
+ Object o=labels.get(i+1);
75
+ if (o instanceof String)
76
+ {
77
+ g.drawString((String)o,mx+sw+9,ty);
78
+ ty+=18;
79
+ }
80
+ else
81
+ {
82
+ ArrayList<String> lines=(ArrayList<String>)o;
83
+ int dx=sw+9;
84
+ for (String line: lines)
85
+ {
86
+ g.drawString((String)line,mx+dx,ty);
87
+ ty+=18;
88
+ dx=0;
89
+ }
90
+ ty+=5;
91
+ }
92
+ }
93
+ }
94
+}
timeflow/vis/MouseoverLabel.java
....@@ -0,0 +1,32 @@
1
+package timeflow.vis;
2
+
3
+import java.awt.Graphics2D;
4
+import java.util.ArrayList;
5
+
6
+import timeflow.data.db.Act;
7
+import timeflow.data.db.ActDB;
8
+import timeflow.data.db.Field;
9
+import timeflow.model.Display;
10
+
11
+public class MouseoverLabel extends Mouseover {
12
+
13
+ String label1, label2;
14
+
15
+ public MouseoverLabel(String label1, String label2, int x, int y, int w, int h) {
16
+ super(label1, x, y, w, h);
17
+ this.label1=label1;
18
+ this.label2=label2;
19
+ }
20
+
21
+
22
+ public void draw(Graphics2D g, int maxW, int maxH, Display display)
23
+ {
24
+ super.draw(g, maxW, maxH, display);
25
+ ArrayList labels=new ArrayList();
26
+ labels.add(label1);
27
+ labels.add(label2);
28
+ int numLines=1;
29
+ draw(g, maxW, maxH, display, labels, numLines);
30
+ }
31
+}
32
+
timeflow/vis/TagVisualAct.java
....@@ -0,0 +1,52 @@
1
+package timeflow.vis;
2
+
3
+import timeflow.data.db.*;
4
+
5
+import java.awt.*;
6
+import java.util.*;
7
+
8
+public class TagVisualAct extends VisualAct {
9
+
10
+ Color[] colors;
11
+ private static Color[] nullColors={new Color(230,230,230)};
12
+
13
+ public TagVisualAct(Act act) {
14
+ super(act);
15
+ }
16
+
17
+ public void setColors(Color[] colors)
18
+ {
19
+ this.colors=colors;
20
+ this.color=colors.length>0 ? colors[0] : Color.gray;
21
+ }
22
+
23
+ public void draw(Graphics2D g, int ox, int oy, int r, Rectangle maxFill, boolean showDuration)
24
+ {
25
+ if (colors==null)
26
+ {
27
+ super.draw(g, ox, oy, r, maxFill, showDuration);
28
+ return;
29
+ }
30
+
31
+ Color[] c= colors==null || colors.length==0 ? nullColors : colors;
32
+ int tx=ox-r;
33
+ int side=2*r;
34
+ for (int i=0; i<c.length; i++)
35
+ {
36
+ g.setColor(c[i]);
37
+ int y0=-5+(oy-r)+(2*side*i)/c.length;
38
+ int y1=-5+(oy-r)+(2*side*(i+1))/c.length;
39
+ if (size>=0)
40
+ g.fillRect(tx,y0,side+2,y1-y0);
41
+ else
42
+ g.drawRect(tx,y0,side+2,y1-y0);
43
+ }
44
+
45
+ if (end!=null && showDuration)
46
+ {
47
+ int lineY=y+6;
48
+ g.fillRect(getX(), lineY, getEndX()-getX(), 2);
49
+ g.drawLine(getX(), lineY, getX(), lineY-4);
50
+ }
51
+ }
52
+}
timeflow/vis/TimeScale.java
....@@ -0,0 +1,95 @@
1
+package timeflow.vis;
2
+
3
+import timeflow.data.time.*;
4
+
5
+import java.util.*;
6
+
7
+public class TimeScale
8
+{
9
+ private double low, high;
10
+ private Interval interval;
11
+
12
+ public TimeScale()
13
+ {
14
+ low = 0;
15
+ high = 100;
16
+ interval = new Interval(new Date(0).getTime(), new Date().getTime());
17
+ }
18
+
19
+ public Interval getInterval()
20
+ {
21
+ return interval;
22
+ }
23
+
24
+ public void setNumberRange(double low, double high)
25
+ {
26
+ this.low = low;
27
+ this.high = high;
28
+ }
29
+
30
+ public void setDateRange(Interval t)
31
+ {
32
+ setDateRange(t.start, t.end);
33
+ }
34
+
35
+ public void setDateRange(long first, long last)
36
+ {
37
+ interval.setTo(first, last);
38
+ }
39
+
40
+ public boolean containsDate(long date)
41
+ {
42
+ return interval.contains(date);
43
+ }
44
+
45
+ public boolean containsNum(double x)
46
+ {
47
+ return x >= low && x <= high;
48
+ }
49
+
50
+ public long duration()
51
+ {
52
+ return interval.length();
53
+ }
54
+
55
+ public double toNum(long time)
56
+ {
57
+ return low + (high - low) * (time - interval.start) / (double) duration();
58
+ }
59
+
60
+ public long spaceToTime(double space)
61
+ {
62
+ return (long) (space * duration() / (high - low));
63
+ }
64
+
65
+ public int toInt(long time)
66
+ {
67
+ return (int) toNum(time);
68
+ }
69
+
70
+ public long toTime(double num)
71
+ {
72
+ double millis = interval.start + duration() * (num - low) / (high - low);
73
+ return (long) millis;
74
+ }
75
+
76
+ public double getLow()
77
+ {
78
+ return low;
79
+ }
80
+
81
+ public void setLow(double low)
82
+ {
83
+ this.low = low;
84
+ }
85
+
86
+ public double getHigh()
87
+ {
88
+ return high;
89
+ }
90
+
91
+ public void setHigh(double high)
92
+ {
93
+ this.high = high;
94
+ }
95
+}
timeflow/vis/VisualAct.java
....@@ -0,0 +1,314 @@
1
+package timeflow.vis;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+import timeflow.model.*;
6
+import timeflow.vis.timeline.TimelineTrack;
7
+
8
+import java.awt.*;
9
+import java.util.*;
10
+
11
+import timeflow.util.*;
12
+
13
+public class VisualAct implements Comparable
14
+{
15
+
16
+ Color color;
17
+ String label;
18
+ String mouseOver;
19
+ double size = 1;
20
+ String trackString;
21
+ TimelineTrack track;
22
+ boolean visible;
23
+ Act act;
24
+ int x, y;
25
+ int spaceToRight;
26
+ RoughTime start, end;
27
+ int endX;
28
+
29
+ public VisualAct(Act act)
30
+ {
31
+ this.act = act;
32
+ }
33
+
34
+ public int getX()
35
+ {
36
+ return x;
37
+ }
38
+
39
+ public int getDotR()
40
+ {
41
+ return Math.max(1, (int) Math.abs(size));
42
+ }
43
+
44
+ public void setX(int x)
45
+ {
46
+ this.x = x;
47
+ }
48
+
49
+ public int getY()
50
+ {
51
+ return y;
52
+ }
53
+
54
+ public void setY(int y)
55
+ {
56
+ this.y = y;
57
+ }
58
+
59
+ public void draw(Graphics2D g, int ox, int oy, int r, Rectangle maxFill, boolean showDuration)
60
+ {
61
+ g.setColor(getColor());
62
+ if (size >= 0)
63
+ {
64
+ // Slow!
65
+ g.fillOval(ox, y - r, 2 * r, 2 * r);
66
+ } else
67
+ {
68
+ // Slow!
69
+ g.drawOval(ox, y - r, 2 * r, 2 * r);
70
+ }
71
+ if (end != null && showDuration)
72
+ {
73
+ int lineY = y + 6;
74
+ g.fillRect(getX(), lineY, getEndX() - getX(), 2);
75
+ g.drawLine(getX(), lineY, getX(), lineY - 4);
76
+ }
77
+
78
+ }
79
+
80
+ public Mouseover draw(Graphics2D g, Rectangle maxFill, Rectangle bounds,
81
+ Display display, boolean text, boolean showDuration)
82
+ {
83
+ if (!isVisible())
84
+ {
85
+ return null;
86
+ }
87
+
88
+ if (x > bounds.x + bounds.width && (end == null || endX > bounds.x + bounds.width)
89
+ || x < bounds.x - 200 && (end == null || endX < bounds.x - 200))
90
+ {
91
+ return null;
92
+ }
93
+
94
+ g.setFont(display.plain());
95
+
96
+ int r = getDotR();
97
+ if (r <= 0)
98
+ {
99
+ r = 1;
100
+ }
101
+ if (r > 30)
102
+ {
103
+ r = 30;
104
+ }
105
+ int ox = text ? x - 2 * r : x;
106
+
107
+ draw(g, ox, y - 2, r, maxFill, showDuration);
108
+
109
+
110
+ if (!text)
111
+ {
112
+ return new VisualActMouseover(ox - 2, y - r - 4, 4 + 2 * r, 4 + 2 * r);
113
+ }
114
+
115
+ int labelSpace = getSpaceToRight() - 12;
116
+ int sw = 0;
117
+ if (labelSpace > 50)
118
+ {
119
+ String s = display.format(getLabel(), labelSpace / 8, true);
120
+ int n = s.indexOf(' ');
121
+ int tx = x + 5;
122
+ int ty = y + 4;
123
+ if (n < 1)
124
+ {
125
+ g.drawString(s, tx, ty);
126
+ } else
127
+ {
128
+ String first = s.substring(0, n);
129
+ g.drawString(first, tx, ty);
130
+ Color c = ColorUtils.interpolate(g.getColor(), Color.white, .33);
131
+ g.setColor(c);
132
+ g.drawString(s.substring(n), tx + display.plainFontMetrics().stringWidth(first), ty);
133
+ }
134
+ sw = display.plainFontMetrics().stringWidth(s);
135
+ }
136
+
137
+ return new VisualActMouseover(x - 3 - 2 * r, y - r - 8, 14 + sw, r + 13 + 2 * r);
138
+ }
139
+
140
+ public Act getAct()
141
+ {
142
+ return act;
143
+ }
144
+
145
+ public boolean isVisible()
146
+ {
147
+ return visible;
148
+ }
149
+
150
+ public void setVisible(boolean visible)
151
+ {
152
+ this.visible = visible;
153
+ }
154
+
155
+ public RoughTime getStart()
156
+ {
157
+ return start;
158
+ }
159
+
160
+ public void setStart(RoughTime start)
161
+ {
162
+ this.start = start;
163
+ }
164
+
165
+ public Color getColor()
166
+ {
167
+ return color;
168
+ }
169
+
170
+ public void setColor(Color color)
171
+ {
172
+ this.color = color;
173
+ }
174
+
175
+ public String getLabel()
176
+ {
177
+ return label;
178
+ }
179
+
180
+ public void setLabel(String label)
181
+ {
182
+ this.label = label;
183
+ }
184
+
185
+ public String getMouseOver()
186
+ {
187
+ return mouseOver;
188
+ }
189
+
190
+ public void setMouseOver(String mouseOver)
191
+ {
192
+ this.mouseOver = mouseOver;
193
+ }
194
+
195
+ public double getSize()
196
+ {
197
+ return size;
198
+ }
199
+
200
+ public void setSize(double size)
201
+ {
202
+ this.size = size;
203
+ }
204
+
205
+ public String getTrackString()
206
+ {
207
+ return trackString == null ? "" : trackString;
208
+ }
209
+
210
+ public void setTrackString(String track)
211
+ {
212
+ this.trackString = track;
213
+ }
214
+
215
+ public TimelineTrack getTrack()
216
+ {
217
+ return track;
218
+ }
219
+
220
+ public void setTrack(TimelineTrack track)
221
+ {
222
+ this.track = track;
223
+ }
224
+
225
+ public void setSpaceToRight(int spaceToRight)
226
+ {
227
+ this.spaceToRight = spaceToRight;
228
+ }
229
+
230
+ public int getSpaceToRight()
231
+ {
232
+ return spaceToRight;
233
+ }
234
+
235
+ public int getEndX()
236
+ {
237
+ return endX;
238
+ }
239
+
240
+ public void setEndX(int endX)
241
+ {
242
+ this.endX = endX;
243
+ }
244
+
245
+ public RoughTime getEnd()
246
+ {
247
+ return end;
248
+ }
249
+
250
+ public void setEnd(RoughTime end)
251
+ {
252
+ this.end = end;
253
+ }
254
+
255
+ @Override
256
+ public int compareTo(Object o)
257
+ {
258
+ return RoughTime.compare(start, ((VisualAct) o).start);
259
+ //start.compareTo(((VisualAct)o).start);
260
+ }
261
+
262
+ class VisualActMouseover extends Mouseover
263
+ {
264
+
265
+ public VisualActMouseover(int x, int y, int w, int h)
266
+ {
267
+ super(VisualAct.this, x, y, w, h);
268
+ }
269
+
270
+ public void draw(Graphics2D g, int maxW, int maxH, Display display)
271
+ {
272
+ super.draw(g, maxW, maxH, display);
273
+ Act a = getAct();
274
+ ActDB db = a.getDB();
275
+ java.util.List<Field> fields = db.getFields();
276
+ ArrayList labels = new ArrayList();
277
+ int charWidth = 40;
278
+ int numLines = 1;
279
+ if (VisualAct.this instanceof GroupVisualAct)
280
+ {
281
+ GroupVisualAct gv = (GroupVisualAct) VisualAct.this;
282
+ labels.add(gv.getNumActs() + "");
283
+ labels.add("items");
284
+ Field sizeField = db.getField(VirtualField.SIZE);
285
+ if (sizeField != null)
286
+ {
287
+ labels.add("Total " + sizeField.getName());
288
+ double t = ((GroupVisualAct) (VisualAct.this)).total;
289
+ labels.add(Display.format(t));
290
+ numLines++;
291
+ }
292
+ } else
293
+ {
294
+ for (Field f : fields)
295
+ {
296
+ labels.add(f.getName());
297
+ Object val = a.get(f);
298
+ String valString = display.toString(val);
299
+ if (f.getName().length() + valString.length() + 2 > charWidth)
300
+ {
301
+ ArrayList<String> lines = Display.breakLines(valString, charWidth, 2 + f.getName().length());
302
+ labels.add(lines);
303
+ numLines += lines.size() + 1;
304
+ } else
305
+ {
306
+ labels.add(valString);
307
+ numLines++;
308
+ }
309
+ }
310
+ }
311
+ draw(g, maxW, maxH, display, labels, numLines);
312
+ }
313
+ }
314
+}
timeflow/vis/VisualActFactory.java
....@@ -0,0 +1,119 @@
1
+package timeflow.vis;
2
+
3
+import java.awt.Color;
4
+import java.awt.Rectangle;
5
+import java.util.ArrayList;
6
+import java.util.Collection;
7
+import java.util.HashMap;
8
+
9
+import timeflow.data.db.Act;
10
+import timeflow.data.db.ActDB;
11
+import timeflow.data.db.ActList;
12
+import timeflow.data.db.Field;
13
+import timeflow.model.TFModel;
14
+import timeflow.model.VirtualField;
15
+
16
+public class VisualActFactory
17
+{
18
+ // create one VisualAct per Act
19
+
20
+ private static java.util.List<VisualAct> create(ActList acts)
21
+ {
22
+ java.util.List<VisualAct> list = new ArrayList<VisualAct>();
23
+ for (Act a : acts)
24
+ {
25
+ VisualAct v = new TagVisualAct(a);
26
+ list.add(v);
27
+ }
28
+ return list;
29
+ }
30
+
31
+ // create one VisualAct per Act/tag combo.
32
+ public static java.util.List<VisualAct> create(ActList acts, Field tagField, boolean multipleColors)
33
+ {
34
+ if (tagField == null || tagField.getType() == String.class)
35
+ {
36
+ return create(acts);
37
+ }
38
+ java.util.List<VisualAct> list = new ArrayList<VisualAct>();
39
+ for (Act a : acts)
40
+ {
41
+ String[] tags = a.getTextList(tagField);
42
+ if (tags == null || tags.length < 2)
43
+ {
44
+ VisualAct v = new TagVisualAct(a);
45
+ if (tags != null && tags.length == 1)
46
+ {
47
+ v.setTrackString(tags[0]);
48
+ }
49
+ list.add(v);
50
+ } else
51
+ {
52
+ for (String tag : tags)
53
+ {
54
+ VisualAct v = multipleColors ? new TagVisualAct(a) : new VisualAct(a);
55
+ v.setTrackString(tag);
56
+ list.add(v);
57
+ }
58
+ }
59
+ }
60
+ return list;
61
+ }
62
+
63
+ public static Collection<VisualAct> makeEmFit(TFModel model, ArrayList<VisualAct> vacts, Rectangle bounds)
64
+ {
65
+ // Does everything fit? Because, if so, we're already good to go.
66
+ int area = bounds.width * bounds.height;
67
+ int room = area / 200;
68
+ if (vacts.size() <= room)
69
+ {
70
+ return vacts;
71
+ }
72
+
73
+ ArrayList<VisualAct> results = new ArrayList<VisualAct>();
74
+
75
+ // OK. If:
76
+ // * there's room for more than one item, and
77
+ // * there's more than one color in use,
78
+ //
79
+ // Then let's see how many colors there are. Maybe we can do one bubble per color.
80
+ ActDB db = model.getDB();
81
+ if (room > 1 && (db.getField(VirtualField.COLOR) != null || db.getField(VirtualField.TRACK) != null))
82
+ {
83
+ HashMap<Color, ArrayList<VisualAct>> colorGroupings = new HashMap<Color, ArrayList<VisualAct>>();
84
+ for (VisualAct v : vacts)
85
+ {
86
+ Color c = v.color;
87
+ ArrayList<VisualAct> grouping = colorGroupings.get(c);
88
+ if (grouping == null)
89
+ {
90
+ grouping = new ArrayList<VisualAct>();
91
+ colorGroupings.put(c, grouping);
92
+ }
93
+ grouping.add(v);
94
+ }
95
+
96
+ if (colorGroupings.size() <= room) // Great! The colors fit. We now return one group VisualAct per color.
97
+ {
98
+ for (Color c : colorGroupings.keySet())
99
+ {
100
+ ArrayList<VisualAct> grouping = colorGroupings.get(c);
101
+ if (grouping.size() == 1)
102
+ {
103
+ results.add(grouping.get(0));
104
+ } else if (grouping.size() > 1)
105
+ {
106
+ results.add(new GroupVisualAct(grouping, false, bounds));
107
+ }
108
+ }
109
+ return results;
110
+ }
111
+ }
112
+
113
+ // OK, too bad, even that doesn't fit. We will just create one fat VisualAct
114
+ // that descibes the aggregate. C'est la vie!
115
+
116
+ results.add(new GroupVisualAct(vacts, true, bounds));
117
+ return results;
118
+ }
119
+}
timeflow/vis/VisualEncoder.java
....@@ -0,0 +1,138 @@
1
+package timeflow.vis;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.model.*;
5
+
6
+import java.awt.Color;
7
+import java.util.*;
8
+
9
+public class VisualEncoder
10
+{
11
+
12
+ private TFModel model;
13
+ private java.util.List<VisualAct> visualActs = new ArrayList<VisualAct>();
14
+ private double maxSize = 0;
15
+
16
+ public VisualEncoder(TFModel model)
17
+ {
18
+ this.model = model;
19
+ }
20
+
21
+ public java.util.List<VisualAct> getVisualActs()
22
+ {
23
+ return visualActs;
24
+ }
25
+
26
+ public void createVisualActs()
27
+ {
28
+ Field colorField = model.getDB().getField(VirtualField.COLOR);
29
+ Field trackField = model.getDB().getField(VirtualField.TRACK);
30
+ boolean multipleColors = colorField != null && colorField.getType() == String[].class && colorField != trackField;
31
+ visualActs = VisualActFactory.create(model.getDB().all(), trackField, multipleColors);
32
+ Collections.sort(visualActs);
33
+ }
34
+
35
+ public List<VisualAct> apply()
36
+ {
37
+
38
+ ActList visibleActs = model.getActs();
39
+ Field start = model.getDB().getField(VirtualField.START);
40
+ Field end = model.getDB().getField(VirtualField.END);
41
+ Field size = model.getDB().getField(VirtualField.SIZE);
42
+ if (size != null)
43
+ {
44
+ double[] minmax = DBUtils.minmax(model.getActs(), size);
45
+ maxSize = Math.max(Math.abs(minmax[0]), Math.abs(minmax[1]));
46
+ }
47
+
48
+ // apply color, label, visibility, etc.
49
+ for (VisualAct v : visualActs)
50
+ {
51
+ Act a = v.getAct();
52
+ v.setStart(start == null ? null : a.getTime(start));
53
+ v.setEnd(end == null ? null : a.getTime(end));
54
+ v.setVisible(visibleActs.contains(a) && v.getStart() != null && v.getStart().isDefined());
55
+ apply(v);
56
+ }
57
+
58
+ return visualActs;
59
+ }
60
+
61
+ public void apply(VisualAct v)
62
+ {
63
+ Display display = model.getDisplay();
64
+ ActDB db = model.getDB();
65
+ Act a = v.getAct();
66
+ Field label = db.getField(VirtualField.LABEL);
67
+ Field track = db.getField(VirtualField.TRACK);
68
+ Field color = db.getField(VirtualField.COLOR);
69
+ Field size = db.getField(VirtualField.SIZE);
70
+
71
+ if (label == null)
72
+ {
73
+ v.setLabel("");
74
+ } else
75
+ {
76
+ v.setLabel(a.getString(label));
77
+ }
78
+
79
+ double s = Display.MAX_DOT_SIZE;
80
+ if (size == null || maxSize == 0)
81
+ {
82
+ v.setSize(s / 3);
83
+ } else
84
+ {
85
+ double z = s * Math.sqrt(Math.abs(a.getValue(size)) / maxSize);
86
+ if (a.getValue(size) < 0)
87
+ {
88
+ z = -z;
89
+ }
90
+ v.setSize(z);
91
+ }
92
+
93
+ // For setting the track:
94
+ // This is a little complicated, but if the track is set to
95
+ // tags (that is, track.getType()==String[].class) then
96
+ // the track string was set earlier so it doesn't need to be set now.
97
+ if (track == null)
98
+ {
99
+ v.setTrackString("");
100
+ } else if (track.getType() == String.class)
101
+ {
102
+ v.setTrackString(a.getString(track));
103
+ }
104
+
105
+ if (color == null || color == track)
106
+ {
107
+ if (track == null)
108
+ {
109
+ v.setColor(display.getColor("timeline.unspecified.color"));
110
+ } else
111
+ {
112
+ v.setColor(display.makeColor(v.getTrackString()));
113
+ }
114
+ } else
115
+ {
116
+ if (color.getType() == String[].class)
117
+ {
118
+ String[] tags = a.getTextList(color);
119
+ if (tags == null || tags.length == 0)
120
+ {
121
+ ((TagVisualAct) v).setColors(new Color[0]);
122
+ } else
123
+ {
124
+ int n = tags.length;
125
+ Color[] c = new Color[n];
126
+ for (int i = 0; i < n; i++)
127
+ {
128
+ c[i] = display.makeColor(tags[i]);
129
+ }
130
+ ((TagVisualAct) v).setColors(c);
131
+ }
132
+ } else
133
+ {
134
+ v.setColor(display.makeColor(a.getString(color)));
135
+ }
136
+ }
137
+ }
138
+}
timeflow/vis/calendar/CalendarVisuals.java
....@@ -0,0 +1,113 @@
1
+package timeflow.vis.calendar;
2
+
3
+import java.awt.*;
4
+import java.util.Collection;
5
+
6
+
7
+import timeflow.model.*;
8
+import timeflow.vis.*;
9
+import timeflow.data.time.*;
10
+import timeflow.data.db.*;
11
+
12
+public class CalendarVisuals {
13
+ VisualEncoder encoder;
14
+ TFModel model;
15
+ Rectangle bounds=new Rectangle();
16
+ public Grid grid;
17
+
18
+ public enum Layout {DAY, WEEK, MONTH, YEAR};
19
+ private Layout layoutStyle=Layout.DAY;
20
+
21
+ public enum DrawStyle {LABEL, ICON};
22
+ DrawStyle drawStyle=DrawStyle.ICON;
23
+
24
+ public enum FitStyle {LOOSE, TIGHT};
25
+ FitStyle fitStyle=FitStyle.LOOSE;
26
+
27
+ public CalendarVisuals(TFModel model)
28
+ {
29
+ this.model=model;
30
+ encoder=new VisualEncoder(model);
31
+ }
32
+
33
+ public void render(Graphics2D g, Collection<Mouseover> objectLocations)
34
+ {
35
+ grid.render(g, model.getDisplay(), bounds, this, objectLocations);
36
+ }
37
+
38
+ public Interval getGlobalInterval()
39
+ {
40
+ return DBUtils.range(model.getDB().all(), VirtualField.START);
41
+ }
42
+
43
+ public Rectangle getBounds() {
44
+ return bounds;
45
+ }
46
+
47
+ public void setBounds(int x, int y, int w, int h) {
48
+ bounds.setBounds(x,y,w,h);
49
+ if (grid==null)
50
+ makeGrid(true);
51
+ }
52
+
53
+ public void setDrawStyle(DrawStyle drawStyle)
54
+ {
55
+ this.drawStyle=drawStyle;
56
+ }
57
+
58
+ public void setLayoutStyle(Layout layoutStyle)
59
+ {
60
+ this.layoutStyle=layoutStyle;
61
+ makeGrid(true);
62
+ }
63
+
64
+ public void setFitStyle(FitStyle fitStyle)
65
+ {
66
+ this.fitStyle=fitStyle;
67
+ makeGrid(true);
68
+ }
69
+
70
+ public void makeGrid(boolean fresh)
71
+ {
72
+ Interval interval=getGlobalInterval();
73
+ int dy=0;
74
+ if (grid!=null)
75
+ dy=grid.dy;
76
+ switch(layoutStyle)
77
+ {
78
+ case DAY: grid=new Grid(TimeUnit.WEEK, TimeUnit.DAY_OF_WEEK, interval); break;
79
+ case WEEK: grid=new Grid(TimeUnit.multipleWeeks(8), TimeUnit.WEEK, interval); break;
80
+ case MONTH: grid=new Grid(TimeUnit.YEAR, TimeUnit.MONTH, interval); break;
81
+ case YEAR: grid=new Grid(TimeUnit.DECADE, TimeUnit.YEAR, interval);
82
+ }
83
+ grid.makeCells(encoder.getVisualActs());
84
+ if (!fresh)
85
+ grid.dy=dy;
86
+ }
87
+
88
+ public void init()
89
+ {
90
+ note(new TFEvent(TFEvent.Type.DATABASE_CHANGE,null));
91
+ }
92
+
93
+ public void initAllButGrid()
94
+ {
95
+ encoder.createVisualActs();
96
+ encoder.apply();
97
+ }
98
+
99
+ public void note(TFEvent e) {
100
+ if (e.affectsRowSet())
101
+ {
102
+ encoder.createVisualActs();
103
+ }
104
+
105
+ updateVisuals(e.affectsRowSet());
106
+ }
107
+
108
+ public void updateVisuals(boolean fresh)
109
+ {
110
+ encoder.apply();
111
+ makeGrid(fresh);
112
+ }
113
+}
timeflow/vis/calendar/Grid.java
....@@ -0,0 +1,375 @@
1
+package timeflow.vis.calendar;
2
+
3
+import timeflow.data.time.*;
4
+import timeflow.model.Display;
5
+import timeflow.vis.*;
6
+
7
+import java.util.*;
8
+import java.awt.*;
9
+import java.text.*;
10
+
11
+public class Grid
12
+{
13
+
14
+ TimeUnit rowUnit, columnUnit;
15
+ RoughTime startRow, endRow;
16
+ Interval interval;
17
+ HashMap<Long, GridCell> cells;
18
+ int[] screenGridX;
19
+ int cellHeight = 80, cellWidth, numCols, numRows;
20
+ Rectangle bounds = new Rectangle();
21
+ int dy;
22
+ static final DateFormat dayOfWeek = new SimpleDateFormat("EEE");
23
+ static final DateFormat month = new SimpleDateFormat("MMM d");
24
+ static final String[] day =
25
+ {
26
+ "SUN", "MON", "TUES", "WED", "THURS", "FRI", "SAT"
27
+ };
28
+
29
+ Grid(TimeUnit rowUnit, TimeUnit columnUnit, Interval interval)
30
+ {
31
+ this.rowUnit = rowUnit;
32
+ this.columnUnit = columnUnit;
33
+ numCols = columnUnit.numUnitsIn(rowUnit);
34
+ setInterval(interval);
35
+ }
36
+
37
+ public void setDY(int dy)
38
+ {
39
+ this.dy = dy;
40
+ }
41
+
42
+ public int getCalendarHeight()
43
+ {
44
+ return bounds.height + bounds.y + 20;
45
+ }
46
+
47
+ public RoughTime getTime(int x, int y)
48
+ {
49
+ y += dy;
50
+ if (!bounds.contains(x, y))
51
+ {
52
+ return null;
53
+ }
54
+
55
+ // find grid coordinates.
56
+ int gridX = (x - bounds.x) / cellWidth;
57
+ int gridY = (y - bounds.y) / cellHeight;
58
+
59
+ return startRow.plus(rowUnit, gridY).plus(columnUnit, gridX);
60
+ }
61
+
62
+ public double getScrollFraction()
63
+ {
64
+ double x = (getFirstDrawnTime().getTime() - startRow.getTime()) / (double) (endRow.getTime() - startRow.getTime());
65
+ if (x < 0)
66
+ {
67
+ return 0;
68
+ }
69
+ if (x > 1)
70
+ {
71
+ return 1;
72
+ }
73
+ return x;
74
+ }
75
+
76
+ public RoughTime getFirstDrawnTime()
77
+ {
78
+ int gridY = (dy - bounds.y) / cellHeight;
79
+
80
+ return startRow.plus(rowUnit, gridY);
81
+ }
82
+
83
+ private Point getGridCorner(long timestamp)
84
+ {
85
+ int diff = (int) columnUnit.difference(timestamp, startRow.getTime());
86
+ int gridX = diff % numCols;
87
+ int gridY = diff / numCols;
88
+ return new Point(gridX, gridY);
89
+ }
90
+
91
+ public Rectangle getCell(long timestamp)
92
+ {
93
+ Point p = getGridCorner(timestamp);
94
+ return new Rectangle(bounds.x + p.x * cellWidth, bounds.y + p.y * cellHeight - dy,
95
+ cellWidth, cellHeight);
96
+ }
97
+
98
+ void setInterval(Interval interval)
99
+ {
100
+ this.interval = interval;
101
+ startRow = rowUnit.roundDown(interval.start);
102
+ endRow = rowUnit.roundDown(interval.end);
103
+ numRows = 1 + (int) (rowUnit.difference(endRow.getTime(), startRow.getTime()));
104
+
105
+ // the next line fixes a problem with multi-century data sets.
106
+ // it works, but there's probably a better way to do this :-)
107
+ if (numRows > 50 && rowUnit.getRoughSize() >= TimeUnit.YEAR.getRoughSize())
108
+ {
109
+ numRows++;
110
+ }
111
+ }
112
+
113
+ void makeCells(java.util.List<VisualAct> visualActs)
114
+ {
115
+ cells = new HashMap<Long, GridCell>();
116
+ for (VisualAct v : visualActs)
117
+ {
118
+ if (v.getStart() == null)
119
+ {
120
+ continue;
121
+ }
122
+ long timestamp = v.getStart().getTime();
123
+ RoughTime timeKey = columnUnit.roundDown(timestamp);
124
+ GridCell cell = cells.get(timeKey.getTime());
125
+ if (cell == null)
126
+ {
127
+ cell = new GridCell(timeKey);
128
+ cells.put(timeKey.getTime(), cell);
129
+ Point p = getGridCorner(timestamp);
130
+ cell.gridX = p.x;
131
+ cell.gridY = p.y;
132
+ }
133
+ cell.visualActs.add(v);
134
+ }
135
+ }
136
+
137
+ void render(Graphics2D g, Display display, Rectangle screenBounds, CalendarVisuals visuals,
138
+ Collection<Mouseover> objectLocations)
139
+ {
140
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
141
+ int left = 110, right = 20;
142
+ int padY = 50;
143
+ boolean shouldLabel = visuals.drawStyle == CalendarVisuals.DrawStyle.LABEL;
144
+
145
+ cellWidth = (screenBounds.width - left - right) / numCols;
146
+
147
+ boolean fitTight = visuals.fitStyle == CalendarVisuals.FitStyle.TIGHT;
148
+ int idealHeight = fitTight ? 12 : Display.CALENDAR_CELL_HEIGHT;
149
+ cellHeight = Math.max(idealHeight, (screenBounds.height - padY - 10) / numRows);
150
+ this.bounds.setBounds(left, padY, numCols * cellWidth, numRows * cellHeight);
151
+
152
+ g.setColor(new Color(240, 240, 240));//Color.white);
153
+ g.fill(screenBounds);
154
+ g.setColor(new Color(245, 245, 245));
155
+ g.drawRect(bounds.x, bounds.y - dy, bounds.width, bounds.height);
156
+ g.setFont(display.bold());
157
+
158
+ // draw vertical grid lines.
159
+ Color gridColor = new Color(220, 220, 220);
160
+
161
+ for (int i = 0; i <= numCols; i++)
162
+ {
163
+ int x = bounds.x + i * cellWidth;
164
+ g.setColor(gridColor);
165
+ g.drawLine(x, bounds.y - dy, x, bounds.y + bounds.height - dy);
166
+ if (rowUnit == TimeUnit.WEEK && i < 7)
167
+ {
168
+ g.setColor(Color.gray);
169
+ g.drawString(day[i], x, bounds.y - dy - 6);
170
+ }
171
+ }
172
+
173
+ // horizontal grid lines.
174
+ RoughTime labelTime = startRow.copy();
175
+ int lastLabelY = -100;
176
+ int lastYear = -1000000;
177
+ FontMetrics fm = display.boldFontMetrics();
178
+ int skipped = 0;
179
+ for (int i = 0; i < numRows; i++)
180
+ {
181
+ int y = bounds.y + i * cellHeight;
182
+ if (y - dy > -50)
183
+ {
184
+ if (skipped > 0)
185
+ {
186
+ rowUnit.addTo(labelTime, skipped);
187
+ skipped = 0;
188
+ }
189
+ g.setColor(gridColor);
190
+ g.drawLine(bounds.x, y - dy, bounds.x + bounds.width, y - dy);
191
+ if (y - lastLabelY > 30 || lastLabelY < 0)
192
+ {
193
+ String label = null;
194
+ if (rowUnit == TimeUnit.WEEK)
195
+ {
196
+ int year = TimeUtils.cal(labelTime.getTime()).get(Calendar.YEAR);
197
+ if (year != lastYear)
198
+ {
199
+ label = labelTime.format();
200
+ } else
201
+ {
202
+ label = month.format(labelTime.toDate());
203
+ }
204
+ lastYear = year;
205
+ } else
206
+ {
207
+ label = labelTime.format();
208
+ }
209
+ g.setColor(Color.gray);
210
+ g.drawString(label, bounds.x - fm.stringWidth(label) - 15, y + 15 - dy);
211
+ lastLabelY = y;
212
+ }
213
+ if (y - dy > screenBounds.height)
214
+ {
215
+ break;
216
+ }
217
+
218
+ // now draw, in gray, the labels for each of the boxes.
219
+ if (!fitTight)
220
+ {
221
+ RoughTime gridLabel = labelTime.copy();
222
+ int labelH = 13;
223
+ for (int j = 0; j < numCols; j++)
224
+ {
225
+
226
+ g.setColor(Color.gray);
227
+ g.setFont(display.bold());
228
+ String label = columnUnit.format(gridLabel.toDate());
229
+ g.drawString(label, bounds.x + j * cellWidth + 3, y - dy + labelH);
230
+ columnUnit.addTo(gridLabel);
231
+ }
232
+ }
233
+ rowUnit.addTo(labelTime);
234
+ } else
235
+ {
236
+ skipped++;
237
+ }
238
+ }
239
+
240
+ // draw a frame around the whole thing.
241
+ g.setColor(Color.darkGray);
242
+ g.drawRect(bounds.x, bounds.y - dy, bounds.width, bounds.height);
243
+
244
+ // draw backgrounds
245
+ for (GridCell cell : cells.values())
246
+ {
247
+ // are any visible?
248
+ boolean visible = false;
249
+ for (VisualAct v : cell.visualActs)
250
+ {
251
+ if (v.isVisible())
252
+ {
253
+ visible = true;
254
+ break;
255
+ }
256
+ }
257
+ int cx = bounds.x + cell.gridX * cellWidth;
258
+ int cy = bounds.y + cell.gridY * cellHeight - dy;
259
+
260
+ if (cy < screenBounds.y - 50 || cy > screenBounds.y + screenBounds.height + 50)
261
+ {
262
+ continue;
263
+ }
264
+
265
+ // label top of cell.
266
+ int labelH = 0;
267
+ g.setColor(new Color(240, 240, 240));
268
+
269
+ if (visible)
270
+ {
271
+ g.setColor(Color.white);
272
+ g.fillRect(cx + 1, cy + 1, cellWidth - 1, cellHeight - 1);
273
+ }
274
+ if (cellHeight > 42)
275
+ {
276
+ labelH = 13;
277
+ g.setColor(Color.darkGray);
278
+ g.setFont(display.bold());
279
+ String label = columnUnit.format(cell.time.toDate());
280
+ g.drawString(label, cx + 3, cy + labelH);
281
+ }
282
+
283
+ }
284
+
285
+
286
+
287
+ // draw items.
288
+ int mx = 10, my = shouldLabel ? 18 : 10;
289
+ for (GridCell cell : cells.values())
290
+ {
291
+
292
+ int cx = bounds.x + cell.gridX * cellWidth;
293
+ int cy = bounds.y + cell.gridY * cellHeight - dy;
294
+
295
+ if (cy < screenBounds.y - 50 || cy > screenBounds.y + screenBounds.height + 50)
296
+ {
297
+ continue;
298
+ }
299
+
300
+ // label top of cell.
301
+ int labelH = cellHeight > 42 ? 13 : 0;
302
+
303
+ // now draw the items in the cell.
304
+ // old, non-aggregation code:
305
+
306
+ // START AGGREGATION CODE
307
+
308
+ ArrayList<VisualAct> visibleActs = new ArrayList<VisualAct>();
309
+ for (VisualAct v : cell.visualActs)
310
+ {
311
+ if (v.isVisible())
312
+ {
313
+ visibleActs.add(v);
314
+ }
315
+ }
316
+ Iterator<VisualAct> vacts =
317
+ VisualActFactory.makeEmFit(visuals.model, visibleActs, new Rectangle(cx, cy, cellWidth, cellHeight)).iterator();
318
+
319
+ // END AGGREGATION CODE
320
+
321
+ int leftX = 6;
322
+ int cdx = leftX;
323
+ int topDotY = Math.min(labelH + 16, cellHeight / 2);
324
+ int cdy = topDotY;
325
+ while (vacts.hasNext())
326
+ {
327
+ VisualAct v = vacts.next();
328
+ if (!v.isVisible())
329
+ {
330
+ continue;
331
+ }
332
+
333
+ // set x,y, room to right.
334
+ int x = cx + cdx;
335
+ int y = cy + cdy;
336
+
337
+ int space = cellWidth - 20;
338
+ v.setX(x);
339
+ v.setY(y);
340
+ v.setSpaceToRight(space);
341
+ Mouseover o = v.draw(g, new Rectangle(cx + 1, cy + labelH + 1, cellWidth - 2, cellHeight - 2 - labelH),
342
+ bounds, display, shouldLabel, false);
343
+ if (o != null)
344
+ {
345
+ objectLocations.add(o);
346
+ }
347
+
348
+ // go to next location. if we're labeling, we do this vertically.
349
+ // otherwise, left-to-right, then top-to-bottom.
350
+
351
+ if (shouldLabel)
352
+ {
353
+ cdy += my;
354
+ if (cdy > cellHeight - 2 - my)
355
+ {
356
+ g.drawString("...", x, y + my);
357
+ break;
358
+ }
359
+ } else
360
+ {
361
+ cdx += mx;
362
+ if (cdx > cellWidth - mx / 2 - 2 && vacts.hasNext())
363
+ {
364
+ cdx = leftX;
365
+ cdy += my;
366
+ if (cdy > cellHeight - my / 2)
367
+ {
368
+ break;
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+}
timeflow/vis/calendar/GridCell.java
....@@ -0,0 +1,20 @@
1
+package timeflow.vis.calendar;
2
+
3
+
4
+import timeflow.vis.*;
5
+import timeflow.data.time.*;
6
+
7
+import java.util.*;
8
+import java.awt.*;
9
+
10
+public class GridCell {
11
+ ArrayList<VisualAct> visualActs=new ArrayList<VisualAct>();
12
+ Rectangle bounds;
13
+ RoughTime time;
14
+ int gridX, gridY;
15
+
16
+ GridCell(RoughTime time)
17
+ {
18
+ this.time=time;
19
+ }
20
+}
timeflow/vis/timeline/AxisRenderer.java
....@@ -0,0 +1,88 @@
1
+package timeflow.vis.timeline;
2
+
3
+import java.awt.*;
4
+import java.util.*;
5
+
6
+import timeflow.data.time.Interval;
7
+import timeflow.data.time.TimeUtils;
8
+import timeflow.model.*;
9
+import timeflow.vis.Mouseover;
10
+import timeflow.vis.TimeScale;
11
+
12
+public class AxisRenderer {
13
+
14
+ TimelineVisuals visuals;
15
+
16
+ public AxisRenderer(TimelineVisuals visuals)
17
+ {
18
+ this.visuals=visuals;
19
+ }
20
+
21
+ public void render(Graphics2D g, Collection<Mouseover> objectLocations)
22
+ {
23
+ TFModel model=visuals.getModel();
24
+ g.setColor(model.getDisplay().getColor("chart.background"));
25
+ Rectangle bounds=visuals.getBounds();
26
+
27
+ TimeScale scale=visuals.getTimeScale();
28
+ java.util.List<AxisTicMarks> t=AxisTicMarks.allRelevant(scale.getInterval());
29
+
30
+ int dateLabelH=model.getDisplay().getInt("timeline.datelabel.height");
31
+ int y=bounds.y+bounds.height-dateLabelH;
32
+
33
+ // draw in reverse order so bigger granularity at top.
34
+ int n=t.size();
35
+ for (int i=0; i<n; i++)
36
+ {
37
+ render(t.get(i), g, bounds.x, y, dateLabelH-1, bounds.y, i==0, objectLocations);
38
+ y-=dateLabelH;
39
+ }
40
+ }
41
+
42
+ void render(AxisTicMarks t, Graphics2D g, int x, int y, int h, int top, boolean full, Collection<Mouseover> objectLocations)
43
+ {
44
+ TFModel model=visuals.getModel();
45
+
46
+ int n=t.tics.size();
47
+ for (int i=0; i<n-1; i++)
48
+ {
49
+
50
+ long start=t.tics.get(i);
51
+ long end=t.tics.get(i+1);
52
+
53
+ int x0=Math.max(x,visuals.getTimeScale().toInt(start));
54
+ int x1=visuals.getTimeScale().toInt(end);
55
+
56
+ int dayOfWeek=TimeUtils.cal(start).get(Calendar.DAY_OF_WEEK);
57
+
58
+ g.setColor(t.unit.isDayOrLess() && (dayOfWeek==1 || dayOfWeek==7) ?
59
+ new Color(245,245,245) : new Color(240,240,240));
60
+
61
+ g.fillRect(x0, y, x1-x0-1, h);
62
+ g.setColor(Color.white);
63
+ g.drawLine(x1-1, y, x1-1, y+h);
64
+ g.drawLine(x0,y+h,x1,y+h);
65
+ objectLocations.add(new Mouseover(new Interval(start,end), x0, y, x1-x0-1, h));
66
+
67
+ g.setFont(model.getDisplay().timeLabel());
68
+ String label=full? t.unit.formatFull(start) : t.unit.format(new Date(start));
69
+ int tx=x0+3;
70
+ int ty=y+h-5;
71
+ g.setColor(full ? Color.darkGray : Color.gray);
72
+ int sw=model.getDisplay().timeLabelFontMetrics().stringWidth(label);
73
+ if (sw<x1-tx-3)
74
+ g.drawString(label, tx,ty);
75
+ else
76
+ {
77
+ int c=label.indexOf(':');
78
+ if (c>0)
79
+ {
80
+ label=label.substring(0,c);
81
+ sw=model.getDisplay().timeLabelFontMetrics().stringWidth(label);
82
+ if (sw<x1-tx-3)
83
+ g.drawString(label, tx,ty);
84
+ }
85
+ }
86
+ }
87
+ }
88
+}
timeflow/vis/timeline/AxisTicMarks.java
....@@ -0,0 +1,117 @@
1
+package timeflow.vis.timeline;
2
+
3
+import java.util.*;
4
+
5
+import timeflow.data.time.*;
6
+
7
+public class AxisTicMarks {
8
+ public TimeUnit unit;
9
+ public List<Long> tics;
10
+
11
+ private static final TimeUnit[] units={
12
+ TimeUnit.YEAR, TimeUnit.MONTH, TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE, TimeUnit.SECOND
13
+ };
14
+
15
+ private static final TimeUnit[] histUnits={
16
+ TimeUnit.YEAR.times(100), TimeUnit.YEAR.times(50), TimeUnit.YEAR.times(25),
17
+ TimeUnit.YEAR.times(10), TimeUnit.YEAR.times(5), TimeUnit.YEAR.times(2), TimeUnit.YEAR,
18
+ TimeUnit.MONTH.times(6), TimeUnit.MONTH.times(3), TimeUnit.MONTH.times(2), TimeUnit.MONTH,
19
+ TimeUnit.WEEK, TimeUnit.DAY.times(2), TimeUnit.DAY,
20
+
21
+ TimeUnit.HOUR,
22
+ TimeUnit.MINUTE,
23
+ TimeUnit.SECOND
24
+ };
25
+
26
+ public AxisTicMarks(TimeUnit unit, long start, long end)
27
+ {
28
+ this.unit=unit;
29
+ tics=new ArrayList<Long>();
30
+ RoughTime r=unit.roundDown(start);
31
+ tics.add(r.getTime());
32
+ do
33
+ {
34
+ unit.addTo(r);
35
+ tics.add(r.getTime());
36
+ } while (r.getTime()<end);
37
+ }
38
+
39
+
40
+
41
+ public static List<AxisTicMarks> allRelevant(Interval interval)
42
+ {
43
+ return allRelevant(interval.start, interval.end);
44
+ }
45
+
46
+ public static List<AxisTicMarks> allRelevant(long start, long end)
47
+ {
48
+ return allRelevant(start, end, 40);
49
+ }
50
+
51
+ public static AxisTicMarks histoTics(long start, long end)
52
+ {
53
+ for (int i=histUnits.length-1; i>=0; i--)
54
+ {
55
+ TimeUnit u=histUnits[i];
56
+ long estimate=u.approxNumInRange(start, end);
57
+ if (estimate<200 || i==0)
58
+ {
59
+ AxisTicMarks t=new AxisTicMarks(u, start, end);
60
+ return t;
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+
66
+ public static List<AxisTicMarks> allRelevant(long start, long end, long maxTics)
67
+ {
68
+ List<AxisTicMarks> list=new ArrayList<AxisTicMarks>();
69
+
70
+
71
+ for (int i=0; i<units.length; i++)
72
+ {
73
+ TimeUnit u=units[i];
74
+ long estimate=u.approxNumInRange(start, end);
75
+
76
+ if (estimate<maxTics)
77
+ {
78
+ AxisTicMarks t=new AxisTicMarks(u, start, end);
79
+ if (list.size()>0)
80
+ {
81
+ AxisTicMarks last=list.get(0);
82
+ if (last.tics.size()==t.tics.size())
83
+ list.remove(0);
84
+ }
85
+ list.add(t);
86
+
87
+ }
88
+ }
89
+ while (list.size()>2)
90
+ list.remove(0);
91
+
92
+ if (list.size()==0) // uh oh! must be many years. we will add in bigger increments.
93
+ {
94
+ long length=end-start;
95
+ long size=365*24*60*60*1000L;
96
+ int m=1;
97
+ maxTics=15;
98
+ while (m<2000000000 && length/(m*size)>maxTics)
99
+ {
100
+ if (length/(2*m*size)<=maxTics)
101
+ {
102
+ m*=2;
103
+ break;
104
+ }
105
+ if (length/(5*m*size)<=maxTics)
106
+ {
107
+ m*=5;
108
+ break;
109
+ }
110
+ m*=10;
111
+ }
112
+ AxisTicMarks t=new AxisTicMarks(TimeUnit.multipleYears(m), start, end);
113
+ list.add(t);
114
+ }
115
+ return list;
116
+ }
117
+}
timeflow/vis/timeline/TimelineRenderer.java
....@@ -0,0 +1,184 @@
1
+package timeflow.vis.timeline;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+import timeflow.model.*;
6
+import timeflow.vis.Mouseover;
7
+import timeflow.vis.MouseoverLabel;
8
+import timeflow.vis.VisualAct;
9
+
10
+import timeflow.util.*;
11
+
12
+import java.awt.*;
13
+import java.awt.geom.AffineTransform;
14
+import java.util.*;
15
+import java.util.List;
16
+
17
+public class TimelineRenderer
18
+{
19
+
20
+ private TimelineVisuals visuals;
21
+ private int dy;
22
+
23
+ public TimelineRenderer(TimelineVisuals visuals)
24
+ {
25
+ this.visuals = visuals;
26
+ }
27
+
28
+ public void setDY(int dy)
29
+ {
30
+ this.dy = dy;
31
+ }
32
+
33
+ public void render(Graphics2D g, Collection<Mouseover> objectLocations)
34
+ {
35
+ AffineTransform old = g.getTransform();
36
+ g.setTransform(AffineTransform.getTranslateInstance(0, -dy));
37
+
38
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
39
+ TFModel model = visuals.getModel();
40
+ Display display = model.getDisplay();
41
+ ActDB db = model.getDB();
42
+
43
+ if (display.emptyMessage(g, model))
44
+ {
45
+ return;
46
+ }
47
+
48
+ // need to check this, because resize events don't (and shouldn't) register with central TFModel.
49
+ visuals.layoutIfChanged();
50
+
51
+ java.util.List<VisualAct> visualActs = visuals.getVisualActs();
52
+
53
+ if (visualActs == null || visualActs.size() == 0)
54
+ {
55
+ g.drawString("No data", 10, 30);
56
+ return;
57
+ }
58
+
59
+ Rectangle bounds = visuals.getBounds();
60
+ boolean colorTrackLabels = db.getField(VirtualField.COLOR) == null || db.getField(VirtualField.COLOR).equals(db.getField(VirtualField.TRACK));
61
+
62
+ // draw tracks, if more than 1.
63
+ if (visuals.trackList.size() > 1)
64
+ {
65
+ boolean zebra = true;
66
+
67
+ for (TimelineTrack t : visuals.trackList)
68
+ {
69
+ if (zebra)
70
+ {
71
+ g.setColor(display.getColor("timeline.zebra"));
72
+ g.fillRect(bounds.x, t.y0, bounds.width, t.y1 - t.y0);
73
+ }
74
+ zebra = !zebra;
75
+ g.setColor(display.getColor("timeline.grid"));
76
+ g.drawLine(bounds.x, t.y0, bounds.x + bounds.width, t.y0);
77
+ }
78
+ }
79
+
80
+ Interval screenInterval = visuals.getViewInterval().subinterval(-.5, 1.5);
81
+ AxisTicMarks tics = AxisTicMarks.histoTics(screenInterval.start, screenInterval.end);
82
+ for (TimelineTrack t : visuals.trackList)
83
+ {
84
+ // now... if not in graph mode, just draw items
85
+ if (visuals.getLayoutStyle() != TimelineVisuals.Layout.GRAPH)//max<(t.y1-t.y0)/20)
86
+ {
87
+ for (VisualAct v : t.visualActs)
88
+ {
89
+ Mouseover o = v.draw(g, null, bounds, display, true, true);
90
+ if (o != null)
91
+ {
92
+ o.y -= dy;
93
+ objectLocations.add(o);
94
+ }
95
+ }
96
+ continue;
97
+ }
98
+
99
+ if (true) continue;
100
+
101
+ // draw bars. to do so, we make a histogram of visible items.
102
+ t.histogram = new DoubleBag<Long>();
103
+ for (VisualAct v : t.visualActs)
104
+ {
105
+ long time = v.getStart().getTime();
106
+ if (screenInterval.contains(time))
107
+ {
108
+ t.histogram.add(tics.unit.roundDown(v.getStart().getTime()).getTime(), 1);//v.getSize());
109
+ }
110
+ }
111
+
112
+ // get max of items.
113
+ double max = t.histogram.getMax();
114
+
115
+ // now draw bars on screen.
116
+ Color fg = colorTrackLabels ? model.getDisplay().makeColor(t.label) : Color.gray;
117
+ if (visuals.trackList.size() < 2)
118
+ {
119
+ fg = Color.gray;
120
+ }
121
+
122
+ List<Long> keys = t.histogram.unordered();
123
+ Collections.sort(keys);
124
+ for (Long r : keys)
125
+ {
126
+ double num = t.histogram.num(r);
127
+ int x1 = visuals.getTimeScale().toInt(r);
128
+ int x2 = visuals.getTimeScale().toInt(tics.unit.roundUp(r + 1).getTime());
129
+ int barY = t.y1 - (int) (.9 * ((t.y1 - t.y0) * num) / max);
130
+ g.setColor(new Color(230, 230, 230));
131
+ int m = 12;
132
+ g.fillRoundRect(x1 + 3, barY + 3, x2 - x1 - 1, t.y1 - barY, m, m);
133
+
134
+ g.setColor(fg);
135
+ g.fillRoundRect(x1, barY, x2 - x1 - 1, t.y1 - barY, m, m);
136
+
137
+ MouseoverLabel mouse = new MouseoverLabel("" + Math.round(num), "items", x1, barY, x2 - x1 - 1, t.y1 - barY);
138
+ objectLocations.add(mouse);
139
+ }
140
+ }
141
+
142
+ // finally label the tracks. we do this last so that the labels go on top of the data.
143
+
144
+ if (visuals.trackList.size() > 1)
145
+ {
146
+ boolean zebra = false;
147
+ FontMetrics hugeFm = display.hugeFontMetrics();
148
+ for (TimelineTrack t : visuals.trackList)
149
+ {
150
+
151
+ // now label the track.
152
+ //if (t.y1 - t.y0 > 23)
153
+ {
154
+ Color fg = colorTrackLabels ? model.getDisplay().makeColor(t.label) : Color.darkGray;
155
+ Color bg = zebra ? display.getColor("timeline.zebra") : Color.white;
156
+
157
+ String label = t.label;
158
+ if (label.equals(Display.MISC_CODE))
159
+ {
160
+ label = display.getMiscLabel();
161
+ } else if (label.length() == 0)
162
+ {
163
+ label = display.getNullLabel();
164
+ } else
165
+ {
166
+ label = display.format(label, 20, false);
167
+ }
168
+
169
+ // draw background.
170
+ g.setColor(bg);
171
+ int sw = hugeFm.stringWidth(label);
172
+ g.fillRect(0, t.y1 - 20, sw + 8, 19);
173
+
174
+ // draw foreground (actual label)
175
+ g.setFont(display.huge());
176
+ g.setColor(fg);
177
+ g.drawString(label, 2, t.y1); // - 5);
178
+ }
179
+ zebra = !zebra;
180
+ }
181
+ }
182
+ g.setTransform(old);
183
+ }
184
+}
timeflow/vis/timeline/TimelineSlider.java
....@@ -0,0 +1,239 @@
1
+package timeflow.vis.timeline;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.time.*;
5
+import timeflow.model.*;
6
+import timeflow.vis.TimeScale;
7
+import timeflow.vis.VisualAct;
8
+import timeflow.vis.timeline.*;
9
+
10
+import timeflow.util.*;
11
+
12
+import java.awt.*;
13
+import javax.swing.*;
14
+import java.awt.event.*;
15
+
16
+public class TimelineSlider extends ModelPanel
17
+{
18
+
19
+ TimelineVisuals visuals;
20
+ Interval original;
21
+ long minRange;
22
+ int ew = 10;
23
+ int eventRadius = 2;
24
+ TimeScale scale;
25
+ Point mouseHit = new Point();
26
+ Point mouse = new Point(-1, 0);
27
+
28
+ enum Modify
29
+ {
30
+
31
+ START, END, POSITION, NONE
32
+ };
33
+ Modify change = Modify.NONE;
34
+ Rectangle startRect = new Rectangle(-1, -1, 0, 0);
35
+ Rectangle endRect = new Rectangle(-1, -1, 0, 0);
36
+ Rectangle positionRect = new Rectangle(-1, -1, 0, 0);
37
+ Color sidePlain = Color.orange;
38
+ Color sideMouse = new Color(230, 100, 0);
39
+
40
+ public TimelineSlider(final TimelineVisuals visuals, final long minRange, final Runnable action)
41
+ {
42
+ super(visuals.getModel());
43
+
44
+ this.minRange = minRange;
45
+ this.visuals = visuals;
46
+
47
+ addMouseListener(new MouseAdapter()
48
+ {
49
+
50
+ @Override
51
+ public void mousePressed(MouseEvent e)
52
+ {
53
+ int mx = e.getX();
54
+ int my = e.getY();
55
+ if (positionRect.contains(mx, my))
56
+ {
57
+ change = Modify.POSITION;
58
+ } else if (startRect.contains(mx, my))
59
+ {
60
+ change = Modify.START;
61
+ } else if (endRect.contains(mx, my))
62
+ {
63
+ change = Modify.END;
64
+ } else
65
+ {
66
+ change = Modify.NONE;
67
+ }
68
+ mouseHit.setLocation(mx, my);
69
+ original = window().copy();
70
+ mouse.setLocation(mx, my);
71
+ repaint();
72
+ }
73
+
74
+ @Override
75
+ public void mouseReleased(MouseEvent e)
76
+ {
77
+ change = Modify.NONE;
78
+ repaint();
79
+ }
80
+ });
81
+ addMouseMotionListener(new MouseMotionAdapter()
82
+ {
83
+
84
+ @Override
85
+ public void mouseDragged(MouseEvent e)
86
+ {
87
+
88
+ if (change == Modify.NONE)
89
+ {
90
+ return;
91
+ }
92
+ mouse.setLocation(e.getX(), e.getY());
93
+ int mouseDiff = mouse.x - mouseHit.x;
94
+ Interval limits = visuals.getGlobalInterval();
95
+ long timeDiff = scale.spaceToTime(mouseDiff);
96
+
97
+ switch (change)
98
+ {
99
+ case POSITION:
100
+ window().translateTo(original.start + timeDiff);
101
+ window().clampInside(limits);
102
+ break;
103
+ case START:
104
+ window().start = Math.min(original.start + timeDiff, original.end - minRange);
105
+ window().start = Math.max(window().start, limits.start);
106
+ break;
107
+ case END:
108
+ window().end = Math.max(original.end + timeDiff, original.start + minRange);
109
+ window().end = Math.min(window().end, limits.end);
110
+ }
111
+ getModel().setViewInterval(window());
112
+ action.run();
113
+ repaint();
114
+ }
115
+ });
116
+ }
117
+
118
+ private Interval window()
119
+ {
120
+ return visuals.getViewInterval();
121
+ }
122
+
123
+ @Override
124
+ public Dimension getPreferredSize()
125
+ {
126
+ return new Dimension(600, 30);
127
+ }
128
+
129
+ public void setMinRange(long minRange)
130
+ {
131
+ this.minRange = minRange;
132
+ }
133
+
134
+ @Override
135
+ public void note(TFEvent e)
136
+ {
137
+ repaint();
138
+ }
139
+
140
+ void setTimeInterval(Interval interval)
141
+ {
142
+ window().setTo(interval);
143
+ repaint();
144
+ }
145
+
146
+ public void paintComponent(Graphics g1)
147
+ {
148
+ int w = getSize().width, h = getSize().height;
149
+ Graphics2D g = (Graphics2D) g1;
150
+
151
+ long start = System.currentTimeMillis();
152
+
153
+ // draw main backdrop.
154
+ g.setColor(Color.white);
155
+ g.fillRect(0, 0, w, h);
156
+
157
+ if (visuals.getModel() == null || visuals.getModel().getActs() == null)
158
+ {
159
+ g.setColor(Color.darkGray);
160
+ g.drawString("No data for timeline.", 5, 20);
161
+ return;
162
+ }
163
+
164
+ scale = new TimeScale();
165
+ scale.setDateRange(visuals.getGlobalInterval());
166
+ scale.setNumberRange(ew, w - ew);
167
+
168
+
169
+ // draw the area for the central "thumb".
170
+ int lx = scale.toInt(window().start);
171
+ int rx = scale.toInt(window().end);
172
+ g.setColor(change == Modify.POSITION ? new Color(255, 255, 120) : new Color(255, 245, 200));
173
+ positionRect.setBounds(lx, 0, rx - lx, h);
174
+ g.fill(positionRect);
175
+
176
+ // Figure out how best to draw events.
177
+ // If there are too many, we just draw a kind of histogram of frequency,
178
+ // rather than using the timeline layout.
179
+ int slotW = 2 * eventRadius;
180
+ int slotNum = w / slotW + 1;
181
+ int[] slots = new int[slotNum];
182
+ int mostInSlot = 0;
183
+ for (VisualAct v : visuals.getVisualActs())
184
+ {
185
+ if (!v.isVisible())
186
+ {
187
+ continue;
188
+ }
189
+ int x = scale.toInt(v.getStart().getTime());
190
+ int s = x / slotW;
191
+ if (s >= 0 && s < slotNum)
192
+ {
193
+ slots[s]++;
194
+ mostInSlot = Math.max(mostInSlot, slots[s]);
195
+ }
196
+ }
197
+ if (mostInSlot > 30)
198
+ {
199
+ g.setColor(Color.gray);
200
+ for (int i = 0; i < slots.length; i++)
201
+ {
202
+ int sh = (h * slots[i]) / mostInSlot;
203
+ g.fillRect(slotW * i, h - sh, slotW, sh);
204
+ }
205
+ } else
206
+ {
207
+ // draw individual events.
208
+ for (VisualAct v : visuals.getVisualActs())
209
+ {
210
+ if (!v.isVisible())
211
+ {
212
+ continue;
213
+ }
214
+ g.setColor(v.getColor());
215
+ int x = scale.toInt(v.getStart().getTime());
216
+
217
+ int y = eventRadius + (int) (v.getY() * h) / (visuals.getBounds().height - 2 * eventRadius);
218
+ g.fillRect(x - 1, y - eventRadius, 2 * eventRadius, 3);
219
+ if (v.getEnd() != null)
220
+ {
221
+ int endX = scale.toInt(v.getEnd().getTime());
222
+ g.drawLine(x, y, endX, y);
223
+ }
224
+ }
225
+ }
226
+
227
+ g.setColor(Color.gray);
228
+ g.drawLine(0, 0, w, 0);
229
+ g.drawLine(0, h - 1, w, h - 1);
230
+
231
+ // draw "expansion" areas on sides of thumb.
232
+ startRect.setBounds(positionRect.x - ew, 1, ew, h - 2);
233
+ g.setColor(change == Modify.START ? sideMouse : sidePlain);
234
+ g.fill(startRect);
235
+ endRect.setBounds(positionRect.x + positionRect.width, 1, ew, h - 2);
236
+ g.setColor(change == Modify.END ? sideMouse : sidePlain);
237
+ g.fill(endRect);
238
+ }
239
+}
timeflow/vis/timeline/TimelineTrack.java
....@@ -0,0 +1,129 @@
1
+package timeflow.vis.timeline;
2
+
3
+import timeflow.vis.VisualAct;
4
+import timeflow.data.time.*;
5
+import timeflow.util.*;
6
+
7
+import java.util.*;
8
+
9
+public class TimelineTrack implements Comparable
10
+{
11
+
12
+ String label;
13
+ List<VisualAct> visualActs = new ArrayList<VisualAct>();
14
+ int y0, y1;
15
+ DoubleBag<Long> histogram;
16
+
17
+ TimelineTrack(String label)
18
+ {
19
+ this.label = label;
20
+ }
21
+
22
+ void add(VisualAct v)
23
+ {
24
+ visualActs.add(v);
25
+ }
26
+
27
+ int size()
28
+ {
29
+ return visualActs.size();
30
+ }
31
+
32
+ // assumes a>b>0
33
+ int gcd(int a, int b)
34
+ {
35
+ int mod = a % b;
36
+ if (mod == 0)
37
+ {
38
+ return b;
39
+ }
40
+ return gcd(b, mod);
41
+ }
42
+
43
+ int nearAndRelPrime(int target, int modulus)
44
+ {
45
+ if (target < 2)
46
+ {
47
+ return 1;
48
+ }
49
+ while (gcd(modulus, target) > 1)
50
+ {
51
+ target--;
52
+ }
53
+ return target;
54
+ }
55
+
56
+ // top and height are in proportion of total height of frame.
57
+ void layout(double top, double height, TimelineVisuals visuals)
58
+ {
59
+ int n = visualActs.size();
60
+ if (n == 0)
61
+ {
62
+ return;
63
+ }
64
+
65
+ int labelHeight = 0; // 80;
66
+ int fh = visuals.getBounds().height - labelHeight;
67
+ int fy = visuals.getBounds().y;
68
+ int cellH = visuals.getModel().getDisplay().getInt("timeline.item.height.min");
69
+
70
+ int fontHeight = 12;
71
+
72
+ y0 = fy + (int) (fh * top);
73
+ y1 = fy + (int) (fh * (top + height));
74
+ //int mid = (y0 + y1) / 2;
75
+ int iy0 = y0; // Math.min(y0 + 5, mid);
76
+ int iy1 = y1; // Math.max(y1 - 15, mid);
77
+
78
+ int numCells = Math.max(1, (iy1 - iy0) / cellH);
79
+
80
+ VisualAct[] rights = new VisualAct[numCells];
81
+
82
+ int step = nearAndRelPrime((int) (.61803399 * numCells), numCells);
83
+ int i = 0;
84
+ VisualAct last = null;
85
+ for (VisualAct v : visualActs)
86
+ {
87
+ if (!v.isVisible() || !v.getTrack().equals(this))
88
+ {
89
+ continue;
90
+ }
91
+ v.setSpaceToRight(1000);
92
+
93
+ double num = visuals.getTimeScale().toNum(v.getStart().getTime());
94
+ int x = (int) num;
95
+
96
+ int cell = numCells < 2 ? 0 : (i % numCells);
97
+ int y = iy0 + cell * cellH; // (iy1 - iy0 < 12 ? 0 : cell * cellH);
98
+ v.setX(x);
99
+ v.setY(y + fontHeight);
100
+
101
+ if (v.getEnd() != null)
102
+ {
103
+ v.setEndX((int) visuals.getTimeScale().toNum(v.getEnd().getTime()));
104
+ }
105
+
106
+ if (rights[cell] != null)
107
+ {
108
+ int space = x - rights[cell].getX();
109
+ rights[cell].setSpaceToRight(space);
110
+ }
111
+ rights[cell] = v;
112
+ if ((last != null && v.getStart().getTime() == last.getStart().getTime())
113
+ || visuals.getLayoutStyle() == TimelineVisuals.Layout.TIGHT)
114
+ {
115
+ i++;
116
+ } else
117
+ {
118
+ i += step;
119
+ }
120
+ last = v;
121
+ }
122
+ }
123
+
124
+ @Override
125
+ public int compareTo(Object o)
126
+ {
127
+ return ((TimelineTrack) o).size() - size();
128
+ }
129
+}
timeflow/vis/timeline/TimelineVisuals.java
....@@ -0,0 +1,345 @@
1
+package timeflow.vis.timeline;
2
+
3
+import timeflow.data.db.*;
4
+import timeflow.data.db.filter.*;
5
+import timeflow.data.time.*;
6
+import timeflow.model.*;
7
+import timeflow.vis.*;
8
+
9
+import java.util.*;
10
+import java.awt.*;
11
+
12
+/*
13
+ * A VisualEncoding takes the info about which fields to translate to
14
+ * which visual aspects, and applies that to particular Acts.
15
+ */
16
+public class TimelineVisuals
17
+{
18
+
19
+ private Map<String, TimelineTrack> trackTable = new HashMap<String, TimelineTrack>();
20
+ ArrayList<TimelineTrack> trackList = new ArrayList<TimelineTrack>();
21
+ private TimeScale timeScale = new TimeScale();
22
+ private Rectangle bounds = new Rectangle();
23
+ private boolean frameChanged;
24
+ private int numShown = 0;
25
+ private Interval globalInterval;
26
+
27
+ public double scale = 1;
28
+
29
+ public enum Layout
30
+ {
31
+
32
+ TIGHT, LOOSE, GRAPH
33
+ };
34
+ private Layout layoutStyle = Layout.TIGHT;
35
+ private VisualEncoder encoder;
36
+ private TFModel model;
37
+ private int fullHeight;
38
+
39
+ public int getFullHeight()
40
+ {
41
+ return fullHeight;
42
+ }
43
+
44
+ public TimelineVisuals(TFModel model)
45
+ {
46
+ this.model = model;
47
+ encoder = new VisualEncoder(model);
48
+ }
49
+
50
+ public TimeScale getTimeScale()
51
+ {
52
+ return timeScale;
53
+ }
54
+
55
+ public Rectangle getBounds()
56
+ {
57
+ return bounds;
58
+ }
59
+
60
+ public void setBounds(int x, int y, int w, int h)
61
+ {
62
+ bounds.setBounds(x, y, w, h);
63
+ timeScale.setLow(x);
64
+ timeScale.setHigh(x + w);
65
+ frameChanged = true;
66
+ }
67
+
68
+ public Layout getLayoutStyle()
69
+ {
70
+ return layoutStyle;
71
+ }
72
+
73
+ public void setLayoutStyle(Layout style)
74
+ {
75
+ layoutStyle = style;
76
+ layout();
77
+ }
78
+
79
+ public Interval getFitToVisibleRange()
80
+ {
81
+ ActList acts = model.getActs();
82
+
83
+ // add a little bit to the right so we can see labels...
84
+ ActDB db = getModel().getDB();
85
+ Field endField = db.getField(VirtualField.END);
86
+ Interval i = null;
87
+ if (endField == null)
88
+ {
89
+ i = DBUtils.range(acts, VirtualField.START);
90
+ } else
91
+ {
92
+ i = DBUtils.range(acts, new Field[]
93
+ {
94
+ db.getField(VirtualField.START), endField
95
+ });
96
+ }
97
+ if (i.length() == 0)
98
+ {
99
+ i.expand(globalInterval.length() / 20);
100
+ }
101
+ i = i.subinterval(-.05, 1.1);
102
+ i.intersection(globalInterval);
103
+ return i;
104
+ }
105
+
106
+ public void fitToVisible()
107
+ {
108
+ Interval i = getFitToVisibleRange();
109
+ setTimeBounds(i.start, i.end);
110
+ }
111
+
112
+ public void zoomOut()
113
+ {
114
+ setTimeBounds(globalInterval.start, globalInterval.end);
115
+ }
116
+
117
+ public void setTimeBounds(long first, long last)
118
+ {
119
+ timeScale.setDateRange(first, last);
120
+ frameChanged = true;
121
+ model.setViewInterval(new Interval(first, last));
122
+ }
123
+
124
+ public Interval getGlobalInterval()
125
+ {
126
+ if (globalInterval == null && model != null && model.getDB() != null)
127
+ {
128
+ createGlobalInterval();
129
+ }
130
+ return globalInterval;
131
+ }
132
+
133
+ public void createGlobalInterval()
134
+ {
135
+ globalInterval = DBUtils.range(model.getDB().all(), VirtualField.START).subinterval(-.05, 1.1);
136
+ }
137
+
138
+ public Interval getViewInterval()
139
+ {
140
+ return timeScale.getInterval();
141
+ }
142
+
143
+ public java.util.List<VisualAct> getVisualActs()
144
+ {
145
+ return encoder.getVisualActs();
146
+ }
147
+
148
+ public void layoutIfChanged()
149
+ {
150
+ if (frameChanged)
151
+ {
152
+ layout();
153
+ }
154
+ }
155
+
156
+ public void init(boolean majorChange)
157
+ {
158
+ note(new TFEvent(majorChange ? TFEvent.Type.DATABASE_CHANGE : TFEvent.Type.ACT_CHANGE, null));
159
+ }
160
+
161
+ public void note(TFEvent e)
162
+ {
163
+ ActList all = null;
164
+ if (e.type == TFEvent.Type.DATABASE_CHANGE)
165
+ {
166
+ all = model.getDB().all();
167
+ createGlobalInterval();
168
+ Interval i = guessInitialViewInterval(all, globalInterval);
169
+ setTimeBounds(i.start, i.end);
170
+ }
171
+ if (e.affectsRowSet())
172
+ {
173
+ all = model.getDB().all();
174
+ encoder.createVisualActs();
175
+ createGlobalInterval();
176
+ } else
177
+ {
178
+ encoder.createVisualActs();
179
+ }
180
+ Interval v = model.getViewInterval();
181
+ if (v != null && v.start != timeScale.getInterval().start)
182
+ {
183
+ timeScale.getInterval().translateTo(v.start);
184
+ }
185
+ updateVisuals();
186
+ }
187
+
188
+ private Interval guessInitialViewInterval(ActList acts, Interval fullRange)
189
+ {
190
+ if (acts.size() < 50)
191
+ {
192
+ return fullRange.copy();
193
+ }
194
+
195
+ Interval best = null;
196
+ int most = -1;
197
+ double d = Math.max(.1, 50. / acts.size());
198
+ d = Math.min(1. / 3, d);
199
+ for (double x = 0; x < 1 - d; x += d / 4)
200
+ {
201
+ Interval i = fullRange.subinterval(x, x + d);
202
+ TimeIntervalFilter f = new TimeIntervalFilter(i, getModel().getDB().getField(VirtualField.START));
203
+ int num = 0;
204
+ for (Act a : acts)
205
+ {
206
+ if (f.accept(a))
207
+ {
208
+ num++;
209
+ }
210
+ }
211
+ if (num > most)
212
+ {
213
+ most = num;
214
+ best = i;
215
+ }
216
+ }
217
+ return best;
218
+ }
219
+
220
+ public void updateVisuals()
221
+ {
222
+ scale = 1;
223
+ updateVisualEncoding();
224
+ layout();
225
+ }
226
+
227
+ public TFModel getModel()
228
+ {
229
+ return model;
230
+ }
231
+
232
+ public int getNumTracks()
233
+ {
234
+ return trackList.size();
235
+ }
236
+
237
+ public double layout()
238
+ {
239
+ ActList acts = model.getActs();
240
+ if (acts == null)
241
+ {
242
+ return scale;
243
+ }
244
+
245
+ double labelHeight = 30;
246
+
247
+ double min = bounds.height == 0 ? 0 : labelHeight / bounds.height;
248
+ double cellH = (double)getModel().getDisplay().getInt("timeline.item.height.min") / bounds.height;
249
+
250
+ double maxCount = 0;
251
+
252
+ // Set the minimum scale
253
+ for (TimelineTrack t : trackList)
254
+ {
255
+ //double height = t.size() * scale / (double) numShown;
256
+
257
+ maxCount = Math.max(t.size(), maxCount);
258
+ }
259
+
260
+ scale = Math.min(cellH * numShown, scale);
261
+ scale = Math.max(min * numShown / maxCount, scale);
262
+
263
+ double top = 0;
264
+ for (TimelineTrack t : trackList)
265
+ {
266
+ double height = Math.max(min, t.size() * scale / (double) numShown);
267
+ t.layout(top, height, this);
268
+ top += height;
269
+ }
270
+ fullHeight = (int) (top * bounds.height);
271
+
272
+ Collections.sort(trackList);
273
+ frameChanged = false;
274
+
275
+ return scale;
276
+ }
277
+
278
+ private void updateVisualEncoding()
279
+ {
280
+ java.util.List<VisualAct> acts = encoder.apply();
281
+
282
+ // now arrange on tracks
283
+ trackTable = new HashMap<String, TimelineTrack>();
284
+ trackList = new ArrayList<TimelineTrack>();
285
+ numShown = 0;
286
+ for (VisualAct v : acts)
287
+ {
288
+ if (!v.isVisible())
289
+ {
290
+ continue;
291
+ }
292
+ numShown++;
293
+ String s = v.getTrackString();
294
+ TimelineTrack t = trackTable.get(s);
295
+ if (t == null)
296
+ {
297
+ t = new TimelineTrack(s);
298
+ trackTable.put(s, t);
299
+ trackList.add(t);
300
+ }
301
+ t.add(v);
302
+ v.setTrack(t);
303
+ }
304
+
305
+ /*
306
+ // the following code is no longer used, but could come in handy again one day...
307
+
308
+ // If there is more than one "small" track, then we will coalesce them into
309
+ // one bigger "miscellaneous" track.
310
+ int minSize=numShown/30;//Math.max(3,numShown/30);
311
+ ArrayList<TimelineTrack> small=new ArrayList<TimelineTrack>();
312
+ for (TimelineTrack t: trackList)
313
+ {
314
+ if (t.size()<minSize)
315
+ small.add(t);
316
+ }
317
+ if (small.size()>1)
318
+ {
319
+ // create a new Track for "miscellaneous."
320
+ TimelineTrack misc=new TimelineTrack(Display.MISC_CODE);
321
+ trackList.add(misc);
322
+ trackTable.put(misc.label, misc);
323
+
324
+ // remove the old tracks.
325
+ for (TimelineTrack t:small)
326
+ {
327
+ trackList.remove(t);
328
+ trackTable.remove(t.label);
329
+ for (VisualAct v: t.visualActs)
330
+ {
331
+ v.setTrack(misc);
332
+ misc.add(v);
333
+ }
334
+ }
335
+ // sort miscellaneous items in time order.
336
+ //Collections.sort(misc.visualActs);
337
+ }
338
+ */
339
+
340
+ for (TimelineTrack t : trackList)
341
+ {
342
+ Collections.sort(t.visualActs);
343
+ }
344
+ }
345
+}