1 /*********************************************************************
3 * File : $Source: /cvsroot/ijbswa/current/src/java/org/privoxy/activityconsole/ActivityConsoleGui.java,v $
5 * Purpose : Provide the central GUI for displaying Privoxy
6 * statistics. It can be contacted either by the
7 * local machine or other machines in a network and
8 * display consolidated, tabular statistics.
10 * Copyright : Written by and Copyright (C) 2003 the SourceForge
11 * Privoxy team. http://www.privoxy.org/
13 * Based on the Internet Junkbuster originally written
14 * by and Copyright (C) 1997 Anonymous Coders and
15 * Junkbusters Corporation. http://www.junkbusters.com
17 * This program is free software; you can redistribute it
18 * and/or modify it under the terms of the GNU General
19 * Public License as published by the Free Software
20 * Foundation; either version 2 of the License, or (at
21 * your option) any later version.
23 * This program is distributed in the hope that it will
24 * be useful, but WITHOUT ANY WARRANTY; without even the
25 * implied warranty of MERCHANTABILITY or FITNESS FOR A
26 * PARTICULAR PURPOSE. See the GNU General Public
27 * License for more details.
29 * The GNU General Public License should be included with
30 * this file. If not, you can view it at
31 * http://www.gnu.org/copyleft/gpl.html
32 * or write to the Free Software Foundation, Inc., 59
33 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
36 * $Log: ActivityConsoleGui.java,v $
37 * Revision 1.1 2003/01/18 14:37:24 david__schmidt
38 * Initial checkin of directory structure and source code of the java Activity
41 *********************************************************************/
43 package org.privoxy.activityconsole;
46 import java.awt.event.*;
50 import javax.swing.border.*;
51 import javax.swing.event.*;
52 import javax.swing.table.*;
55 * The main Activity Console GUI.
56 * @author Last Modified By: $Author: david__schmidt $
57 * @version $Rev$-$Date: 2003/01/18 14:37:24 $$State: Exp $
59 public final class ActivityConsoleGui extends JFrame implements ActionListener
61 private static final String
62 COPYRIGHT = org.privoxy.activityconsole.Copyright.COPYRIGHT;
64 ActivityConsoleGui parent_;
65 ServerThread _serverThread = null;
66 private ListResourceBundle resStrings = (ListResourceBundle)ListResourceBundle.getBundle("org.privoxy.activityconsole.ActivityConsoleResources");
70 JScrollPane _tableScroller = new JScrollPane();
72 SortableTableModel _model;
74 Vector _tableColumnMap = new Vector();
76 JPanel _mainPanel = new JPanel(new GridBagLayout());
78 JMenuItem _deleteItem, _quitItem, _configItem;
79 JCheckBoxMenuItem _viewWideItem;
81 private DefaultTableCellRenderer _statRenderer = null;
85 Properties _properties = null;
88 * Constructor of the Activity Console GUI.
89 * @param arg the port to serve connections on - as an int parsed from the String
91 public ActivityConsoleGui(String arg)
95 addWindowListener(new WindowCloseMonitor());
97 JMenuBar menuBar = new JMenuBar();
99 JMenu menuFile = new JMenu(resStrings.getString("menuFile"));
100 MenuAction quitAction = new MenuAction(resStrings.getString("menuFileQuit"));
101 _quitItem = menuFile.add(quitAction);
102 menuBar.add(menuFile);
104 JMenu menuEdit = new JMenu(resStrings.getString("menuEdit"));
105 _configItem = menuEdit.add(new MenuAction(resStrings.getString("menuEditConfig")));
106 _deleteItem = menuEdit.add(new MenuAction(resStrings.getString("menuEditDelete")));
107 menuBar.add(menuEdit);
109 JMenu menuView = new JMenu(resStrings.getString("menuView"));
110 _viewWideItem = new JCheckBoxMenuItem(resStrings.getString("menuViewWide"));
111 _viewWideItem.addActionListener(this);
112 menuView.add(_viewWideItem);
113 menuBar.add(menuView);
115 this.setJMenuBar(menuBar);
116 _deleteItem.setEnabled(false);
122 _port = Integer.parseInt(arg);
132 * The cell renderer for the StatWidget Component - simply returns the component
133 * itself. Additionally, it has the extra hack of telling the StatWidget where
134 * it is in the table so it can update itself again when it comes time to flash.
136 _statRenderer = new DefaultTableCellRenderer()
138 public Component getTableCellRendererComponent(JTable table,
145 /* Housekeeping: keep track of the row, column and table references as we go */
146 ((StatWidget)value).setRowColTable(row,column,table);
147 return(Component)value;
150 public void setValue(Object value)
155 color = (Color)value;
157 catch (ClassCastException e)
161 setBackground(color);
167 ActivityConsoleGuiUtil.constrain(_mainPanel, _tableScroller,
168 1, 1, // X, Y Coordinates
169 1, 1, // Grid width, height
170 GridBagConstraints.BOTH, // Fill value
171 GridBagConstraints.WEST, // Anchor value
172 1.0,1.0, // Weight X, Y
173 0, 0, 0, 0 ); // Top, left, bottom, right insets
175 this.getContentPane().add(_mainPanel, BorderLayout.CENTER);
179 _table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth(),50));
184 _serverThread = new ServerThread(this, _port);
185 _serverThread.start();
188 setBounds(ActivityConsoleGuiUtil.center(this.getSize()));
193 * Updates the title bar with the port currently being served.
194 * @param port the port being served
196 public void updateTitle(int port)
198 String title = resStrings.getString("guiTitle");
200 title = StringUtil.replaceSubstring(title,"%1",""+port);
204 public void actionPerformed(ActionEvent e)
206 if (e.getSource() == _viewWideItem)
208 setProperty("AC.detailedColumnSet", _viewWideItem.isSelected());
211 _table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth(),50));
216 class MenuAction extends AbstractAction
218 public MenuAction(String text)
223 public MenuAction(String text, Icon icon)
228 public void actionPerformed(ActionEvent e)
230 if (e.getSource() == _quitItem)
233 parent_.setVisible(false);
237 else if (e.getSource() == _deleteItem)
241 else if (e.getSource() == _configItem)
243 changeServerAction();
249 * Asks the user to specify a new port to serve
251 public void changeServerAction()
254 String message = resStrings.getString("guiNewPortPrompt");
255 message = StringUtil.replaceSubstring(message,"%1",""+_port);
257 String inputValue = JOptionPane.showInputDialog(this,
259 resStrings.getString("guiNewPortTitle"),
260 JOptionPane.QUESTION_MESSAGE);
261 if (inputValue != null)
264 port = Integer.parseInt(inputValue);
271 JOptionPane.showMessageDialog(null, resStrings.getString("guiNewPortErrorPrompt"), resStrings.getString("guiNewPortErrorTitle"), JOptionPane.ERROR_MESSAGE);
276 if (_serverThread != null)
278 _serverThread.doClose();
279 _serverThread.interrupt();
280 _serverThread = null;
283 _serverThread = new ServerThread(parent_, port);
284 _serverThread.start();
291 * Deletes the "selected" row after seeking confirmation
293 public void deleteAction()
295 int numSelections = _table.getSelectedRowCount();
296 int selRow = _table.getSelectedRow();
297 if (numSelections > 0)
300 (selRow < _table.getRowCount()))
302 /* Ask for confirmation */
303 String message = resStrings.getString("guiDeleteConfirmPrompt");
304 message = StringUtil.replaceSubstring(message,"%1",(String)_model.getValueAt(selRow,0));
305 int ret = JOptionPane.showConfirmDialog(null,
307 resStrings.getString("guiDeleteConfirmTitle"),
308 JOptionPane.YES_NO_OPTION);
309 if (ret == JOptionPane.YES_OPTION)
311 _model.removeRow(selRow);
318 * Builds a new table with the requested columns.
320 public void initTable()
322 _model = new SortableTableModel(new Vector(), getColumnNames());
323 _table = new JTable(_model);
324 _table.setPreferredScrollableViewportSize(new Dimension(800,50));
325 _table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
326 _table.setCellSelectionEnabled(false);
327 _table.setRowSelectionAllowed(false);
328 SortButtonRenderer headerRenderer = new SortButtonRenderer();
329 TableColumnModel cm = _table.getColumnModel();
330 /* Make the first column twice the width of the others. It shows bigger stuff. */
331 cm.getColumn(0).setPreferredWidth(cm.getColumn(0).getPreferredWidth() * 2);
332 cm.getColumn(0).setHeaderRenderer(headerRenderer);
333 for (int i = 1;i<_model.getColumnCount();i++)
335 cm.getColumn(i).setPreferredWidth((int)(cm.getColumn(i).getPreferredWidth() * 1));
336 cm.getColumn(i).setCellRenderer(_statRenderer);
337 cm.getColumn(i).setHeaderRenderer(headerRenderer);
340 JTableHeader header = _table.getTableHeader();
341 header.addMouseListener(new HeaderListener(header,headerRenderer));
343 ListSelectionModel csm = _table.getSelectionModel();
344 csm.addListSelectionListener(new SelectedListener(csm));
346 _tableScroller.setViewportView(_table);
351 * Retrieves the names of the column headers.
352 * @return Vector the set of column names. It also has the side-effect of adding
353 * entries to the global column mapping Vector where we map the staus integer identifiers
354 * to the column positions and names. Should probably fix that too.
356 public Vector getColumnNames()
358 Vector names = new Vector();
359 _tableColumnMap = getUserColumnNames();
361 names.addElement(resStrings.getString("guiDefaultColumn0"));
362 for (int i = 0; i < _tableColumnMap.size(); i ++)
364 names.addElement(((ColumnRef)_tableColumnMap.elementAt(i)).getDescription());
371 * Builds a map of columns based on the properties file.
372 * If it is somehow unsuitable, the default table will be built.
373 * This should be made to read a properties file. FIXME.
374 * @return Vector The vector of column name-to-stat-ID mappings
376 public Vector getUserColumnNames()
380 map = getDefaultColumnNames();
386 * Builds a default map of columns.
387 * @return Vector The vector of column name-to-stat-ID mappings
389 public Vector getDefaultColumnNames()
391 Vector map = new Vector();
392 boolean detailedList = getProperty("AC.detailedColumnSet", false);
393 // In case they didn't have the preference set... set it.
394 setProperty("AC.detailedColumnSet", detailedList);
395 _viewWideItem.setSelected(detailedList);
397 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn1"),1));
398 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn2"),2));
399 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn3"),3));
402 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn4"),4));
403 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn5"),5));
404 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn6"),6));
405 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn7"),7));
406 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn8"),8));
407 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn9"),9));
408 map.addElement(new ColumnRef(resStrings.getString("guiDefaultColumn10"),10));
414 * Parses a String of statistics coming from Privoxy.
415 * @param line The statistics string sent from Privoxy
416 * @param from the hostname that sent the statistics
418 public void updateStats(String line, String from)
421 * An example line of data:
422 * 0:8118 1:0 2:0 3:0 4:0 5:0 6:0 7:0 8:0 9:0 10:0
425 String tableKey = "", key_str, value_str, token;
426 StringTokenizer colonToken;
427 StringTokenizer spaceTokens = new StringTokenizer(line);
428 Vector stats = new Vector();
430 while (spaceTokens.hasMoreTokens())
432 token = spaceTokens.nextToken();
433 colonToken = new StringTokenizer(token,":");
434 if (colonToken.hasMoreTokens())
436 key_str = null; value_str = null;
439 /* First token is the key */
440 key_str = colonToken.nextToken();
443 key = Integer.parseInt(key_str);
445 catch (NumberFormatException n)
450 if ((colonToken.hasMoreTokens()) && (key > -1))
452 /* Next token, if present, is the value */
453 value_str = colonToken.nextToken();
457 * The key to the table row is the concatenation of the serving
458 * IP address string, a full colon, and the port string.
460 tableKey = from + ":" + value_str;
464 value = Integer.parseInt(value_str);
465 stats.addElement((Object)(new Stat(key, value)));
467 catch (NumberFormatException n)
474 if ((tableKey.compareTo("") != 0) && (stats.size() > 0))
476 updateTable(tableKey, stats);
477 stats.removeAllElements();
483 * Updates (or creates) a line in the table representing the incoming packet of stats.
484 * @param tableKey Our key to a unique table row: the hostname concatenated with the Privoxy port being served.
485 * @param stats Vector of statistics elements
487 public void updateTable(String tableKey, Vector stats)
489 boolean found = false;
490 for (int i = 0; i < _model.getRowCount(); i++)
492 if (((String)_model.getValueAt(i,_table.convertColumnIndexToView(0))).compareTo(tableKey) == 0)
494 updateTableEntry(i, stats);
498 /* If we can't find one in the table already... */
500 createTableEntry(tableKey, stats);
504 * Creates a line in the table representing the incoming packet of stats.
505 * @param tableKey Our key to a unique table row: the hostname concatenated with the Privoxy port being served.
506 * @param stats Vector of statistics elements
508 public void createTableEntry(String tableKey, Vector stats)
511 Vector row = new Vector();
512 boolean added = false;
514 row.addElement(tableKey);
517 * If we have a key (in stats) that maps to a key in the _tableColumnMap,
518 * then we add it to the vector destined for the table.
520 for (i = 0; i < _tableColumnMap.size(); i ++)
522 for (j = 0; j < stats.size(); j++)
524 if (((Stat)stats.elementAt(j)).getKey() == ((ColumnRef)_tableColumnMap.elementAt(i)).getKey())
526 row.addElement(new StatWidget(((Stat)stats.elementAt(j)).getValue(),500));
532 row.addElement(new StatWidget(0,500));
541 * Updates a line in the table by tweaking the StatWidgets.
542 * @param row the table row if the StatWidget
543 * @param stats The Vector of Stat elements to update the table row with
545 public void updateTableEntry(int row, Vector stats)
549 for (i = 0; i < _tableColumnMap.size(); i ++)
551 for (j = 0; j < stats.size(); j++)
553 if (((Stat)stats.elementAt(j)).getKey() == ((ColumnRef)_tableColumnMap.elementAt(i)).getKey())
555 ((StatWidget)_model.getValueAt(row,i+1)).updateValue(((Stat)stats.elementAt(j)).getValue());
556 stats.removeElementAt(j);
564 * Load up the properties
567 private void loadProperties()
569 _properties = new Properties();
572 _properties.load(new FileInputStream("ActivityConsole.properties"));
576 // System.out.println(t);
577 // No properties file... use hardcoded defaults.
582 * Save the properties
585 private void saveProperties()
589 _properties.store(new FileOutputStream("ActivityConsole.properties"),resStrings.getString("guiPropertiesFileHeader"));
593 System.out.println(t);
600 public void setProperty(String key, String value)
602 _properties.setProperty(key,value);
606 * Set a boolean property
608 public void setProperty(String key, boolean value)
610 Boolean bVal = new Boolean(value);
611 _properties.setProperty(key,(String)bVal.toString());
617 public String getProperty(String key, String defaultValue)
619 return _properties.getProperty(key,defaultValue);
623 * Get a boolean property
625 public boolean getProperty(String key, boolean defaultValue)
627 String sDefaultValue;
629 if (defaultValue == false)
630 sDefaultValue = "false";
632 sDefaultValue = "true";
633 property = _properties.getProperty(key,sDefaultValue);
634 if (property.compareToIgnoreCase("true") == 0)
643 public void removeProperty(String key)
645 _properties.remove(key);
649 * Worker class to offer a clickable table header for sorting.
651 class HeaderListener extends MouseAdapter
654 SortButtonRenderer renderer;
656 HeaderListener(JTableHeader header,SortButtonRenderer renderer)
658 this.header = header;
659 this.renderer = renderer;
662 public void mousePressed(MouseEvent e)
664 Point click = e.getPoint();
665 int col = header.columnAtPoint(click);
666 int margin1, margin2;
667 int sortCol = header.getTable().convertColumnIndexToModel(col);
669 /* Don't perform the sort if the user is just trying to resize the columns. */
670 margin1 = header.columnAtPoint(new Point(click.x+3,click.y));
671 margin2 = header.columnAtPoint(new Point(click.x-3,click.y));
672 if ((col == margin1) && (col == margin2))
674 renderer.setPressedColumn(col);
675 renderer.setSelectedColumn(col);
678 if (header.getTable().isEditing())
680 header.getTable().getCellEditor().stopCellEditing();
684 if (SortButtonRenderer.DOWN == renderer.getState(col))
692 ((SortableTableModel)header.getTable().getModel())
693 .sortByColumn(sortCol, isAscent);
697 public void mouseReleased(MouseEvent e)
699 int col = header.columnAtPoint(e.getPoint());
700 renderer.setPressedColumn(-1);
706 * Worker class to tell the menu when it's OK to delete a row (i.e. when a row gets
707 * selected). This doesn't work reliably, but it's better than nothing.
709 public class SelectedListener implements ListSelectionListener
711 ListSelectionModel model;
713 public SelectedListener(ListSelectionModel lsm)
718 public void valueChanged(ListSelectionEvent lse)
720 // NOTE - keep this in sync with columnSelectionChanged below...
721 int numSelections = _table.getSelectedRowCount();
722 int selRow = _table.getSelectedRow();
723 if (numSelections > 0)
726 (selRow < _table.getRowCount()))
728 _deleteItem.setEnabled(true);
731 _deleteItem.setEnabled(false);
734 _deleteItem.setEnabled(false);
736 public void columnSelectionChanged(ListSelectionEvent lse)
738 // NOTE - keep this in sync with valueChanged above...
739 int numSelections = _table.getSelectedRowCount();
740 int selRow = _table.getSelectedRow();
741 if (numSelections > 0)
744 (selRow < _table.getRowCount()))
746 _deleteItem.setEnabled(true);
749 _deleteItem.setEnabled(false);
752 _deleteItem.setEnabled(false);
757 * Watch for the window closing event. Dunno why swing doesn't handle this better natively.
759 public class WindowCloseMonitor extends WindowAdapter
761 public void windowClosing(WindowEvent e)
764 Window w = e.getWindow();