//
// CabSyncViewer.java
// S.Fraize July 2002
// TODO: when autosync is enabled, trigger a sync. (Could trigger a refresh,
// but that only detects state *changes*, so if it's already aware of
// stuff that's out of sync, the next refresh won't actually trigger
// a sync).
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import javax.swing.event.*;
import org.okip.service.filing.api.*;
public class CabSyncViewer extends JFrame
{
static Logger log = new Logger(CabSyncViewer.class);
JTree jtree;
DragTable localTable;
DragTable remoteTable;
JLabel statusBar;
SyncList syncList;
JLabel leftLabel = new JLabel("lv", SwingConstants.CENTER);
JLabel rightLabel = new JLabel("rv", SwingConstants.CENTER);
SyncListTCModel leftTCModel;
SyncListTCModel rightTCModel;
boolean showGhosts = false;
private static final Cursor dragCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
private static final Cursor waitCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
private static final Cursor defaultCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
/*
* DragTable is designed to have EXACTLY TWO INSTANCES.
* This is for a hacked-up drag&drop implementation
* to get around JTable mouse grabs, and until we
* could do a nice D&D with the new stuff in Java 1.4.
*/
static boolean dragging = false;
static DragTable firstTable;
class DragTable extends JTable
{
DragTable otherTable = null;
boolean containsMouse = false;
public DragTable(TableModel dm, TableColumnModel cm)
{
super(dm, cm);
//defaultCursor = getCursor();
if (firstTable == null) {
firstTable = this;
} else {
otherTable = firstTable;
firstTable.otherTable = this;
}
}
//todo: we also need to be able to drop into the table container!
// (in case there's, say, 3 items in the table, and then mostly
// white space below which currently won't be a drop target!)
protected void processMouseEvent(MouseEvent e)
{
if (log.d) log.debug("processMouseEvent: " + e.paramString());
int id = e.getID();
if (id == MouseEvent.MOUSE_ENTERED) {
containsMouse = true;
} else if (id == MouseEvent.MOUSE_EXITED) {
containsMouse = false;
} else if (id == MouseEvent.MOUSE_PRESSED) {
int r = rowAtPoint(e.getPoint());
// if we've pressed the mouse within the selection
// zone, start dragging...
if (r >= getSelectionModel().getMinSelectionIndex() &&
r <= getSelectionModel().getMaxSelectionIndex())
startDragging();
else
super.processMouseEvent(e);
} else if (dragging && id == MouseEvent.MOUSE_RELEASED) {
if (otherTable.containsMouse) {
if (log.v) log.verbose("Drag SUCCEDED");
exportSelected(this);
}
else {
if (log.v) log.verbose("Drag ABORTED");
}
stopDragging();
super.processMouseEvent(e);
} else
super.processMouseEvent(e);
}
protected void processMouseMotionEvent(MouseEvent e)
{
int id = e.getID();
if (dragging && id == MouseEvent.MOUSE_DRAGGED) {
if (log.d) log.debug("drag");
} else
super.processMouseMotionEvent(e);
}
void startDragging()
{
dragging = true;
if (log.d) log.debug("DRAG START");
getParent().setCursor(dragCursor);
}
void stopDragging()
{
dragging = false;
if (log.d) log.debug("DRAG STOP");
getParent().setCursor(defaultCursor);
}
}
class SyncListTreeNode extends DefaultMutableTreeNode
{
// a tree node capable of creating a data-model based
// on its recalled object.
// a TreeNode, initialzed with some object that can generate a
// list of items that can be fed to a given factory to
// produduce an AbstractTableModel of some kind. Could use
// this generic call with CabViewer if we used a
// TableModelFactory and an additional name factory to be used
// in toString() (to ge ta displayable name from the user
// object); OR, if it was passed in as an argument.
SyncListTableModel dataModel;
SyncListTableModel remoteDataModel;
String displayName;
SyncListTreeNode(SyncList sl)
{
super(sl);
if (log.d) log.debug(this, "new with " + sl);
}
public void setDisplayName(String name)
{
this.displayName = name;
}
private SyncList getSyncList()
{
return (SyncList) getUserObject();
}
private SyncList getRemoteSyncList()
{
return getSyncList().getRemoteList();
}
SyncListTableModel getDataModel()
{
if (this.dataModel == null) {
if (log.d) log.debug(this, "getDataModel: creating SyncListTableModel for " + getSyncList());
this.dataModel = new SyncListTableModel(getSyncList());//todo: propigate log bits
buildTree();
}
return this.dataModel;
}
SyncListTableModel getRemoteDataModel()
{
if (this.remoteDataModel == null) {
if (log.d) log.debug(this, "getRemoteDataModel: creating SyncListTableModel for " + getRemoteSyncList());
this.remoteDataModel = new SyncListTableModel(getRemoteSyncList());//todo: propigate log bits
}
return this.remoteDataModel;
}
private void buildTree()
{
Iterator i = getSyncList().iterator();
while (i.hasNext()) {
SyncEntry se = (SyncEntry) i.next();
CabinetEntry ce = se.getCabinetEntry();
//if (log.d) log.debug("getDataModel scanning " + se);
//if (se instanceof SyncList) {
if (ce instanceof Cabinet) {
Cabinet c = (Cabinet) ce;
if (log.d) log.debug("will want new SyncListTreeNode for " + se);
//this.add(new SyncListTreeNode((SyncList)se);
}
}
}
/*
* toString is very functional here -- it's
* used to produce the text that the tree widget displays.
*/
public String toString()
{
if (displayName != null)
return displayName;
try {
// won't work once we're recursing...
return getUserObject().toString();
} catch (Exception e) {
return getUserObject().getClass().toString();
}
}
}
static class BooleanRenderer extends JCheckBox implements TableCellRenderer
{
public BooleanRenderer() {
super();
setHorizontalAlignment(JLabel.CENTER);
//setRequestFocusEnabled(false);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
}
else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
if (value != null) {
if (value instanceof Boolean)
setSelected(((Boolean)value).booleanValue());
else
log.errout("NOT BOOLEAN: " + value.getClass().getName() + ": " + value);
}
return this;
}
}
/*
* The main renderer
*/
final static int COL_NAME = 0;
final static int COL_SIZE = 1;
final static int COL_MODTIME = 2;
final static int COL_STATUS = 3;
final static int COL_DOSYNC = 4;
final static int COL_OWNER = 5;
static DateFormat dateFormatter = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss");//fix: this ignores locale
class SyncEntryRenderer extends DefaultTableCellRenderer
{
int col;
public SyncEntryRenderer(int index)
{
col = index;
if (col == COL_MODTIME || col == COL_SIZE)
setHorizontalAlignment(SwingConstants.RIGHT);
}
private boolean looping = false;
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
if (value == null)
return super.getTableCellRendererComponent(table, "<null>", isSelected, hasFocus, row, column);
SyncEntry se = (SyncEntry) value;
// if a file goes missing, sometimes value doesn't get set below before
// going on to call super.getTableCellRendererComponent (why?) so
// we make sure it doesn't call an innapropraite toString() on a
// SyncEntry by immediately setting value to something innocous
// here.
value = "-";
boolean isGhost = se.isGhost();
boolean isBS = se.isByteStore();
ByteStore bs = isBS ? se.getByteStore() : null;
//CabinetEntry ce = se.getCabinetEntry();
if (isGhost)
setForeground(ghostColor);
else
setForeground(defaultColor);
try {
if (col == COL_NAME) {
String label = se.getName();
if (se.isCabinet())
label += "/";
if (!isGhost) {
//if (syncList.isImportAll() || se.isSyncRequested())
// add above condition if you only want to see the
// status color of items explicitly marked for syncing.
setForeground(getStatusColor(se));
}
value = label;
} else if (col == COL_MODTIME) {
//if (isGhost) value = ""; else
if (isBS) {
long time;
try {
time = bs.getLastModifiedTime();
value = dateFormatter.format(new Date(time));
} catch (FilingException e) {
// if a file is deleted out from under us,
// we start getting an exception here -- so
// we attempt a single refresh if we detect this.
// todo: this is still a bit dangerous -- we
// should really set a bit and find a control
// point with a larger granularity. Or perhaps
// we could initiate the refresh only if we
// verified that the bytestore no longer exists.
if (!looping && !syncList.isSyncRunning()) {
looping = true;
if (log.d) log.debug(this, "loop-protected syncList refresh");
try {
syncList.refresh();
// todo: only call on the LOCAL list,
// and let it master the process?
} catch (Exception ex) {
looping = false;
throw ex;
}
looping = false;
} else
//throw e;
value="-";
}
}
else
value = "";
} else if (col == COL_SIZE) {
//if (isGhost) value = ""; else
if (isBS)
value = new Long(bs.length());
else
value = "";
} else if (col == COL_STATUS)
value = " " + se.getStatusDescription();
// default: leave value alone
} catch (Exception e) {
log.errout(e, "rendering");
value = "<"+e.getMessage()+">";
}
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
};
class SyncListTCModel extends DefaultTableColumnModel
{
TableColumn syncColumn;
boolean syncColumnShowing = false;
SyncListTCModel(boolean local)
{
createColumns(local);
}
private void createColumns(boolean local)
{
addTableColumn(COL_NAME, "Entry Name", 800);
addTableColumn(COL_SIZE, "Size", 350);
addTableColumn(COL_MODTIME, "Modified", 700);
if (local) {
addTableColumn(COL_STATUS, "Status", 400);
syncColumn = addTableColumn(COL_DOSYNC, "Sync", 150);
syncColumnShowing = true;
syncColumn.setCellRenderer(new BooleanRenderer());
}
//addTableColumn(COL_OWNER, "Owner", 500);
}
public boolean isSyncShowing()
{
return syncColumnShowing;
}
TableColumn addTableColumn(int index, String name, int width)
{
TableColumn tc = new TableColumn(index, width);
// could also initialize the TableColumn here with renderers instead of
// relying on getColumnClass to map to them. That actually feels a bit cleaner.
tc.setHeaderValue(name);
tc.setCellRenderer(new SyncEntryRenderer(index));
addColumn(tc);
return tc;
}
public void showSyncColumn(boolean showit)
{
if (showit) {
if (!syncColumnShowing) {
addColumn(syncColumn);
syncColumnShowing = true;
}
} else if (syncColumnShowing) {
removeColumn(syncColumn);
syncColumnShowing = false;
}
}
}
class SyncListTableModel extends AbstractTableModel
implements ChangeListener
{
SyncList syncList;
ArrayList entries;
SyncListTableModel(SyncList sl)
{
if (log.d) log.debug(this, "new with " + sl);
this.entries = new ArrayList();
this.refreshModel(sl);
}
public void stateChanged(ChangeEvent e)
{
if (log.d) log.debug(this, "stateChanged: " + e);
if (e instanceof SyncListChangeEvent) {
SyncListChangeEvent se = (SyncListChangeEvent) e;
if (se.changes > 0)
refreshModel();
else // se.updates>0
fireTableDataChanged();
if (se.tosync > 0 && !syncList.isSyncRunning() && syncList.isAutoSync()) {
if (log.v) log.verbose(se + ": initiating auto-synchronization");
doAutoSync();
}
}
else if (e.getSource() instanceof SyncEntry) {
//currently, we get the syncentry that changed
// if one did durning a synchronization
fireTableDataChanged();
}
else
refreshModel();
}
SyncListTCModel columnModel;
public SyncListTCModel getTableColumnModel()
{
if (columnModel == null)
columnModel = new SyncListTCModel(true);
return columnModel;
}
public boolean isCellEditable(int rIndex, int cIndex) {
if (cIndex == COL_DOSYNC) {
SyncEntry se = getEntryAt(rIndex);
if (se.isGhost())
return syncList.isImportAll() == false;
return true;
}
return false;
}
public void setValueAt(Object aValue, int rIndex, int cIndex)
{
//if (log.d) log.debug("setValueAt r="+rIndex + " c="+cIndex + " " + aValue);
SyncEntry se = getEntryAt(rIndex);
if (se == null)
log.errout("setValueAt, no SyncEntry at row " + rIndex);
else
syncList.setSyncRequested(se, ((Boolean)aValue).booleanValue());
}
/* This is mainly being ignored because we use our own
* renderer now, tho it's still needed for the Boolean
* editor, as we don't define our own editors.
*/
public Class getColumnClass(int col) {
if (col == COL_DOSYNC) return Boolean.class; // required for editing
//if (col == COL_NAME) return SyncEntry.class;
//if (col == COL_SIZE) return Integer.class; // this will cause align-right
//if (col == COL_MODTIME) return Date.class;
return super.getColumnClass(col);
}
// since we're using a TableColumnModel, nobody should be calling getColumnCount.
public int getColumnCount() { return 0; }
// public int getColumnCount() { throw new RuntimeException("getColumnCount:where was TableColumnModel?"); }
public int getRowCount() { return entries.size(); }
public Object getValueAt(int row, int col)
{
SyncEntry se = getEntryAt(row);
if (se == null) {
//if (log.d) log.debug("getValueAt: no entry at row " + row);
return null;
}
try {
return getColumnData(se, col);
} catch (FilingException e) {
log.errout(e);
return e.getMessage();
}
}
private SyncEntry getEntryAt(int row)
{
try {
return (SyncEntry) entries.get(row);
} catch (java.lang.IndexOutOfBoundsException e) {
// got this once I think in a race-condition
return null;
}
}
// This almost always proudces just a SyncEntry
// now that we handle evertyhing in the renderer
// (because it needs all that information). When
// we implement sorting, we may need to move some
// of the raw data production back down here.
private Object getColumnData(SyncEntry se, int col)
throws FilingException
{
Object data = null;
try {
if (col == COL_DOSYNC) {
if (syncList.isImportAll())
data = Boolean.TRUE;
else
data = new Boolean(se.isSyncRequested());
} else
data = se;
} catch (Exception e) {
log.errout(e);
data = "<" + e.getMessage() + ">";
}
return data == null ? "-" : data;
}
public void refreshModel()
{
refreshModel(this.syncList);
}
// note that ultimately this will be a SORTED
// list, so we do need to keep our own array lst.
public void refreshModel(SyncList sl)
{
/*
* if there are any additions or deletions to
* the SyncList, we need to reflect those in our
* arraylist. As this shouldn't happen often
* (when sombody is modifying a cabinet out from
* under you) we just rebuild the whole list.
*/
if (log.d) log.debug(this, "refreshModel: " + sl);
this.entries.clear();
Iterator i = sl.iterator();
while (i.hasNext()) {
SyncEntry se = (SyncEntry) i.next();
if (showGhosts || !se.isGhost())
entries.add(se);
}
setSyncList(sl);
// todo.perf: only fire if something actually changed...
if (log.d) log.debug(this, "refreshModel: fireTableDataChanged " + sl);
fireTableDataChanged();
}
private void setSyncList(SyncList sl)
{
if (syncList != sl) {
if (syncList != null)
syncList.removeChangeListener(this);
syncList = sl;
syncList.addChangeListener(this);
}
}
};
//------------------------------------------------------------------
// CabSyncViewer
//------------------------------------------------------------------
CSAction aRefresh = new CSAction("Refresh");
CSAction aSync = new CSAction("Synchronize");
CSAction aCancel = new CSAction("Cancel");
CSAction aAutoSync = new CSAction("AutoSync");
AbstractButton tbAutoSync = new JCheckBox(aAutoSync);
AbstractButton tbRefresh = new JButton(aRefresh);
// file menu actions
CSAction aOpen = new CSAction("Open...");
CSAction aQuit = new CSAction("Quit");
// edit menu actions
CSAction aCopy = new CSAction("Copy");
CSAction aPaste = new CSAction("Paste");
CSAction aClear = new CSAction("Clear");
CSAction aSelectAll = new CSAction("Select All");
CSAction aSyncSelected = new CSAction("Sync Selected");
CSAction aMarkSync = new CSAction("Mark for Sync");
CSAction aUnmarkSync = new CSAction("Unmark for Sync");
JCheckBox leftImportAll = new JCheckBox("Sync All");
JCheckBox rightImportAll = new JCheckBox("Import All");
CSAction aUploadOnly = new CSAction("Upload ->");
CSAction aDownloadOnly = new CSAction("<- Download");
protected JMenu editMenu = new JMenu("Edit");
protected JMenuItem miCopy;
protected JMenuItem miPaste;
protected JMenuItem miClear;
protected JMenuItem miSelectAll;
CSAction defaultActionHandler = new CSAction("default");
public CabSyncViewer(SyncList syncList)
{
super("OKI Cabinet Synchronizer");
if (log.d) log.debug("new with " + syncList);
Cabinet cabinet = syncList.getLocalCabinet();
this.syncList = syncList;
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(new Dimension(1024,640));
this.setLocation(100,100);
this.addMenus();
this.setVisible(true);// show something right away
SyncListTreeNode rootNode;
rootNode = new SyncListTreeNode(syncList);
String pathName;
try {
pathName = cabinet.getPath();
} catch (FilingException e) {
log.errout(e);
pathName = "<" + e.getMessage() + ">";
}
rootNode.setDisplayName(pathName);
/*
* create the JTree
*/
//TODO: don't show the JTree unless the cabinets
// you're syncing have subdirectories.
this.jtree = new JTree(rootNode, true);
addJTreeListeners();
/*
* create the local JTable -- and we need a table-column model for that
*/
Color gridColor = new Color(224,224,224);
// todo: cleanup getting the data model
// ALSO: set up updaters for remote list -- we're not seeing changes..
SyncListTableModel dataModel = (SyncListTableModel) rootNode.getDataModel();
leftTCModel = new SyncListTCModel(true);
//leftTCModel.setColumnMargin(10);// this makes for ugly breaks in line-selection indicator tho...
this.localTable = new DragTable(dataModel, leftTCModel);
this.localTable.setGridColor(gridColor);
//addTableRenderers(localTable);
/*
* create the remote JTable
*/
dataModel = rootNode.getRemoteDataModel();
rightTCModel = new SyncListTCModel(false);
this.remoteTable = new DragTable(dataModel, rightTCModel);
this.remoteTable.setGridColor(gridColor);
//addTableRenderers(remoteTable);
/*
* set up the two tables with scrollers & in a splitPane
*/
JSplitPane tableSplit = new JSplitPane();
final JScrollPane leftScroller = new JScrollPane(localTable);
final JScrollPane rightScroller = new JScrollPane(remoteTable);
final Container leftView = new Container();
final Container rightView = new Container();
// setLeftView(syncList, null);
// todo: okay, make a class that handles all the left/right common
// stuff -- really a sync list viewer.
// TODO: if you ever go back to single-pane view (local list only)
// you'd need an import all AND an export all to enable
// full sync functionality!
JPanel leftBar = new JPanel();
leftBar.setLayout(new BoxLayout(leftBar, BoxLayout.X_AXIS));
leftImportAll.addActionListener(defaultActionHandler);
leftImportAll.setRequestFocusEnabled(false);
leftBar.add(Box.createHorizontalGlue());
leftBar.add(leftLabel);
leftBar.add(Box.createHorizontalGlue());
leftBar.add(leftImportAll);
leftBar.add(Box.createHorizontalGlue());
JButton leb = new JButton(aUploadOnly);
leb.setForeground(darkGreen);
leb.setRequestFocusEnabled(false);
leftBar.add(leb);
leftBar.add(Box.createHorizontalGlue());
JPanel rightBar = new JPanel();
rightBar.setLayout(new BoxLayout(rightBar, BoxLayout.X_AXIS));
rightImportAll.addActionListener(defaultActionHandler);
rightImportAll.setRequestFocusEnabled(false);
rightBar.add(Box.createHorizontalGlue());
JButton b = new JButton(aDownloadOnly);
b.setForeground(darkGreen);
b.setRequestFocusEnabled(false);
rightBar.add(b);
rightBar.add(Box.createHorizontalGlue());
//rightBar.add(rightImportAll);
rightBar.add(Box.createHorizontalGlue());
rightBar.add(rightLabel);
rightBar.add(Box.createHorizontalGlue());
leftView.setLayout(new BorderLayout());
leftView.add(leftBar, BorderLayout.NORTH);
leftView.add(leftScroller, BorderLayout.CENTER);
// setRightView(syncList, null);
rightView.setLayout(new BorderLayout());
rightView.add(rightBar, BorderLayout.NORTH);
rightView.add(rightScroller, BorderLayout.CENTER);
tableSplit.setLeftComponent(leftView);
tableSplit.setRightComponent(rightView);
tableSplit.setOneTouchExpandable(true);
tableSplit.setResizeWeight(0.7);
MouseInputAdapter mouseListener = new MouseInputAdapter() {
public void mouseClicked(MouseEvent e)
{
if (log.d) log.debug("mouseClicked (clicks= "+ e.getClickCount() + ") on " + e.getComponent());
}
public void mousePressed(MouseEvent e)
{
if (log.d) log.debug("mousePressed " + e);
Component c = e.getComponent();
// clicking anywhere in the container NOT
// on the table is a short-cut for de-select
// all. E.g., clicking on the title (the
// name of the cabinet)
if (c != remoteTable)
remoteTable.clearSelection();
if (c != localTable)
localTable.clearSelection();
if (e.getComponent() == leftView) {
localTable.requestFocus();
} else if (e.getComponent() == rightView) {
remoteTable.requestFocus();
}
}
// Part of our drag&drop hack...
public void mouseEntered(MouseEvent e) {
if (e.getComponent() == leftView)
localTable.containsMouse = true;
else if (e.getComponent() == rightView)
remoteTable.containsMouse = true;
}
public void mouseExited(MouseEvent e) {
if (e.getComponent() == leftView)
localTable.containsMouse = false;
else if (e.getComponent() == rightView)
remoteTable.containsMouse = false;
}
};
leftView.addMouseListener(mouseListener);
rightView.addMouseListener(mouseListener);
localTable.addMouseListener(mouseListener);
remoteTable.addMouseListener(mouseListener);
setView(syncList);
/*
* create the tree/table(s) splitpane & subcomponents with scrollers
*/
if (false){
JSplitPane splitPane = new JSplitPane();
JScrollPane treeScroller = new JScrollPane(jtree);
splitPane.setResizeWeight(0.25); // 25% space to the left component
splitPane.setResizeWeight(0.0);
splitPane.setContinuousLayout(true);
splitPane.setOneTouchExpandable(true);
//splitPane.setLeftComponent(treeScroller);
splitPane.setLeftComponent(new Label(""));
splitPane.setRightComponent(tableSplit);
splitPane.setDividerLocation(0);// start collapsed for now
}
/*
* layout all the application components
*/
statusBar = new JLabel("OKI Cabinet Synchronization demo.");
statusBar.setBorder(new EmptyBorder(1, 4, 2, 0));//too crowded by default
Container pane = getContentPane();
pane.setLayout(new BorderLayout());
pane.add(getToolBar(), BorderLayout.NORTH);
// pane.add(splitPane, BorderLayout.CENTER);
pane.add(tableSplit, BorderLayout.CENTER);
pane.add(statusBar, BorderLayout.SOUTH);
setSyncAllEnabled(syncList.isImportAll());
this.constructing = false;
}
private String abbrevName(String s)
{
final int max = 30;
String base = null;
int i;
if ((i=s.indexOf(':')) > 0) {
base = s.substring(0, i);
s = s.substring(i);
}
if (s.length() > max)
s = "..." + s.substring(s.length()-max);
if (base == null)
return s;
else
return base + s;
}
public void setLeftView(SyncList sl, AbstractTableModel tm)
{
leftLabel.setText(abbrevName(sl.getLocalCabinetName()));
//+ "<->" + sl.getRemoteCabinetName());
if (tm != null)
localTable.setModel(tm);
}
public void setRightView(SyncList sl, AbstractTableModel tm)
{
rightLabel.setText(abbrevName(sl.getLocalCabinetName()));
if (tm != null)
remoteTable.setModel(tm);
}
public void setView(SyncListTreeNode syncNode)
{
setView(syncNode.getSyncList());
}
//this method will make more sense when we re-introduce the tree
public void setView(SyncList sl)
{
setLeftView(sl, null);
setRightView(sl.getRemoteList(), null);
//todo: put back when tree up and running
//setLeftView(sl, new SyncListTableModel(sl));
//setRightView(sl.getRemoteList(), new SyncListTableModel(sl.getRemoteList()));
}
protected void addJTreeListeners()
{
jtree.addTreeSelectionListener
(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e)
{
SyncListTreeNode syncNode = (SyncListTreeNode) jtree.getLastSelectedPathComponent();
if (syncNode == null) return;
if (log.d) log.debug("tree node selected: " + syncNode);
setView(syncNode);
}
});
// could put these on the the class instead of inline
jtree.addTreeWillExpandListener
(new TreeWillExpandListener() {
public void treeWillExpand(TreeExpansionEvent e)
{
TreePath path = e.getPath();
if (log.d) log.debug("tree expanding at path: " + path);
SyncListTreeNode syncNode = (SyncListTreeNode) path.getLastPathComponent();
if (syncNode == null) return;
if (log.d) log.debug("tree expanding at node: " + syncNode);
jtree.setSelectionPath(path);
setView(syncNode);
}
public void treeWillCollapse(TreeExpansionEvent e) {}
});
}
static final Color darkGreen = new Color(0, 128, 0);
static final Color ghostColor = Color.gray;
static final Color defaultColor = Color.black;
// changing font is not working...
// static final Font defaultFont = new Font("serif", Font.PLAIN, 14);
// static final Font ghostFont = new Font("serif", Font.ITALIC, 10);
//static final Color statusColors[] = { Color.black, Color.black, darkGreen };
private static Color getStatusColor(SyncEntry se)
{
switch (se.getStatus()) {
case SyncList.STATUS_NEW_REMOTE: return ghostColor;
case SyncList.STATUS_CHANGED_REMOTE: return Color.red;
case SyncList.STATUS_NEW_LOCAL:
case SyncList.STATUS_CHANGED_LOCAL:
return darkGreen;
case SyncList.STATUS_CONFLICT: return Color.orange;
}
return defaultColor;
}
public void setMessage(String s)
{
statusBar.setText(s);
}
public void progressMessage(String s)
{
statusBar.setText(s + "...");
}
public void clearMessage()
{
statusBar.setText("");
}
private boolean constructing = true;
private Font startupMessageFont = new Font("serif", Font.ITALIC+Font.BOLD, 48);
public void paint(Graphics g)
{
super.paint(g);
if (constructing) {
g.setColor(Color.lightGray);
g.setFont(startupMessageFont);
g.drawString("OKI FILING DEMO", getWidth()/4, getHeight()/2);
}
}
protected void processEvent(AWTEvent e)
{
if (log.d) log.debug(e.toString());
super.processEvent(e);
}
// Declarations for menus
static JMenuBar mainMenuBar = null;
static JMenu fileMenu = null;
private static JMenuBar getMainMenuBar()
{
if (mainMenuBar == null)
mainMenuBar = new JMenuBar();
return mainMenuBar;
}
private static JMenu getFileMenu()
{
if (fileMenu == null)
fileMenu = new JMenu("File");
return fileMenu;
}
class CSAction extends AbstractAction {
//todo: use this or lose it
public CSAction(String name)
{
super(name);
}
public String getActionCommand()
{
return (String) getValue(Action.NAME);
}
private boolean actionMatch(ActionEvent ae, CSAction a)
{
if (a != null)
return ae.getActionCommand().equals(a.getActionCommand());
return false;
}
public void actionPerformed(ActionEvent ae) {
if (log.d) log.debug(ae.getActionCommand() + " ActionEvent=" + ae.getSource());
// handle the menu items
if (actionMatch(ae, aCopy)) doCopy();
else if (actionMatch(ae, aPaste)) doPaste();
else if (actionMatch(ae, aClear)) doClear();
else if (actionMatch(ae, aSelectAll)) doSelectAll();
else if (actionMatch(ae, aAutoSync)) syncList.setAutoSync(tbAutoSync.isSelected());
else if (actionMatch(ae, aSyncSelected)) doSyncSelected();
else if (actionMatch(ae, aMarkSync)) doMarkSelectionForSync(Boolean.TRUE);
else if (actionMatch(ae, aUnmarkSync)) doMarkSelectionForSync(Boolean.FALSE);
// handle the tool-bar buttons
else if (actionMatch(ae, aRefresh)) {
if (ae.getSource() == tbRefresh)
syncList.refresh(ae.getModifiers() != 0);//modifiers is always 0 no matter what keys we hold!
else
tbRefresh.doClick();
}
else if (actionMatch(ae, aUploadOnly)) doAsyncSync(SyncList.SYNC_UPLOADS_ONLY);
else if (actionMatch(ae, aDownloadOnly)) doAsyncSync(SyncList.SYNC_DOWNLOADS_ONLY);
else if (actionMatch(ae, aSync)) doAsyncSync(SyncList.SYNC_ALL);
else if (actionMatch(ae, aCancel)) doCancel();
else if (actionMatch(ae, aQuit)) doQuit();
else if (ae.getSource() == leftImportAll) {
setSyncAllEnabled(leftImportAll.isSelected());
} else if (ae.getSource() == rightImportAll) {
syncList.getRemoteList().setImportAll(leftImportAll.isSelected());
}
else {
//if (log.v) log.verbose("unhandled action: " + ae.getActionCommand());
if (log.v) log.verbose("unhandled action: " + ae);
}
}
}
private synchronized void setSyncAllEnabled(boolean importAll)
{
syncList.setImportAll(importAll);
leftTCModel.showSyncColumn(!importAll);
aMarkSync.setEnabled(!importAll);
aUnmarkSync.setEnabled(!importAll);
//aSyncSelected.setEnabled(!importAll);
leftImportAll.setSelected(importAll);
}
public void setPollInterval(int seconds)
{
syncList.setPollInterval(seconds);
if (syncList.getPollInterval() <= 0)
aAutoSync.setEnabled(false);
else
aAutoSync.setEnabled(true);
}
Container toolBar;
// AbstractButton tbPoll = new JRadioButton("AutoRefresh");
private Component getToolBar()
{
if (toolBar != null)
return toolBar;
toolBar = new JPanel();
toolBar.setLayout(new BoxLayout(toolBar, BoxLayout.X_AXIS));
toolBar.add(Box.createHorizontalGlue());
//JButton tbSync = new JButton(new ImageIcon("images/sync.gif", "Synchronize"));
addToolButton(tbRefresh);
// JButton b = new JButton(aUploadOnly);
// b.setForeground(darkGreen);
// addToolButton(b);
addToolButton(new JButton(aSync));
// b = new JButton(aDownloadOnly);
// b.setForeground(darkGreen);
// addToolButton(b);
addToolButton(new JButton(aCancel));
aCancel.setEnabled(false);
//toolBar.addSeparator();
//tbAutoSync.setForeground(Color.blue);
toolBar.add(Box.createHorizontalGlue());
addToolButton(new JButton(aSyncSelected));
toolBar.add(Box.createHorizontalGlue());
addToolButton(tbAutoSync);
toolBar.add(Box.createHorizontalGlue());
//tbSync.setBackground(Color.white);
//toolBar.add(new JLabel(new ImageIcon("images/sync.gif")));
//toolBar.add(new JLabel("Synchronize", new ImageIcon("images/sync.gif"), JLabel.CENTER));
return toolBar;
}
private void addToolButton(AbstractButton b)
{
//toolBar.addSeparator();
if (b instanceof JComponent) {
// tab-selected items (those with default input focus)
// in mac LAF look gross when 'selected' -- this
// doesn't let them get the default selection.
((JComponent)b).setRequestFocusEnabled(false);
}
toolBar.add(b);
}
public void addMenus() {
addFileMenuItems();
addEditMenuItems();
//mainMenuBar.add(Box.createHorizontalGlue());
//mainMenuBar.add(new JLabel(syncList.getLocalCabinet().toString(), JLabel.RIGHT));
// JButton btnSync = null;//new JButton("Synchronize");
// if (btnSync != null) {
// mainMenuBar.add(btnSync);
// //btnSync.addActionListener(this);
// btnSync.setDefaultCapable(false);//drawn border gets messed up in apple LAF
// }
setJMenuBar(mainMenuBar);
}
public void addFileMenuItems()
{
getFileMenu();
//todo: change from meta to ctrl if it's a PC? META accelerators don't
// seem to be working on my Java 1.4 Win2k box.
fileMenu.add(new JMenuItem(aOpen)).
setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.META_MASK));
fileMenu.add(new JMenuItem(aRefresh)).
setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, Event.META_MASK));
fileMenu.add(new JMenuItem(aSync)).
setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.META_MASK));
fileMenu.addSeparator();
fileMenu.add(new JMenuItem(aQuit)).
setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, Event.META_MASK));
getMainMenuBar().add(fileMenu);
}
public void addEditMenuItems()
{
miCopy = new JMenuItem(aCopy);
miCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, Event.META_MASK));
editMenu.add(miCopy);
miPaste = new JMenuItem(aPaste);
miPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, Event.META_MASK));
editMenu.add(miPaste).setEnabled(false);
miClear = new JMenuItem(aClear);
editMenu.add(miClear);
miClear.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, Event.META_MASK));
//why neither of these working?
//miClear.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
//miClear.setMnemonic(KeyEvent.VK_ESCAPE);
editMenu.addSeparator();
miSelectAll = new JMenuItem(aSelectAll);
miSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Event.META_MASK));
editMenu.add(miSelectAll);
editMenu.addSeparator();
editMenu.add(new JMenuItem(aSyncSelected));
editMenu.add(new JMenuItem(aMarkSync))
.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, Event.META_MASK));
editMenu.add(new JMenuItem(aUnmarkSync))
.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, Event.META_MASK));
getMainMenuBar().add(editMenu);
}
private int doSyncType = SyncList.SYNC_ALL;
private Throwable syncException = null;
private Runnable syncThreadCode = new Runnable() {
public void run() {
if (log.d) log.debug("sync thread started");
try {
doSync(doSyncType);
} finally {
enableSyncButtons(true);
}
if (syncException != null) {
Throwable e = syncException;
syncException = null;
if (e instanceof LockException) {
// todo: embed lock data in exception and unpack it here for display,
// offering user option of breaking the lock.
LockException le = (LockException) e;
JOptionPane.showMessageDialog(null, e.getMessage() + "\nlock" + le.lock + "\nCurrent time: " + new Date());
} else if (e instanceof SyncException)
JOptionPane.showMessageDialog(null, "Synchronization error: " + e.getMessage());
else {
log.outln("displaying exception dialog: " + e);
// Bug: java 1.3.1 on Mac is Segmentation Faulting if the
// line-length in a string handed to showMessageDialog is too long...
JOptionPane.showMessageDialog(null, e.toString());
}
}
if (log.d) log.debug("sync thread complete");
}};
Thread syncThread;
public synchronized void doAsyncSync(int syncType)
{
enableSyncButtons(false);
try {
doSyncType = syncType;
syncThread = new Thread(syncThreadCode, "CabSyncViewer sync");
syncThread.setPriority(Thread.MIN_PRIORITY);//give gui max priority
syncThread.start();
} catch (Exception e) {
enableSyncButtons(true);
log.errout(e);
}
}
private void enableSyncButtons(boolean tv)
{
aRefresh.setEnabled(tv);
aSync.setEnabled(tv);
aUploadOnly.setEnabled(tv);
aDownloadOnly.setEnabled(tv);
aCancel.setEnabled(!tv);
SwingUtilities.getRootPane(this).setCursor(tv ? defaultCursor : waitCursor);
}
public synchronized void doAutoSync()
{
if (syncList.isSyncRunning())
return;
aSync.setEnabled(false);
try {
doSync(SyncList.SYNC_ALL);
} finally {
aSync.setEnabled(true);
}
}
public synchronized void doSync(int syncType)
{
if (log.d) log.debug("synchronizing");
// progressMessage("Synchronizing "
// + syncList.getLocalCabinet() + " with "
// + syncList.getRemoteCabinet());
try {
syncList.doSynchronize(syncType);
} catch (Exception e) {
syncException = e;
log.errout(e, "doSync");
}
// TODO: Don't set this message if we didn't do anything
//setMessage("Synchronization complete at " + new Date());
clearMessage();
}
public void doCancel()
{
syncList.requestSyncCancel();
if (log.d) log.debug("doCancel");
try {
syncThread.join();
} catch (InterruptedException e) {
log.errout(e);
}
}
public void doSyncSelected()
{
JTable table = getFocusedTable();
syncSelected(table, false);
}
protected void exportSelected(JTable table)
{
syncSelected(table, true);
}
protected void syncSelected(JTable table, boolean exportsOnly)
{
boolean remoteSource = (table == remoteTable);
boolean localSource = (table == localTable);
int rows[] = table.getSelectedRows();
if (log.d) log.debug("exportSelected " + rows.length + " rows");
//TODO: disable buttons.
TableModel tm = table.getModel();
for (int i = 0; i < rows.length; i++) {
SyncEntry se = (SyncEntry) tm.getValueAt(rows[i], COL_NAME);
if (exportsOnly) {
/*
* ONLY do "exports" -- copy out data from source
* So only do items that appear new in this table.
*/
if (! (se.getStatus() == SyncList.STATUS_NEW_LOCAL ||
se.getStatus() == SyncList.STATUS_CHANGED_LOCAL))
continue;
}
if (log.d) log.debug("export " + se);
// if this is REMOTE list, need to get local match.
// right now we're set up only to do remote list --
// todo: CHANGE THAT.
// todo: this is confusing: addRemote only for remote
// sources?
SyncEntry localEntry;
if (remoteSource)
localEntry = syncList.addRemote(se);
else
localEntry = se;
int changes = 0;
try {
if (syncList.doSynchronizeEntry(localEntry))
changes++;
} catch (FilingException e) {
setMessage(e.toString());
log.errout(e);
} finally {
// be sure to keep the properties up to date!
syncList.releaseLocks();
if (changes > 0)
syncList.saveProperties();
}
}
}
public void doMarkSelectionForSync(Boolean b)
{
JTable table = getFocusedTable();
if (table != localTable || !leftTCModel.isSyncShowing())
return;
TableModel tm = table.getModel();
int rows[] = getSelectedRows();
for (int i = 0; i < rows.length; i++) {
tm.setValueAt(b, rows[i], COL_DOSYNC);
}
// when using accelerators, the table doesn't
// seem to update, so we tried firing this manually to be sure.
// UNFORTUNATELY, this extra call also causes the
// selection to clear!
// if (tm instanceof AbstractTableModel)
// ((AbstractTableModel)tm).fireTableDataChanged();
// okay, repaint actually does the trick:
table.repaint();
}
public void doCopy()
{
if (log.d) log.debug("copy row " + localTable.getSelectedRow());
// determine active viewer, get all selected rows, get
// SyncEntry's from the data model, and do our stuff.
}
public void doPaste() {}
public void doQuit()
{
if (log.v) log.verbose("exiting");
System.exit(0);
}
public void doClear()
{
if (log.d) log.debug("doClear");
getFocusedTable().clearSelection();
}
public void doSelectAll()
{
if (log.d) log.debug("doSelectAll");
getFocusedTable().selectAll();
}
protected JTable getFocusedTable()
{
Component fo = getFocusOwner();
if (fo != null) {
if (log.d) log.debug("focusOwner: " + fo.getClass().getName()
+", "+fo.getLocation()
+", "+fo.getSize());
if (fo == remoteTable)
return remoteTable;
}
return localTable;
}
protected int[] getSelectedRows()
{
int rows[] = getFocusedTable().getSelectedRows();
if (log.d) {
log.debug("Selected rows:");
for (int i = 0; i < rows.length; i++)
log.debug("r"+rows[i]);
}
return rows;
}
static boolean A_DEBUG = false;
static boolean A_VERBOSE = false;
static boolean A_TAKE_ACTION = true;
static boolean A_SHOW_GHOSTS = false;
public static final String USAGE = "Usage: CabSyncViewer <local-cabinet> <remote-cabinet> [-poll seconds] [-show_ghosts]";
public static void main(String args[])
{
int pollInterval = 30; // default seconds
CabUtil.parseCommandLine(args);
log.parseArgs(args);
ArrayList dirs = new ArrayList();
for (int i = 0; i < args.length; i++) {
String a = args[i];
if (a.equals("-poll")) {
if (args.length > (i+1)) {
i++;
pollInterval = Integer.parseInt(args[i]);
}
}
else if (a.equals("-help")) {
System.out.println(USAGE);
System.exit(0);
}
else if (a.equals("-show_ghosts"))
A_SHOW_GHOSTS = true;
// else if (a.equals("-always_sync_all"))
//todo
else if (!a.startsWith("-"))
dirs.add(a);
}
String dirLocal = "./c/l";
String dirRemote = "./c/r";
if (dirs.size() > 1) {
dirLocal = (String) dirs.get(0);
dirRemote = (String) dirs.get(1);
} else {
log.errout(USAGE);
System.exit(1);
}
Cabinet cabinet0 = CabUtil.getCabinetFromDirectory(dirLocal);
Cabinet cabinet1 = CabUtil.getCabinetFromDirectory(dirRemote);
//CabUtil.printCabinet(cabinet0);
//CabUtil.printCabinet(cabinet1);
if (log.v) log.verbose("local=" + cabinet0 + ", remote=" + cabinet1);
if (cabinet0 == null || cabinet1 == null) {
log.errout("Couldn't open cabinet(s).");
System.exit(1);
}
SyncList syncList = new SyncList(cabinet0, cabinet1, log);
CabSyncViewer cv = new CabSyncViewer(syncList);
cv.showGhosts = A_SHOW_GHOSTS;
syncList.setVisibleGhosts(A_SHOW_GHOSTS);
cv.setPollInterval(pollInterval);
cv.setVisible(true);
}
}