.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 1 | +package timeflow.data.db.filter; |
---|
| 2 | + |
---|
| 3 | +public interface ValueFilter { |
---|
| 4 | + public boolean ok(Object o); |
---|
| 5 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | + |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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> "); |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 1 | +package timeflow.model; |
---|
| 2 | + |
---|
| 3 | +public interface TFListener { |
---|
| 4 | + public void note(TFEvent e); |
---|
| 5 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +}
|
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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> "); |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | + |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|
.. | .. |
---|
| 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 | +} |
---|