/*
gclient.java
Ganymede client main module
Created: 24 Feb 1997
Module By: Mike Mulvaney, Jonathan Abbey, and Navin Manohar
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2013
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.client;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.KeyEventDispatcher;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.rmi.RemoteException;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import java.util.prefs.*;
import javax.swing.SwingConstants;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JFileChooser;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicToolBarUI;
import org.python.core.PySystemState;
import arlut.csd.JDataComponent.JSetValueObject;
import arlut.csd.JDataComponent.JValueObject;
import arlut.csd.JDataComponent.JErrorValueObject;
import arlut.csd.JDataComponent.JsetValueCallback;
import arlut.csd.JDataComponent.LAFMenu;
import arlut.csd.JDataComponent.listHandle;
import arlut.csd.JDialog.DialogRsrc;
import arlut.csd.JDialog.JDialogBuff;
import arlut.csd.JDialog.JErrorDialog;
import arlut.csd.JDialog.StandardDialog;
import arlut.csd.JDialog.StringDialog;
import arlut.csd.JDialog.messageDialog;
import arlut.csd.JDialog.aboutGanyDialog;
import arlut.csd.JDialog.aboutJavaDialog;
import arlut.csd.JTree.treeCallback;
import arlut.csd.JTree.treeControl;
import arlut.csd.JTree.treeMenu;
import arlut.csd.JTree.treeNode;
import arlut.csd.Util.PackageResources;
import arlut.csd.Util.TranslationService;
import arlut.csd.Util.VecQuickSort;
import arlut.csd.ganymede.common.BaseDump;
import arlut.csd.ganymede.common.CatTreeNode;
import arlut.csd.ganymede.common.CategoryDump;
import arlut.csd.ganymede.common.CategoryTransport;
import arlut.csd.ganymede.common.DumpResult;
import arlut.csd.ganymede.common.ErrorTypeEnum;
import arlut.csd.ganymede.common.FieldTemplate;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.InvidPool;
import arlut.csd.ganymede.common.NotLoggedInException;
import arlut.csd.ganymede.common.ObjectHandle;
import arlut.csd.ganymede.common.Query;
import arlut.csd.ganymede.common.QueryResult;
import arlut.csd.ganymede.common.RegexpException;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.SchemaConstants;
import arlut.csd.ganymede.common.windowSizer;
import arlut.csd.ganymede.rmi.Base;
import arlut.csd.ganymede.rmi.Category;
import arlut.csd.ganymede.rmi.CategoryNode;
import arlut.csd.ganymede.rmi.Session;
import arlut.csd.ganymede.rmi.db_object;
import apple.dts.samplecode.osxadapter.OSXAdapter;
import com.explodingpixels.macwidgets.UnifiedToolBar;
/*------------------------------------------------------------------------------
class
gclient
------------------------------------------------------------------------------*/
/**
* <p>Main ganymede client class. When {@link
* arlut.csd.ganymede.client.glogin glogin} is run and a user logs in
* to the server, the client obtains a {@link
* arlut.csd.ganymede.rmi.Session Session} reference that allows it to
* talk to the server on behalf of a user, and a single instance of
* this class is created to handle all client GUI and networking
* operations for that user.</p>
*
* <p>gclient creates a {@link arlut.csd.ganymede.client.windowPanel
* windowPanel} object to contain internal object ({@link
* arlut.csd.ganymede.client.framePanel framePanel}) and query windows
* on the right side of a Swing JSplitPane. The left side contains a
* custom {@link arlut.csd.JTree.treeControl treeControl} GUI
* component displaying object categories, types, and instances for
* the user to browse and edit.</p>
*
* @author Mike Mulvaney, Jonathan Abbey, and Navin Manohar
*/
public final class gclient extends JFrame implements treeCallback, ActionListener, JsetValueCallback {
public static boolean debug = false;
/**
* TranslationService object for handling string localization in the
* Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.gclient");
/**
* If reportVersionToServer is true, the Ganymede client will report
* the Java version information to the server that it has logged
* into on startup.
*/
private static final boolean reportVersionToServer = false;
/**
* Preferences object for the Ganymede client. Using this object,
* we can save and retrieve preferences data from a system-dependent
* backing-store.. the Registry on Windows, a XML file under
* ~/.java/user-prefs/ on Linux/Unix/Mac, and who-knows-what on
* other platforms.
*/
public static final Preferences prefs;
// If we're running as an applet, we might not be able to
// successfully load our static Preferences reference. Make sure
// that we don't block this class' static initialization if we can't
// get Preferences.
static
{
Preferences _prefs = null;
try
{
_prefs = Preferences.userNodeForPackage(gclient.class);
}
catch (Throwable ex)
{
ex.printStackTrace();
}
prefs = _prefs;
}
public static final windowSizer sizer = new windowSizer(prefs);
/**
* we're only going to have one gclient at a time per running client (singleton pattern).
*/
public static gclient client = null;
// Image numbers
static final int NUM_IMAGE = 17;
static final int OPEN_BASE = 0;
static final int CLOSED_BASE = 1;
static final int OPEN_FIELD = 2;
static final int OPEN_FIELD_DELETE = 3;
static final int OPEN_FIELD_CREATE = 4;
static final int OPEN_FIELD_CHANGED = 5;
static final int OPEN_FIELD_REMOVESET = 6;
static final int OPEN_FIELD_EXPIRESET = 7;
static final int CLOSED_FIELD = 8;
static final int CLOSED_FIELD_DELETE = 9;
static final int CLOSED_FIELD_CREATE = 10;
static final int CLOSED_FIELD_CHANGED = 11;
static final int CLOSED_FIELD_REMOVESET = 12;
static final int CLOSED_FIELD_EXPIRESET = 13;
static final int OPEN_CAT = 14;
static final int CLOSED_CAT = 15;
static final int OBJECTNOWRITE = 16;
/* our fixed (no localization needed) action command strings for
* node-attached popup menus. */
private final static String
hide_pop_action = "Hide Non-Editables",
show_pop_action = "Show Non-Editables",
query_pop_action = "Query",
report_edit_pop_action = "Report editable",
report_pop_action = "Report all",
create_pop_action = "Create",
view_pop_action = "View Object",
edit_pop_action = "Edit Object",
clone_pop_action = "Clone Object",
delete_pop_action = "Delete Object",
inactivate_pop_action = "Inactivate Object",
reactivate_pop_action = "Reactivate Object";
/* our fixed (no localization needed) action command strings for
* menus and toolbar items. */
private final static String
persona_action = "change persona",
query_action = "compose a query",
create_action = "create new object",
edit_action = "open object for editing",
view_action = "open object for viewing",
delete_action = "delete an object",
clone_action = "clone an object",
inactivate_action = "inactivate an object",
access_invid_action = "Show me an Invid",
owner_filter_action = "Set Owner Filter",
default_owner_action = "Set Default Owner",
help_action = "Help",
about_action = "About Ganymede",
java_version_action = "Java Version",
motd_action = "Message of the day";
final String commitButtonText = ts.l("init.commit_button"); // "Commit"
final String commitCommentButtonText = ts.l("init.commit_comment_button"); // "Commit with Comments"
/**
* This is a convenience method used by the server to get a
* stack trace from a throwable object in String form.
*/
static public String stackTrace(Throwable thing)
{
StringWriter stringTarget = new StringWriter();
PrintWriter writer = new PrintWriter(stringTarget);
thing.printStackTrace(writer);
writer.close();
return stringTarget.toString();
}
// ---
/**
* Main remote interface for communications with the server.
*/
Session session;
/**
* Reference to the applet which instantiated us.
*/
glogin _myglogin;
/**
* Local copy of the category/object tree downloaded from
* the server by the {@link arlut.csd.ganymede.client.gclient#buildTree() buildTree()}
* method.
*/
CategoryDump dump;
/**
* Name of the currently active persona.
*/
String currentPersonaString;
// set up a bunch of borders
// Turns out we don't need to do this anyway, since the BorderFactory does it for us.
public EmptyBorder
emptyBorder5 = (EmptyBorder) BorderFactory.createEmptyBorder(5,5,5,5),
emptyBorder10 = (EmptyBorder) BorderFactory.createEmptyBorder(10,10,10,10);
public BevelBorder
raisedBorder = (BevelBorder) BorderFactory.createBevelBorder(BevelBorder.RAISED),
loweredBorder = (BevelBorder) BorderFactory.createBevelBorder(BevelBorder.LOWERED);
public LineBorder
lineBorder = (LineBorder) BorderFactory.createLineBorder(Color.black);
public CompoundBorder
statusBorder = BorderFactory.createCompoundBorder(loweredBorder, emptyBorder5),
statusBorderRaised = BorderFactory.createCompoundBorder(raisedBorder, emptyBorder5);
//
// Yum, caches
//
/**
* Cache of {@link arlut.csd.ganymede.common.Invid invid}'s for
* objects that might have been changed by the client. The
* collection of tree nodes corresponding to invid's listed in
* changedSet will be refreshed by the client when a server is
* committed or canceled.
*/
private HashSet<Invid> changedSet = new HashSet<Invid>();
/**
* Mapping of {@link arlut.csd.ganymede.common.Invid invid}'s for objects
* that the client has requested be deleted by the server to
* {@link arlut.csd.ganymede.client.CacheInfo CacheInfo} objects
* which hold information about the object used to make decisions
* about managing the client's tree display.
*/
private Hashtable<Invid, CacheInfo> deleteHash = new Hashtable<Invid, CacheInfo>();
/**
* Mapping of {@link arlut.csd.ganymede.common.Invid invid}'s for objects
* that the client has requested be created by the server to
* {@link arlut.csd.ganymede.client.CacheInfo CacheInfo} objects
* which hold information about the object used to make decisions
* about managing the client's tree display.
*/
private Hashtable<Invid, CacheInfo> createHash = new Hashtable<Invid, CacheInfo>();
/**
* Hash of {@link arlut.csd.ganymede.common.Invid invid}'s corresponding
* to objects that have been created by the client but which have not
* had nodes created in the client's tree display. Once nodes are
* created for these objects, the invid will be taken out of this
* hash and put into createHash.
*/
private Hashtable<Invid, BaseNode> createdObjectsWithoutNodes = new Hashtable<Invid, BaseNode>();
/**
* Hash mapping Short {@link arlut.csd.ganymede.rmi.Base Base} id's to
* the corresponding {@link arlut.csd.ganymede.client.BaseNode BaseNode} or
* displayed in the client's tree display.
*/
protected Hashtable<Short, BaseNode> shortToBaseNodeHash = new Hashtable<Short, BaseNode>();
/**
* Hash mapping {@link arlut.csd.ganymede.common.Invid Invid}'s for objects
* referenced by the client to the corresponding
* {@link arlut.csd.ganymede.client.InvidNode InvidNode} displayed in the
* client's tree display.
*/
protected Hashtable<Invid, InvidNode> invidNodeHash = new Hashtable<Invid, InvidNode>();
/**
* <p>Our main cache, keeps information about all objects we've learned
* about via {@link arlut.csd.ganymede.common.QueryResult QueryResult}'s returned
* to us by the server.</p>
*
* <p>We can get QueryResults from the server by doing direct
* {@link arlut.csd.ganymede.rmi.Session#query(arlut.csd.ganymede.common.Query) query}
* calls on the server, or by calling choices() on an
* {@link arlut.csd.ganymede.rmi.invid_field invid_field} or on a
* {@link arlut.csd.ganymede.rmi.string_field string_field}. Information from
* both sources may be integrated into this cache.</p>
*/
protected objectCache cachedLists = new objectCache();
/**
* Background processing thread, downloads information on
* object and field types defined in the server when run.
*/
Loader loader;
//
// Status tracking
//
private boolean
toolToggle = true,
somethingChanged = false; // This will be set to true if the user changes anything
private int
buildPhase = -1; // unknown
/**
* A custom KeyEventDispatcher we use to toggle the commit button's
* text when the alt/option key is held down.
*/
private KeyEventDispatcher altKeyListener;
helpPanel
help = null;
messageDialog
motd = null;
aboutGanyDialog
about = null;
aboutJavaDialog
java_ver_dialog = null;
Vector<String>
personae;
Vector<listHandle>
ownerGroups = null; // Vector of owner groups
// Dialog and GUI objects
JToolBar
toolBar;
JFilterDialog
filterDialog = null;
PersonaDialog
personaDialog = null;
JDefaultOwnerDialog
defaultOwnerDialog = null;
createObjectDialog
createDialog = null;
Image images[];
JButton
commit,
cancel;
private JPanel
statusPanel = new JPanel(new BorderLayout());
private JSplitPane sPane = null;
/**
* Status, build status, and login status labels at the bottom of the client.
*/
JLabel
statusLabel = new JLabel(),
buildLabel = new JLabel(),
loginLabel = new JLabel();
/**
* The client's GUI tree component.
*/
treeControl tree;
/**
* The currently selected node from the client's GUI tree.
*/
treeNode
selectedNode;
// The top lines
Image
errorImage = null,
questionImage = null,
infoImage = null,
search,
queryIcon,
cloneIcon,
pencil,
personaIcon,
inactivateIcon,
treepencil,
trash,
treetrash,
creation,
treecreation,
newToolbarIcon,
ganymede_logo,
createDialogImage;
ImageIcon
idleIcon, buildIcon, buildIcon2, buildUnknownIcon;
/**
* JDesktopPane on the right side of the client's display, contains
* the object and query result internal windows that are created
* during the client's execution.
*/
windowPanel
wp;
//
// Menu resources
//
treeMenu
pMenuAll = new treeMenu(),
pMenuEditable= new treeMenu(),
pMenuEditableCreatable = new treeMenu(),
pMenuAllCreatable = new treeMenu(),
objectViewPM = new treeMenu(),
objectReactivatePM = new treeMenu(),
objectInactivatePM = new treeMenu(),
objectRemovePM = new treeMenu();
JMenuBar
menubar;
JMenuItem
copyMI,
cutMI,
pasteMI,
logoutMI,
clearTreeMI,
filterQueryMI,
defaultOwnerMI,
showHelpMI,
toggleToolBarMI,
submitXMLMI;
JCheckBoxMenuItem
promptForCommentsMI,
hideNonEditablesMI;
/**
* If true, the client will only display object types that the
* user has permission to edit, and by default will only show objects
* in the tree that the user can edit. If false, all objects and
* object types the the user has permission to view will be shown
* in the tree. Toggled by the user manipulating the hideNonEditablesMI
* check box menu item.
*/
boolean hideNonEditables = prefs.getBoolean("hide_non_editable_objects", true);
/**
* If true, the client will prompt for comments at commit time by
* default. If false, the user will need to hold down the
* alt/option key in order to cause the comment prompt dialog to be
* shown.
*/
boolean promptForComments = prefs.getBoolean("prompt_for_comments", false);
/**
* Variable used to track commit comment text. We have this as an
* object-level member variable so that we can present it to the
* user for his convenience if a commit fails to complete.
*/
private String comment = null;
/**
* Will be set to true by the AltKeyListener if the user has the alt
* or option key held down.
*/
boolean altKeyDown = false;
boolean defaultOwnerChosen = false;
JMenuItem
changePersonaMI,
editObjectMI,
viewObjectMI,
createObjectMI,
deleteObjectMI,
inactivateObjectMI,
menubarQueryMI = null;
String
my_username;
JMenu
fileMenu,
editMenu,
actionMenu,
windowMenu,
helpMenu,
PersonaMenu = null;
LAFMenu
LandFMenu;
/**
* Listener to react to persona dialog events
*/
PersonaListener
personaListener = null;
/**
* Query dialog that is displayed when the user chooses to perform
* a query on the server.
*/
querybox
my_querybox = null;
/**
* <p>This thread is used to clear the statusLabel after some
* interval after it is set.</p>
*
* <p>Whenever the gclient's {@link
* arlut.csd.ganymede.client.gclient#setStatus(java.lang.String,int)
* setStatus} method is called, this thread has a countdown timer
* started, which will clear the status label if it is not reset by
* another call to setStatus.</p>
*/
public StatusClearThread statusThread, loginStatusThread;
/**
* <p>This thread is set up to launder RMI build status updates from
* the server.</p>
*
* <p>In some versions of Sun's JDK, RMI callbacks are not allowed
* to manipulate the GUI event queue. To get around this, this
* securityThread is created to launder these RMI callbacks so that
* the Swing event queue is messed with by a client-local
* thread.</p>
*/
public SecurityLaunderThread securityThread;
/**
* this is true during the handleReturnVal method, while a wizard is
* active. If a wizard is active, don't allow the window to close.
*/
private int wizardActive = 0;
/* -- */
/**
* This is the main constructor for the gclient class.. it handles the
* interactions between the user and the server once the user has
* logged in.
*
* @param s Connection to the server created for us by the glogin applet.
* @param g The glogin applet which is creating us.
*/
public gclient(Session s, glogin g)
{
JPanel
leftP,
leftTop,
rightTop,
mainPanel; // Everything is in this, so it is double buffered
/* -- */
if (gclient.client != null)
{
throw new IllegalStateException("Singleton gclient already created.");
}
client = this;
if (!Invid.hasAllocator())
{
Invid.setAllocator(new InvidPool(3257)); // modest sized prime, should be adequate for the client
}
if (!debug)
{
debug = g.debug;
}
if (debug)
{
System.err.println("Starting client");
}
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
if (s == null)
{
throw new IllegalArgumentException("Ganymede Error: Parameter for Session s is null");
}
// If we're running on the Mac, let's try to fit in a bit better.
if (glogin.isRunningOnMac())
{
System.setProperty("apple.laf.useScreenMenuBar", "true");
}
session = s;
_myglogin = g;
my_username = g.getUserName();
try
{
currentPersonaString = session.getActivePersonaName();
// In gclient, currentPersonaString must not be null.. if we
// didn't get a privileged persona, assume unprivileged
// end-user account name.
if (currentPersonaString == null)
{
currentPersonaString = my_username;
}
// "Ganymede Client: {0} logged in"
setTitle(ts.l("global.logged_in_title", currentPersonaString));
}
catch (RemoteException rx)
{
processExceptionRethrow(rx);
}
if (reportVersionToServer)
{
try
{
session.reportClientVersion(aboutJavaDialog.getVersionInfoString());
}
catch (RemoteException ex)
{
// nada
}
}
JDefaultOwnerDialog.clear();
mainPanel = new JPanel(true);
mainPanel.setLayout(new BorderLayout());
getContentPane().setLayout(new BorderLayout());
getContentPane().add("Center", mainPanel);
if (debug)
{
System.err.println("Creating menu bar");
}
// Make the menu bar
menubar = createMenuBar();
setJMenuBar(menubar);
createTree();
if (debug)
{
System.err.println("Adding left and right panels");
}
leftP = new JPanel(false);
leftP.setLayout(new BorderLayout());
leftP.add("Center", tree);
try
{
buildTree();
}
catch (Exception ex)
{
processExceptionRethrow(ex, "caught remote exception in buildTree");
}
// The right panel which will contain the windowPanel
JPanel rightP = new JPanel(true);
rightP.setLayout(new BorderLayout());
wp = new windowPanel(this, windowMenu);
rightP.add("Center", wp);
rightTop = new JPanel(false);
rightTop.setBorder(statusBorderRaised);
rightTop.setLayout(new BorderLayout());
if (!glogin.isRunningOnMac())
{
toolBar = createToolbar();
getContentPane().add("North", toolBar);
}
else
{
UnifiedToolBar macToolBar = createMacToolBar();
macToolBar.installWindowDraggerOnWindow(this);
getContentPane().add("North", macToolBar.getComponent());
}
commit = new JButton("");
refreshCommitLabel();
commit.setEnabled(false);
commit.setOpaque(true);
commit.setToolTipText(ts.l("init.commit_tooltip")); // "Hold Alt/Option to toggle comments"
commit.addActionListener(this);
// "Cancel"
cancel = new JButton(ts.l("init.cancel_button"));
cancel.setEnabled(false);
cancel.setOpaque(true);
cancel.setToolTipText(ts.l("init.cancel_tooltip")); // "Click this to cancel your transaction"
cancel.addActionListener(this);
// Button bar at bottom, includes commit/cancel panel and taskbar
JPanel bottomButtonP = new JPanel(false);
if (glogin.isRunningOnMac())
{
// cancel is to the left of commit on the mac
bottomButtonP.add(cancel);
bottomButtonP.add(commit);
}
else
{
bottomButtonP.add(commit);
bottomButtonP.add(cancel);
}
bottomButtonP.setBorder(loweredBorder);
// Create the pane splitter
sPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftP, rightP);
sPane.setOneTouchExpandable(true);
mainPanel.add("Center",sPane);
// Create the bottomBar, for the bottom of the window
JPanel bottomBar = new JPanel(false);
bottomBar.setLayout(new BorderLayout());
JPanel statusPanel = new JPanel(false);
statusPanel.setLayout(new BorderLayout());
statusPanel.setBorder(statusBorder);
statusLabel.setOpaque(false);
loginLabel.setOpaque(false);
loginLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
statusPanel.add("West", statusLabel);
statusPanel.add("East", loginLabel);
statusThread = new StatusClearThread(statusLabel);
statusThread.start();
// start the securityThread to launder RMI calls from the server
securityThread = new SecurityLaunderThread(this);
securityThread.start();
JPanel lP = new JPanel(new BorderLayout());
lP.setBorder(statusBorder);
lP.add("Center", buildLabel);
bottomBar.add("West", lP);
bottomBar.add("Center", statusPanel);
bottomBar.add("East", bottomButtonP);
mainPanel.add("South", bottomBar);
// "Starting up"
setStatus(ts.l("init.startup_msg"));
// Since we're logged in and have a session established, create the
// background loader thread to read in object and field type information
loader = new Loader(session, debug);
loader.start();
try
{
ownerGroups = session.getOwnerGroups().getListHandles();
if (ownerGroups.size() == 0)
{
defaultOwnerMI.setEnabled(false);
}
else if (ownerGroups.size() == 1)
{
chooseDefaultOwner(false); // tells the session our default owner
defaultOwnerMI.setEnabled(false);
}
}
catch (RemoteException ex)
{
processExceptionRethrow(ex);
}
pack();
if (!sizer.restoreSize(this))
{
setSize(800, 600);
this.setLocationRelativeTo(null); // center gclient frame
sizer.saveSize(this);
}
tree.requestFocus();
if (glogin.isRunningOnMac())
{
MacOSXController controller = new MacOSXController(this);
try
{
OSXAdapter.setQuitHandler(controller, MacOSXController.class.getMethod("handleQuit", (Class[]) null));
OSXAdapter.setAboutHandler(controller, MacOSXController.class.getMethod("handleAbout", (Class[]) null));
}
catch (NoSuchMethodException ex)
{
// we shouldn't get an exception here at runtime unless
// we've made a mistake in the MacOSXController class.
processExceptionRethrow(ex);
}
}
altKeyListener = new AltKeyListener();
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(altKeyListener);
this.setVisible(true);
setLoginCount(g.getInitialLoginCount());
getContentPane().validate();
}
/* Private stuff */
/**
* Create our client's menu bar from localization resources.
*/
private JMenuBar createMenuBar()
{
JMenuBar menubar = new JMenuBar();
//menubar.setBorderPainted(true);
// File menu
// "File"
fileMenu = new JMenu(ts.l("createMenuBar.file_menu"));
setMenuMnemonic(fileMenu, ts.l("createMenuBar.file_menu_key_optional"));
// the "New Object" menu will appear under 'File' on the Mac, but
// will be under 'Actions' on other platforms.
if (glogin.isRunningOnMac())
{
// "New Object"
createObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_3_mac")));
setAccelerator(createObjectMI, ts.l("createMenuBar.action_menu_3_mac_accelerator_optional"));
}
else
{
// "Create Object"
createObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_3")));
setMenuMnemonic(createObjectMI, ts.l("createMenuBar.action_menu_3_key_optional"));
}
createObjectMI.setActionCommand(create_action);
createObjectMI.addActionListener(this);
// Ditto "Open Object"
if (glogin.isRunningOnMac())
{
// "Open Object"
viewObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_2_mac")));
setAccelerator(viewObjectMI, ts.l("createMenuBar.action_menu_2_mac_accelerator_optional"));
}
else
{
// "View Object"
viewObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_2")));
setMenuMnemonic(viewObjectMI, ts.l("createMenuBar.action_menu_2_key_optional"));
}
viewObjectMI.setActionCommand(view_action);
viewObjectMI.addActionListener(this);
// "Clear Tree"
clearTreeMI = new JMenuItem(ts.l("createMenuBar.file_menu_0"));
setMenuMnemonic(clearTreeMI, ts.l("createMenuBar.file_menu_0_key_optional"));
clearTreeMI.addActionListener(this);
// "Set Owner Filter"
filterQueryMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.file_menu_1")));
setMenuMnemonic(filterQueryMI, ts.l("createMenuBar.file_menu_1_key_optional"));
filterQueryMI.setActionCommand("Set Owner Filter");
filterQueryMI.addActionListener(this);
// "Set Default Owner"
defaultOwnerMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.file_menu_2")));
setMenuMnemonic(defaultOwnerMI, ts.l("createMenuBar.file_menu_2_key_optional"));
defaultOwnerMI.setActionCommand("Set Default Owner");
defaultOwnerMI.addActionListener(this);
// "Prompt for Comments on Commit"
promptForCommentsMI = new JCheckBoxMenuItem(ts.l("createMenuBar.file_menu_6"), promptForComments);
setMenuMnemonic(promptForCommentsMI, ts.l("createMenuBar.file_menu_6_key_optional"));
promptForCommentsMI.setActionCommand("Prompt For Comments");
promptForCommentsMI.addActionListener(this);
// "Hide non-editable objects"
hideNonEditablesMI = new JCheckBoxMenuItem(ts.l("createMenuBar.file_menu_3"), hideNonEditables);
setMenuMnemonic(hideNonEditablesMI, ts.l("createMenuBar.file_menu_3_key_optional"));
hideNonEditablesMI.addActionListener(this);
// "Submit XML data"
submitXMLMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.file_menu_5")));
setMenuMnemonic(submitXMLMI, ts.l("createMenuBar.file_menu_5_key_optional"));
submitXMLMI.setActionCommand("Submit XML");
submitXMLMI.addActionListener(this);
// "Logout"
logoutMI = new JMenuItem(ts.l("createMenuBar.file_menu_4"));
setMenuMnemonic(logoutMI, ts.l("createMenuBar.file_menu_4_key_optional"));
logoutMI.addActionListener(this);
if (glogin.isRunningOnMac())
{
fileMenu.add(createObjectMI);
fileMenu.add(viewObjectMI);
fileMenu.addSeparator();
}
fileMenu.add(clearTreeMI);
fileMenu.add(filterQueryMI);
fileMenu.add(defaultOwnerMI);
fileMenu.add(promptForCommentsMI);
fileMenu.add(hideNonEditablesMI);
fileMenu.addSeparator();
fileMenu.add(submitXMLMI);
if (!glogin.isRunningOnMac())
{
fileMenu.addSeparator();
fileMenu.add(logoutMI);
}
// Edit menu.. don't enable until i figure out whether we actually
// need it, and how to update the activation status of the menu
// items based on swing focus.
if (false && glogin.isRunningOnMac())
{
editMenu = new JMenu(ts.l("createMenuBar.edit_menu")); // "Edit"
cutMI = new JMenuItem(ts.l("createMenuBar.edit_menu_0")); // "Cut"
setAccelerator(cutMI, ts.l("createMenuBar.edit_menu_0_mac_accelerator_optional")); // "X"
cutMI.addActionListener(this);
editMenu.add(cutMI);
copyMI = new JMenuItem(ts.l("createMenuBar.edit_menu_1")); // "Copy"
setAccelerator(copyMI, ts.l("createMenuBar.edit_menu_1_mac_accelerator_optional")); // "C"
copyMI.addActionListener(this);
editMenu.add(copyMI);
pasteMI = new JMenuItem(ts.l("createMenuBar.edit_menu_2")); // "Paste"
setAccelerator(pasteMI, ts.l("createMenuBar.edit_menu_2_mac_accelerator_optional")); // "V"
pasteMI.addActionListener(this);
editMenu.add(pasteMI);
}
// Action menu
// "Actions"
actionMenu = new JMenu(ts.l("createMenuBar.action_menu"));
setMenuMnemonic(actionMenu, ts.l("createMenuBar.action_menu_key_optional"));
// Personae init
try
{
personae = session.getPersonae();
}
catch (Exception rx)
{
processExceptionRethrow(rx);
}
personaListener = new PersonaListener(session, this);
if (personae != null && personae.size() > 1)
{
// "Change Persona"
changePersonaMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_0")));
setMenuMnemonic(changePersonaMI, ts.l("createMenuBar.action_menu_0_key_optional"));
changePersonaMI.setActionCommand(persona_action);
changePersonaMI.addActionListener(this);
}
// "Query"
menubarQueryMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_1")));
setMenuMnemonic(menubarQueryMI, ts.l("createMenuBar.action_menu_1_key_optional"));
menubarQueryMI.setActionCommand(query_action);
menubarQueryMI.addActionListener(this);
// We created "View Object" and "Create Object" above. If we're
// not on a Mac, we'll show them under the Actions menu.
// "Edit Object"
editObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_4")));
setMenuMnemonic(editObjectMI, ts.l("createMenuBar.action_menu_4_key_optional"));
editObjectMI.setActionCommand(edit_action);
editObjectMI.addActionListener(this);
// "Delete Object"
deleteObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_5")));
setMenuMnemonic(deleteObjectMI, ts.l("createMenuBar.action_menu_5_key_optional"));
deleteObjectMI.setActionCommand(delete_action);
deleteObjectMI.addActionListener(this);
// "Inactivate Object"
inactivateObjectMI = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_6")));
setMenuMnemonic(inactivateObjectMI, ts.l("createMenuBar.action_menu_6_key_optional"));
inactivateObjectMI.setActionCommand(inactivate_action);
inactivateObjectMI.addActionListener(this);
if (changePersonaMI != null)
{
actionMenu.add(changePersonaMI);
}
actionMenu.add(menubarQueryMI);
actionMenu.addSeparator();
if (!glogin.isRunningOnMac())
{
actionMenu.add(viewObjectMI);
actionMenu.add(createObjectMI);
}
actionMenu.add(editObjectMI);
actionMenu.add(deleteObjectMI);
actionMenu.add(inactivateObjectMI);
if (debug)
{
// "Access Invid"
JMenuItem viewAnInvid = new JMenuItem(dialogMenuName(ts.l("createMenuBar.action_menu_7")));
setMenuMnemonic(viewAnInvid, ts.l("createMenuBar.action_menu_7_key_optional"));
viewAnInvid.setActionCommand(access_invid_action);
viewAnInvid.addActionListener(this);
actionMenu.addSeparator();
actionMenu.add(viewAnInvid);
}
// windowMenu
// "Windows"
windowMenu = new JMenu(ts.l("createMenuBar.window_menu"));
setMenuMnemonic(windowMenu, ts.l("createMenuBar.window_menu_key_optional"));
// "Toggle Toolbar"
toggleToolBarMI = new JMenuItem(ts.l("createMenuBar.window_menu_0"));
setMenuMnemonic(toggleToolBarMI, ts.l("createMenuBar.window_menu_0_key_optional"));
toggleToolBarMI.addActionListener(this);
windowMenu.add(toggleToolBarMI);
// Look and Feel menu
LandFMenu = new arlut.csd.JDataComponent.LAFMenu(this);
LandFMenu.setMnemonic('l'); // XXX need to localize.. probably should be done in LAFMenu itself
LandFMenu.setCallback(this);
// Help menu
// "Help"
helpMenu = new JMenu(ts.l("createMenuBar.help_menu"));
setMenuMnemonic(helpMenu, ts.l("createMenuBar.help_menu_key_optional"));
// These use action commands, so you don't need to globally
// declare these
if (!glogin.isRunningOnMac())
{
// "About Ganymede"
JMenuItem showAboutMI = new JMenuItem(ts.l("createMenuBar.help_menu_0"));
setMenuMnemonic(showAboutMI, ts.l("createMenuBar.help_menu_0_key_optional"));
showAboutMI.setActionCommand(about_action);
showAboutMI.addActionListener(this);
helpMenu.add(showAboutMI);
}
// "Message of the day"
JMenuItem showMOTDMI = new JMenuItem(ts.l("createMenuBar.help_menu_1"));
setMenuMnemonic(showMOTDMI, ts.l("createMenuBar.help_menu_1_key_optional"));
showMOTDMI.setActionCommand(motd_action);
showMOTDMI.addActionListener(this);
helpMenu.add(showMOTDMI);
if (!glogin.isRunningOnMac())
{
helpMenu.addSeparator();
}
// "Java Version"
JMenuItem javaVersionMI = new JMenuItem(ts.l("createMenuBar.help_menu_2"));
setMenuMnemonic(javaVersionMI, ts.l("createMenuBar.help_menu_2_key_optional"));
javaVersionMI.setActionCommand(java_version_action);
javaVersionMI.addActionListener(this);
helpMenu.add(javaVersionMI);
menubar.add(fileMenu);
if (false && glogin.isRunningOnMac())
{
menubar.add(editMenu);
}
menubar.add(LandFMenu);
menubar.add(actionMenu);
menubar.add(windowMenu);
// we want to force the helpMenu to be on the far right side of
// the menu bar..
menubar.add(Box.createGlue());
menubar.add(helpMenu);
return menubar;
}
/**
* Generates a Mac-compliant menu item name if we're running on a
* Mac, else returns the text unmodified.
*/
private String dialogMenuName(String text)
{
if (glogin.isRunningOnMac())
{
return text + "...";
}
else
{
return text;
}
}
/**
* This helper method sets the mnemonic character for a menu item.
* The pattern string will generally be retrieved from the
* localization properties, and may be null. This method is
* designed to deal with null patterns, since any given menu item
* may not, in fact, have a mnemonic set in the properties resource.
*/
private void setMenuMnemonic(JMenuItem item, String pattern)
{
if (!glogin.isRunningOnMac() && pattern != null)
{
item.setMnemonic((int) pattern.charAt(0));
}
}
/**
* Sets a direct access accelerator on a menu item.
*/
private void setAccelerator(JMenuItem item, String key)
{
if (key == null)
{
return;
}
this.setAccelerator(item, key.toCharArray()[0]);
}
private void setAccelerator(JMenuItem item, char key)
{
item.setAccelerator(javax.swing.KeyStroke.getKeyStroke(key, java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
}
/**
* Create the tree component used in the left hand side of the client.
*/
private void createTree()
{
if (debug)
{
System.err.println("Creating tree");
}
/* pick up the pop-up menu strings from our localization
* resources */
String
hide = ts.l("createTree.hide_non_editable"), // "Hide Non-Editables"
show = ts.l("createTree.show_non_editable"), // "Show Non-Editables"
query = ts.l("createTree.query"), // "Query"
report_editable = ts.l("createTree.report_editable"), // "Report Editable"
report = ts.l("createTree.report"), // "Report All"
create = ts.l("createTree.create"), // "Create"
view = ts.l("createTree.view_object"), // "View Object"
edit = ts.l("createTree.edit_object"), // "Edit Object"
clone = ts.l("createTree.clone_object"), // "Clone Object"
delete = ts.l("createTree.delete_object"), // "Delete Object"
inactivate = ts.l("createTree.inactivate_object"), // "Inactivate Object"
reactivate = ts.l("createTree.reactivate_object"); // "Reactivate Object"
/* but our action commands are fixed. */
// note that a lot of these are given the same text, but a
// JMenuItem can only belong to one menu at a time, so we have to
// create multiple copies
pMenuAll.add(createMenuItem(hide, hide_pop_action));
pMenuAll.add(createMenuItem(query, query_pop_action));
pMenuAll.add(createMenuItem(report_editable, report_edit_pop_action));
pMenuAll.add(createMenuItem(report, report_pop_action));
pMenuEditable.add(createMenuItem(show, show_pop_action));
pMenuEditable.add(createMenuItem(query, query_pop_action));
pMenuEditable.add(createMenuItem(report_editable, report_edit_pop_action));
pMenuEditable.add(createMenuItem(report, report_pop_action));
pMenuAllCreatable.add(createMenuItem(hide, hide_pop_action));
pMenuAllCreatable.add(createMenuItem(query, query_pop_action));
pMenuAllCreatable.add(createMenuItem(report_editable, report_edit_pop_action));
pMenuAllCreatable.add(createMenuItem(report, report_pop_action));
pMenuAllCreatable.add(createMenuItem(create, create_pop_action));
pMenuEditableCreatable.add(createMenuItem(show, show_pop_action));
pMenuEditableCreatable.add(createMenuItem(query, query_pop_action));
pMenuEditableCreatable.add(createMenuItem(report_editable, report_edit_pop_action));
pMenuEditableCreatable.add(createMenuItem(report, report_pop_action));
pMenuEditableCreatable.add(createMenuItem(create, create_pop_action));
objectViewPM.add(createMenuItem(view, view_pop_action));
objectViewPM.add(createMenuItem(clone, clone_pop_action));
objectRemovePM.add(createMenuItem(view, view_pop_action));
objectRemovePM.add(createMenuItem(edit, edit_pop_action));
objectRemovePM.add(createMenuItem(clone, clone_pop_action));
objectRemovePM.add(createMenuItem(delete, delete_pop_action));
objectInactivatePM.add(createMenuItem(view, view_pop_action));
objectInactivatePM.add(createMenuItem(edit, edit_pop_action));
objectInactivatePM.add(createMenuItem(clone, clone_pop_action));
objectInactivatePM.add(createMenuItem(delete, delete_pop_action));
objectInactivatePM.add(createMenuItem(inactivate, inactivate_pop_action));
objectReactivatePM.add(createMenuItem(view, view_pop_action));
objectReactivatePM.add(createMenuItem(edit, edit_pop_action));
objectReactivatePM.add(createMenuItem(clone, clone_pop_action));
objectReactivatePM.add(createMenuItem(delete, delete_pop_action));
objectReactivatePM.add(createMenuItem(reactivate, reactivate_pop_action));
if (debug)
{
System.err.println("Loading images for tree");
}
ganymede_logo = _myglogin.ganymede_logo;
Image openFolder = PackageResources.getImageResource(this, "openfolder.gif", getClass());
Image closedFolder = PackageResources.getImageResource(this, "folder.gif", getClass());
Image list = PackageResources.getImageResource(this, "list.gif", getClass());
Image listnowrite = PackageResources.getImageResource(this, "listnowrite.gif", getClass());
Image redOpenFolder = PackageResources.getImageResource(this, "openfolder-red.gif", getClass());
Image redClosedFolder = PackageResources.getImageResource(this, "folder-red.gif", getClass());
search = PackageResources.getImageResource(this, "srchfol2.gif", getClass());
queryIcon = PackageResources.getImageResource(this, "query.gif", getClass());
cloneIcon = PackageResources.getImageResource(this, "clone.gif", getClass());
idleIcon = new ImageIcon(PackageResources.getImageResource(this, "nobuild.gif", getClass()));
buildUnknownIcon = new ImageIcon(PackageResources.getImageResource(this, "buildunknown.gif", getClass()));
buildIcon = new ImageIcon(PackageResources.getImageResource(this, "build1.gif", getClass()));
buildIcon2 = new ImageIcon(PackageResources.getImageResource(this, "build2.gif", getClass()));
trash = PackageResources.getImageResource(this, "trash.gif", getClass());
creation = PackageResources.getImageResource(this, "creation.gif", getClass());
newToolbarIcon = PackageResources.getImageResource(this, "newicon.gif", getClass());
pencil = PackageResources.getImageResource(this, "pencil.gif", getClass());
personaIcon = PackageResources.getImageResource(this, "persona.gif", getClass());
// inactivateIcon = PackageResources.getImageResource(this, "inactivate.gif", getClass());
// we'll use the pencil/editing image for our client's application icon
setIconImage(pencil);
createDialogImage = PackageResources.getImageResource(this, "wiz3b.gif", getClass());
treepencil = PackageResources.getImageResource(this, "treepencil.gif", getClass());
treetrash = PackageResources.getImageResource(this, "treetrash.gif", getClass());
treecreation = PackageResources.getImageResource(this, "treenewicon.gif", getClass());
Image remove = PackageResources.getImageResource(this, "remove.gif", getClass());
Image expire = PackageResources.getImageResource(this, "expire.gif", getClass());
images = new Image[NUM_IMAGE];
images[OPEN_BASE] = openFolder;
images[CLOSED_BASE ] = closedFolder;
images[OPEN_FIELD] = list;
images[OPEN_FIELD_DELETE] = treetrash;
images[OPEN_FIELD_CREATE] = treecreation;
images[OPEN_FIELD_CHANGED] = treepencil;
images[OPEN_FIELD_EXPIRESET] = expire;
images[OPEN_FIELD_REMOVESET] = remove;
images[CLOSED_FIELD] = list;
images[CLOSED_FIELD_DELETE] = treetrash;
images[CLOSED_FIELD_CREATE] = treecreation;
images[CLOSED_FIELD_CHANGED] = treepencil;
images[CLOSED_FIELD_EXPIRESET] = expire;
images[CLOSED_FIELD_REMOVESET] = remove;
images[OPEN_CAT] = redOpenFolder;
images[CLOSED_CAT] = redClosedFolder;
images[OBJECTNOWRITE] = listnowrite;
tree = new treeControl(new Font("SansSerif", Font.PLAIN, 12),
Color.black, Color.white, this, images,
null);
tree.setMinimumWidth(200);
}
private JMenuItem createMenuItem(String text, String actionCommand)
{
JMenuItem menuItem = new JMenuItem(text);
menuItem.setActionCommand(actionCommand);
return menuItem;
}
/**
* This method handles the start-up tasks after the gclient
* has gotten initialized. Called by glogin.
*/
public void start()
{
// open an initial transaction, in case the user doesn't change
// personae
try
{
ReturnVal rv = session.openTransaction("Ganymede GUI Client");
rv = handleReturnVal(rv);
if ((rv != null) && (!rv.didSucceed()))
{
return;
}
}
catch (Exception rx)
{
// "Could not open transaction."
processExceptionRethrow(rx, ts.l("start.transaction_open_failure"));
}
// If user has multiple personae and he has logged in without
// specifying an admin persona, ask which to start with.
if ((personae != null) && personae.size() > 1 && currentPersonaString.indexOf(':') == -1)
{
// changePersona will block until the user does something
// with the persona selection dialog
changePersona(false);
personaDialog.updatePassField(currentPersonaString);
}
// Check for MOTD on another thread
Thread motdThread = new Thread(new Runnable() {
public void run() {
try
{
// "Checking MOTD"
setStatus(ts.l("start.motd_msg"), 1);
StringBuffer m;
boolean html = true;
m = session.getMessageHTML("motd", true);
// if there wasn't an html motd message, maybe there's a
// txt message?
if (m == null)
{
m = session.getMessage("motd", true);
html = false;
}
// if we didn't get any message, fold it up, we're done
if (m == null)
{
return;
}
// and pop up the motd box back on the main GUI thread
// create final locals to bridge the gap into another
// method in the runnable to go on the GUI thread
final String textString = m.toString();
final boolean doHTML = html;
EventQueue.invokeLater(new Runnable() {
public void run() {
showMOTD(textString, doHTML);
}
});
}
catch (Exception rx)
{
// "Could not get MOTD"
processExceptionRethrow(rx, ts.l("start.motd_failure"));
}
}
});
motdThread.setPriority(Thread.NORM_PRIORITY);
motdThread.start();
}
/**
* Returns a vector of
* {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate}'s.
*
* @param id Object type id to retrieve field information for.
*/
public Vector<FieldTemplate> getTemplateVector(short id)
{
return loader.getTemplateVector(Short.valueOf(id));
}
/**
* Returns a {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate}
* based on the short type id for the containing object and the
* short field id for the field.
*/
public FieldTemplate getFieldTemplate(short objType, short fieldId)
{
for (FieldTemplate template: loader.getTemplateVector(Short.valueOf(objType)))
{
if (template.getID() == fieldId)
{
return template;
}
}
return null;
}
/**
* Returns a vector of
* {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate}'s
* listing fields and field informaton for the object type identified by
* id.
*
* @param id The id number of the object type to be returned the base id.
*/
public Vector<FieldTemplate> getTemplateVector(Short id)
{
return loader.getTemplateVector(id);
}
/**
* Clears out the client's
* {@link arlut.csd.ganymede.client.objectCache objectCache},
* which holds object labels, and activation status for invid's returned
* by various query and {@link arlut.csd.ganymede.rmi.db_field db_field}
* choices() operations.
*/
public void clearCaches()
{
if (debug)
{
System.err.println("Clearing caches");
}
cachedLists.clearCaches();
}
/**
* <p>Gets a list of objects from the server, in
* a form appropriate for use in constructing a list of nodes in the
* tree under an object type (object base) folder.</p>
*
* <p>This method supports client-side caching.. if the list required
* has already been retrieved, the cached list will be returned. If
* it hasn't, getObjectList() will get the list from the server and
* save a local copy in an
* {@link arlut.csd.ganymede.client.objectCache objectCache}
* for future requests.</p>
*/
public objectList getObjectList(Short id, boolean showAll)
{
objectList objectlist = null;
/* -- */
if (cachedLists.containsList(id))
{
if (debug)
{
System.err.println("gclient.getObjectList(" + id + ", " + showAll +
") getting objectlist from the cachedLists.");
}
objectlist = cachedLists.getList(id);
// If we are being asked for a *complete* list of objects of
// the given type and we only have editable objects of this
// type cached, we may need to go back to the server to
// get the full list.
if (showAll && !objectlist.containsNonEditable())
{
if (debug)
{
System.err.println("gclient.getObjectList(" + id + ", " + showAll +
") objectList incomplete, downloading non-editables.");
}
try
{
Query objQuery = new Query(id.shortValue(), null, false);
objQuery.setFiltered(true);
QueryResult qr = session.query(objQuery);
if (qr != null)
{
if (debug)
{
System.err.println("gclient.getObjectList(): augmenting");
}
objectlist.augmentListWithNonEditables(qr);
}
}
catch (Exception rx)
{
// "Could not do the query"
processExceptionRethrow(rx, ts.l("getObjectList.query_exception"));
}
cachedLists.putList(id, objectlist);
}
}
else
{
if (debug)
{
System.err.println("gclient.getObjectList(" + id + ", " + showAll +
") downloading objectlist from the server.");
}
try
{
Query objQuery = new Query(id.shortValue(), null, !showAll);
objQuery.setFiltered(true);
QueryResult qr = session.query(objQuery);
if (debug)
{
System.err.println("gclient.getObjectList(): caching copy");
}
objectlist = new objectList(qr);
cachedLists.putList(id, objectlist);
}
catch (Exception rx)
{
// "Could not get dump"
processExceptionRethrow(rx, ts.l("getObjectList.dump_exception"));
}
}
return objectlist;
}
/**
* Public accessor for the SecurityLaunderThread
*/
public int getBuildPhase()
{
return buildPhase;
}
/**
* By overriding update(), we can eliminate the annoying flash as
* the default update() method clears the frame before rendering.
*/
public void update(Graphics g)
{
paint(g);
}
/**
* Get the session
*/
public final Session getSession()
{
return session;
}
/**
* <p>Loads and returns the error Image for use in client dialogs.</p>
*
* <p>Once the image is loaded, it is cached for future calls to
* getErrorImage().</p>
*/
public final Image getErrorImage()
{
if (errorImage == null)
{
errorImage = PackageResources.getImageResource(this, "error.gif", getClass());
}
return errorImage;
}
/**
* <p>Loads and returns the question-mark Image for use in client dialogs.</p>
*
* <p>Once the image is loaded, it is cached for future calls to
* getQuestionmage().</p>
*/
public final Image getQuestionImage()
{
if (questionImage == null)
{
questionImage = PackageResources.getImageResource(this, "question.gif", getClass());
}
return questionImage;
}
/**
* <p>Loads and returns the neutral 'Info' Image for use in client dialogs.</p>
*
* <p>Once the image is loaded, it is cached for future calls to
* getInfoImage().</p>
*/
public final Image getInfoImage()
{
if (infoImage == null)
{
infoImage = PackageResources.getImageResource(this, "ok.gif", getClass());
}
return infoImage;
}
/**
* <p>Returns a hash mapping {@link arlut.csd.ganymede.common.BaseDump BaseDump}
* references to their title.</p>
*
* <p>Checks to see if the baseNames was loaded, and if not, it loads it.
* Always use this instead of trying to access baseNames directly.</p>
*/
public final Hashtable<Base, String> getBaseNames()
{
return loader.getBaseNames();
}
/**
* <p>Returns a Vector of {@link arlut.csd.ganymede.common.BaseDump BaseDump} objects,
* providing a local cache of {@link arlut.csd.ganymede.rmi.Base Base}
* references that the client consults during operations.</p>
*
* <p>Checks to see if the baseList was loaded, and if not, it loads it.
* Always use this instead of trying to access the baseList
* directly.</p>
*/
public final synchronized Vector<Base> getBaseList()
{
return loader.getBaseList();
}
/**
* <p>Returns a hash mapping Short {@link arlut.csd.ganymede.rmi.Base Base} id's to
* {@link arlut.csd.ganymede.common.BaseDump BaseDump} objects.</p>
*
* <p>Checks to see if the baseMap was loaded, and if not, it loads it.
* Always use this instead of trying to access the baseMap
* directly.</p>
*/
public Hashtable<Short, Base> getBaseMap()
{
return loader.getBaseMap();
}
/**
* <p>Returns a hashtable mapping {@link arlut.csd.ganymede.common.BaseDump BaseDump}
* references to their object type id in Short form. This is
* a holdover from a time when the client didn't create local copies
* of the server's Base references.</p>
*
* <p>Checks to see if the basetoShort was loaded, and if not, it loads it.
* Always use this instead of trying to access the baseToShort
* directly.</p>
*/
public Hashtable getBaseToShort()
{
return loader.getBaseToShort();
}
/**
* <p>Returns the type name for a given object.</p>
*
* <p>If the loader thread hasn't yet downloaded that information, this
* method will block until the information is available.</p>
*/
public String getObjectType(Invid objId)
{
return loader.getObjectType(objId);
}
/**
* <p>This method returns a concatenated string made up of the object
* type and object name. This string is localized in the
* gclient.properties file, and should be structured in a fashion
* suitable for inclusion in gclient message strings with their own
* localizations.</p>
*
* <p>Both the client and the server cache object information until a
* transaction deleting the object is committed, so it is explicitly
* legal to call getObjectDesignation() on Invids for objects that are
* being deleted in the current transaction.</p>
*/
public String getObjectDesignation(Invid objId)
{
ObjectHandle h = getObjectHandle(objId, null);
// "{0} {1}"
return ts.l("getObjectDesignation.combine_str", getObjectType(objId), h.getLabel());
}
/**
* <p>Pulls a object handle for an invid out of the
* client's cache, if it has been cached.</p>
*
* <p>If no handle for this invid has been cached, this method
* will attempt to retrieve one from the server.</p>
*/
public ObjectHandle getObjectHandle(Invid invid)
{
return this.getObjectHandle(invid, null);
}
/**
* <p>Pulls a object handle for an invid out of the
* client's cache, if it has been cached.</p>
*
* <p>If no handle for this invid has been cached, this method will
* attempt to retrieve one from the server.</p>
*
* <p>The Short type parameter is just a micro-optimizing
* convenience for code that already has such a Short
* constructed. This method will work perfectly well if
* the type parameter is null.</p>
*/
public ObjectHandle getObjectHandle(Invid invid, Short type)
{
ObjectHandle handle = null;
if (type == null)
{
type = Short.valueOf(invid.getType());
}
if (cachedLists.containsList(type))
{
handle = cachedLists.getInvidHandle(type, invid);
if (handle != null)
{
return handle;
}
}
// okay, we haven't found it. try to pull this invid down from the
// server, and cache it
Vector paramVec = new Vector();
paramVec.addElement(invid);
try
{
QueryResult result = session.queryInvids(paramVec);
Vector handleList = result.getHandles();
if (handleList.size() > 0)
{
handle = (ObjectHandle) handleList.elementAt(0);
}
}
catch (Exception ex)
{
processExceptionRethrow(ex);
}
return handle;
}
/**
* <p>This method is called to update the client's display of the
* number of users concurrently logged into the server.</p>
*
* @param loginCount The number of users logged into the Ganymede server
*/
public final void setLoginCount(int loginCount)
{
if (loginCount == 1)
{
loginLabel.setText(""); // we're the only users, we don't need a message
}
else
{
// "{0,number,#} users logged in."
loginLabel.setText(ts.l("setLoginCount.multi_login", Integer.valueOf(loginCount)));
}
}
/**
* Sets the text that will appear in the status bar when no other
* status is being displayed.
*
* @param status The text to display
*/
public final void setDefaultStatus(String status)
{
statusThread.setDefaultMessage(status);
}
/**
* Sets text in the status bar, with a 5 second countdown before
* the status bar is cleared.
*
* @param status The text to display
*/
public final void setStatus(String status)
{
setStatus(status, 5);
}
/**
* Sets text in the status bar, with a defined countdown before
* the status bar is cleared.
*
* @param status The text to display
* @param timeToLive Number of seconds to wait until clearing the status bar.
* If zero or negative, the status bar timer will not clear the field until
* the status bar is changed by another call to setStatus.
*/
public final void setStatus(String status, int timeToLive)
{
if (debug)
{
System.err.println("Setting status: " + status);
}
final String fStatus = status;
// use EventQueue.invokeLater so that we play nice
// with the Java display thread
EventQueue.invokeLater(new Runnable() {
public void run() {
statusLabel.setText(fStatus);
statusLabel.paintImmediately(statusLabel.getVisibleRect());
}
});
statusThread.setClock(timeToLive);
}
/**
* This method is triggered by the Ganymede server if the client
* is idle long enough. This method will downgrade the user's
* login to a minimum privilege level if possible, requiring
* the user to enter their admin password again to regain
* admin privileges.
*/
public final void softTimeout()
{
// we use invokeLater so that we free up the RMI thread
// which messaged us, and so we play nice with the Java
// display thread
EventQueue.invokeLater(new Runnable() {
public void run() {
personaListener.softTimeOutHandler();
}
});
}
/**
* <p>Updates the status icon, based on an enumerated list of strings
* that can be provided from the server. These are "idle",
* "building", and "building2". The "building" state applies when
* the server is currently running builderPhase1. That is, when the
* server is (at least partially) locked while it dumps out data
* files.</p>
*
* <p>The "building2" phase is in effect when the server is unlocked
* and has one or more threads waiting for the completion of
* external build scripts.</p>
*
* @param status The text to key off of.
*/
public final void setBuildStatus(String status)
{
if (debug)
{
System.err.println("Setting build status: " + status);
}
if (status.equals("idle"))
{
buildPhase = 0;
}
else if (status.equals("building"))
{
buildPhase = 1;
}
else if (status.equals("building2"))
{
buildPhase = 2;
}
else
{
buildPhase = -1;
}
try
{
securityThread.setBuildStatus(buildPhase);
}
catch (NullPointerException ex)
{
}
}
/**
* Returns the node of the object currently selected in the tree, if
* any. Returns null if there are no nodes selected in the tree, of
* if the node selected in the tree is not an object node.
*/
public InvidNode getSelectedObjectNode()
{
// get our own copy of the current node so
// that we don't get tripped up by threading
treeNode myNode = selectedNode;
if ((myNode == null) ||
!(myNode instanceof InvidNode))
{
return null;
}
else
{
return (InvidNode) myNode;
}
}
/**
* Get the current text from the client's status field
*/
public String getStatus()
{
return statusLabel.getText();
}
/**
* <p>Show the help window.</p>
*
* <p>This might someday take an argument, which would show a
* starting page or some more specific help.</p>
*/
public void showHelpWindow()
{
if (help == null)
{
help = new helpPanel(this);
}
else
{
help.setVisible(true);
}
}
/**
* Shows the Java Version dialog.
*/
public void showJavaVersion()
{
if (java_ver_dialog == null)
{
// "Java Version"
java_ver_dialog = new aboutJavaDialog(this, ts.l("showJavaVersion.dialog_title"));
}
java_ver_dialog.setVisible(true);
}
/**
* Shows the About... dialog.
*/
public void showAboutMessage()
{
if (about == null)
{
// "About Ganymede"
about = new aboutGanyDialog(this, ts.l("showAboutMessage.dialog_title"));
}
else
{
// "About Ganymede"
about.setTitle(ts.l("showAboutMessage.dialog_title"));
}
about.setVisible(true);
}
/**
* Shows the server's message of the day in a dialog.
*/
public void showMOTD()
{
// This will always get the MOTD, even if we've seen it
StringBuffer m;
try
{
m = session.getMessageHTML("motd", false);
if (m == null)
{
m = session.getMessage("motd", false);
if (m != null)
{
showMOTD(m.toString(), false);
}
}
else
{
showMOTD(m.toString(), true);
}
}
catch (Exception rx)
{
processExceptionRethrow(rx, "Could not get motd");
}
}
/**
* This method generates a message-of-the-day dialog.
*
* @param message The message to display. May be multiline.
* @param html If true, showMOTD() will display the motd with a html
* renderer, in Swing 1.1b2 and later.
*/
public void showMOTD(String message, boolean html)
{
if (motd == null)
{
// "MOTD"
motd = new messageDialog(client, ts.l("showMOTD.dialog_title"), null);
}
if (html)
{
motd.setHtmlText(message);
}
else
{
motd.setPlainText(message);
}
motd.setVisible(true);
}
/**
* <p>This method is used to display an error dialog for the given
* exception, and to rethrow it as a RuntimeException.</p>
*
* <p>Potentially useful when catching RemoteExceptions from the
* server.</p>
*/
public final void processExceptionRethrow(Throwable ex)
{
processException(ex);
throw new RuntimeException(ex);
}
/**
* <p>This method is used to display an error dialog for the given
* exception, and to rethrow it as a RuntimeException.</p>
*
* <p>Potentially useful when catching RemoteExceptions from the
* server.</p>
*/
public final void processExceptionRethrow(Throwable ex, String message)
{
processException(ex, message);
throw new RuntimeException(ex);
}
/**
* This method is used to display an error dialog for the given
* exception.
*/
public final void processException(Throwable ex)
{
processException(ex, null);
}
/**
* This method is used to display an error dialog for the given
* exception.
*/
public final void processException(Throwable ex, String message)
{
// make sure we're not processing an exception that has been
// rethrown by processExceptionRethrow..
boolean foundRethrow = false;
StackTraceElement[] frames = ex.getStackTrace();
for (int i = 0; !foundRethrow && i < frames.length; i++)
{
StackTraceElement frame = frames[i];
if (frame.getMethodName().equals("processExceptionRethrow") &&
frame.getClassName().endsWith("gclient"))
{
foundRethrow = true;
}
}
if (foundRethrow)
{
return;
}
if (ex instanceof NotLoggedInException)
{
showNotLoggedIn();
}
else
{
if (ex instanceof RegexpException)
{
// don't bother showing them the stack trace (or offering
// to transmit the error to the server) if they entered a
// bad regexp into a dialog
showErrorMessage(ex.getMessage());
}
else
{
String exceptionString = gclient.stackTrace(ex);
if (message != null)
{
exceptionString = message + "\n" + exceptionString;
}
// "Exception"
showExceptionMessage(ts.l("processException.exception"),
exceptionString);
}
}
setNormalCursor();
}
/**
* Show an Exception dialog, and offer to transmit the error message
* to the server. By default, the icon displayed will be the
* standard Ganymede error icon.
*
* @param title title of dialog.
* @param message Text of dialog.
*/
public final void showExceptionMessage(String title, String message)
{
this.showExceptionMessage(title, message, getErrorImage());
}
/**
* Show an Exception dialog, and offer to transmit the error message to
* the server.
*
* @param title title of dialog.
* @param message Text of dialog.
* @param icon optional icon to display.
*/
public final void showExceptionMessage(String title, String message, Image icon)
{
if (debug)
{
System.err.println("Error message: " + message);
}
final gclient gc = this;
final String Title = title;
final String Message = message;
final Image fIcon = icon;
EventQueue.invokeLater(new Runnable()
{
public void run()
{
ExceptionDialog x = new ExceptionDialog(gc, Title, Message, fIcon); // implicit show
if (x.didRequestReport())
{
boolean success = false;
try
{
session.reportClientBug(aboutJavaDialog.getVersionInfoString(), Message);
success = true;
}
catch (Throwable ex)
{
// ignore
}
if (success)
{
// "Exception Reported"
// "This possible error
// condition has been
// reported to the
// Ganymede
// server.\n\nThank you!"
new JErrorDialog(gc,
ts.l("showExceptionMessage.exception_reported"),
ts.l("showExceptionMessage.thank_you"),
getInfoImage(), StandardDialog.ModalityType.DOCUMENT_MODAL); // implicit show
}
else
{
// "Failure Reporting Exception"
// "This error condition
// could not be reported
// successfully to the
// server. Perhaps the
// server or your network
// has gone down?"
new JErrorDialog(gc,
ts.l("showExceptionMessage.failure_reporting"),
ts.l("showExceptionMessage.failure_explanation"),
getErrorImage(), StandardDialog.ModalityType.DOCUMENT_MODAL); // implicit show
}
}
}
});
setStatus(title + ": " + message, 10);
}
/**
* Display a message explaining that the user is no longer logged in
*/
public final void showNotLoggedIn()
{
// "Error"
// "Not logged in to the server"
showErrorMessage(ts.l("global.error"),
ts.l("showNotLoggedIn.not_logged_in"));
}
/**
* Pops up an error dialog with the default title.
*/
public final void showErrorMessage(String message)
{
// "Error"
showErrorMessage(ts.l("global.error"), message);
}
/**
* Pops up an error dialog. Pre-defines the icon for the dialog as
* the standard Ganymede error icon.
*/
public final void showErrorMessage(String title, String message)
{
showErrorMessage(title, message, getErrorImage());
}
/**
* Show an error dialog.
*
* @param title title of dialog.
* @param message Text of dialog.
* @param icon optional icon to display.
*/
public final void showErrorMessage(String title, String message, Image icon)
{
if (debug)
{
System.err.println("Error message: " + message);
}
final gclient gc = this;
final String Title = title;
final String Message = message;
final Image fIcon = icon;
EventQueue.invokeLater(new Runnable()
{
public void run()
{
new JErrorDialog(gc, Title, Message, fIcon, StandardDialog.ModalityType.DOCUMENT_MODAL); // implicit show
}
});
setStatus(title + ": " + message, 10);
}
/**
* Set the cursor to a wait cursor(usually a watch.)
*/
public void setWaitCursor()
{
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
/**
* Set the cursor to the normal cursor(usually a pointer.)
*
* This is dependent on the operating system.
*/
public void setNormalCursor()
{
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
/**
* <p>Call this method to enable indicators that something in the
* database was changed, so cancelling this transaction will have
* consequences.</p>
*
* <p>This should be called whenever the client makes any changes to
* the database. That includes creating objects, editting fields of
* objects, removing objects, renaming, expiring, deleting,
* inactivating, and so on. It is very important to call this
* whenever something might have changed.</p>
*/
public final void somethingChanged()
{
commit.setEnabled(true);
cancel.setEnabled(true);
setSomethingChanged(true);
}
/**
* Sets or clears the client's somethingChanged flag.
*/
private void setSomethingChanged(boolean state)
{
if (debug)
{
System.err.println("Setting somethingChanged to " + state);
}
somethingChanged = state;
}
/**
* True if something has been changed since the last commit/cancel
*/
public boolean getSomethingChanged()
{
return somethingChanged;
}
/**
* Returns true if this Invid has been created or edited during the
* current transaction.
*/
public boolean invidHasBeenEdited(Invid invid)
{
return changedSet.contains(invid) || createHash.containsKey(invid);
}
/**
* True if we are in an applet context, meaning we don't have access
* to local files, etc.
*/
public boolean isApplet()
{
return _myglogin.isApplet();
}
/**
* <p>This method takes a ReturnVal object from the server and, if
* necessary, runs through a wizard interaction sequence, possibly
* displaying several dialogs before finally returning a final
* result code.</p>
*
* <p>Use the ReturnVal returned from this function after this
* function is called to determine the ultimate success or failure
* of any operation which returns ReturnVal, because a wizard
* sequence may determine the ultimate result.</p>
*
* <p>This method should not be synchronized, since handleReturnVal
* may pop up modal (thread-blocking) dialogs, and if we we
* synchronize this, some Swing or AWT code seems to block on our
* synchronization when we do pop-up dialogs. It's not any of my
* code, so I assume that AWT tries to synchronize on the frame when
* parenting a new dialog.</p>
*/
public ReturnVal handleReturnVal(ReturnVal retVal)
{
Hashtable dialogResults;
/* -- */
if (debug)
{
System.err.println("gclient.handleReturnVal(): Entering");
}
wizardActive++;
try
{
while ((retVal != null) && (retVal.getDialog() != null))
{
if (retVal.getErrorType() == ErrorTypeEnum.SHOWOBJECT)
{
Invid objInvid = findParentInvid(retVal.getInvid());
if (objInvid != null)
{
if (wp.isOpenForEdit(objInvid))
{
wp.showWindow(objInvid);
}
else if (invidHasBeenEdited(objInvid) && !deleteHash.containsKey(objInvid))
{
this.editObject(objInvid, null);
}
}
}
if (debug)
{
System.err.println("gclient.handleReturnVal(): retrieving dialog");
}
JDialogBuff jdialog = retVal.getDialog();
if (debug)
{
System.err.println("gclient.handleReturnVal(): extracting dialog");
}
DialogRsrc resource = jdialog.extractDialogRsrc(this, null);
if (debug)
{
System.err.println("gclient.handleReturnVal(): constructing dialog");
}
StringDialog dialog = new StringDialog(resource, StandardDialog.ModalityType.DOCUMENT_MODAL);
if (debug)
{
System.err.println("gclient.handleReturnVal(): displaying dialog");
}
setWaitCursor();
try
{
// display the Dialog sent to us by the server, get the
// result of the user's interaction with it.
dialogResults = dialog.showDialog();
}
finally
{
setNormalCursor();
}
if (debug)
{
System.err.println("gclient.handleReturnVal(): dialog done");
}
if (retVal.getCallback() != null)
{
try
{
if (debug)
{
System.err.println("gclient.handleReturnVal(): Sending result to callback: " + dialogResults);
}
// send the dialog results to the server
retVal = retVal.getCallback().respond(dialogResults);
if (debug)
{
System.err.println("gclient.handleReturnVal(): Received result from callback.");
}
}
catch (Exception ex)
{
processExceptionRethrow(ex);
}
}
else
{
if (debug)
{
System.err.println("gclient.handleReturnVal(): No callback, breaking");
}
break; // we're done
}
}
}
finally
{
wizardActive--;
}
if (debug)
{
System.err.println("gclient.handleReturnVal(): Done with wizards, checking retVal for rescan.");
}
// Check for objects that need to be rescanned
if (retVal != null && retVal.objectLabelChanged())
{
wp.relabelObject(retVal.getInvid(), retVal.getNewLabel());
}
if (retVal == null || !retVal.doRescan())
{
return retVal;
}
if (debug)
{
System.err.println("gclient.handleReturnVal(): rescan dump: " + retVal.dumpRescanInfo());
}
Vector<Invid> objects = retVal.getRescanObjectsList();
if (objects == null)
{
if (debug)
{
System.err.println("gclient.handleReturnVal(): Odd, was told to rescan, but there's nothing there!");
}
return retVal;
}
if (debug)
{
System.err.println("gclient.handleReturnVal(): Rescanning " + objects.size() + " objects.");
}
for (Invid invid: objects)
{
if (debug)
{
System.err.println("gclient.handleReturnVal(): updating invid: " + invid);
}
wp.refreshObjectWindows(invid, retVal);
}
if (debug)
{
System.err.println("gclient.handleReturnVal(): Exiting handleReturnVal");
}
return retVal;
}
/**
* <p>Find the top-most parent Invid of the given Invid.</p>
*
* @return null if invid is null or if there is an exception trying
* to talk to the server to find the top-most parent.
*/
public Invid findParentInvid(Invid invid)
{
if (invid == null)
{
return null;
}
Invid parentInvid = invid;
try
{
while (true)
{
ReturnVal rv = handleReturnVal(session.view_db_object(parentInvid));
if (!ReturnVal.didSucceed(rv))
{
return null;
}
db_object o = (db_object) rv.getObject();
if (!o.isEmbedded())
{
return o.getInvid();
}
parentInvid = (Invid) o.getFieldValue(SchemaConstants.ContainerField);
}
}
catch (RemoteException ex)
{
return null;
}
}
// Private methods
/**
* Creates and initializes the client's toolbar.
*/
JToolBar createToolbar()
{
Insets insets = new Insets(0,0,0,0);
JToolBar toolBarTemp = new JToolBar();
toolBarTemp.setBorderPainted(true);
toolBarTemp.setFloatable(false); // let's get rid of the stupid floating toolbar
toolBarTemp.setMargin(insets);
// "Create"
JButton b = new JButton(ts.l("createToolbar.create_button"), new ImageIcon(newToolbarIcon));
b.setMargin(insets);
b.setActionCommand(create_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Create a new object"
b.setToolTipText(ts.l("createToolbar.create_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
// "Edit"
b = new JButton(ts.l("createToolbar.edit_button"), new ImageIcon(pencil));
b.setMargin(insets);
b.setActionCommand(edit_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Edit an object"
b.setToolTipText(ts.l("createToolbar.edit_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
// "Delete"
b = new JButton(ts.l("createToolbar.delete_button"), new ImageIcon(trash));
b.setMargin(insets);
b.setActionCommand(delete_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Delete an object"
b.setToolTipText(ts.l("createToolbar.delete_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
// "Clone"
b = new JButton(ts.l("createToolbar.clone_button"), new ImageIcon(cloneIcon));
b.setMargin(insets);
b.setActionCommand(clone_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Clone an object"
b.setToolTipText(ts.l("createToolbar.clone_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
// "View"
b = new JButton(ts.l("createToolbar.view_button"), new ImageIcon(search));
b.setMargin(insets);
b.setActionCommand(view_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "View an object"
b.setToolTipText(ts.l("createToolbar.view_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
// "Query"
b = new JButton(ts.l("createToolbar.query_button"), new ImageIcon(queryIcon));
b.setMargin(insets);
b.setActionCommand(query_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Compose a query"
b.setToolTipText(ts.l("createToolbar.query_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
// If we decide to have an inactivate-type button on toolbar...
// b = new JButton("Inactivate", new ImageIcon(inactivateIcon));
// //b = new JButton(new ImageIcon(inactivateIcon));
// b.setMargin(insets);
// b.setActionCommand("inactivate an object");
// b.setVerticalTextPosition(b.BOTTOM);
// b.setHorizontalTextPosition(b.CENTER);
// b.setToolTipText("Inactivate an object");
// b.addActionListener(this);
// toolBarTemp.add(b);
if ((personae != null) && personae.size() > 1)
{
// "Persona"
b = new JButton(ts.l("createToolbar.persona_button"), new ImageIcon(personaIcon));
b.setMargin(insets);
b.setActionCommand(persona_action);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
// "Change Persona"
b.setToolTipText(ts.l("createToolbar.persona_tooltip"));
b.addActionListener(this);
toolBarTemp.add(b);
}
return toolBarTemp;
}
public UnifiedToolBar createMacToolBar()
{
UnifiedToolBar macToolBar = new UnifiedToolBar();
// "Create"
JButton b = new JButton(ts.l("createToolbar.create_button"), new ImageIcon(newToolbarIcon));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(create_action);
b.addActionListener(this);
macToolBar.addComponentToLeft(b);
// "Edit"
b = new JButton(ts.l("createToolbar.edit_button"), new ImageIcon(pencil));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(edit_action);
b.addActionListener(this);
macToolBar.addComponentToLeft(b);
// "Delete"
b = new JButton(ts.l("createToolbar.delete_button"), new ImageIcon(trash));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(delete_action);
b.addActionListener(this);
macToolBar.addComponentToLeft(b);
// "Clone"
b = new JButton(ts.l("createToolbar.clone_button"), new ImageIcon(cloneIcon));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(clone_action);
b.addActionListener(this);
macToolBar.addComponentToLeft(b);
// "View"
b = new JButton(ts.l("createToolbar.view_button"), new ImageIcon(search));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(view_action);
b.addActionListener(this);
macToolBar.addComponentToLeft(b);
// "Query"
b = new JButton(ts.l("createToolbar.query_button"), new ImageIcon(queryIcon));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(query_action);
b.addActionListener(this);
macToolBar.addComponentToCenter(b);
if ((personae != null) && personae.size() > 1)
{
// "Persona"
b = new JButton(ts.l("createToolbar.persona_button"), new ImageIcon(personaIcon));
b.setBorderPainted(false);
b.setVerticalTextPosition(b.BOTTOM);
b.setHorizontalTextPosition(b.CENTER);
b.setActionCommand(persona_action);
b.addActionListener(this);
macToolBar.addComponentToRight(b);
}
return macToolBar;
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//// Tree Stuff
////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* <p>Clears out the client's tree.</p>
*
* <p>All Nodes will be removed, and the Category and BaseNodes will
* be rebuilt. No InvidNodes will be added.</p>
*/
void clearTree()
{
tree.clearTree();
try
{
buildTree();
}
catch (Exception rx)
{
// "Could not rebuild tree"
processExceptionRethrow(rx, ts.l("clearTree.exception"));
}
}
/**
* This method builds the initial data structures for the object
* selection tree, using the base information in the baseHash
* hashtable gained from the {@link arlut.csd.ganymede.client.Loader Loader}
* thread.
*/
void buildTree() throws RemoteException
{
if (debug)
{
System.err.println("gclient.buildTree(): Building tree");
}
// clear the invidNodeHash
invidNodeHash.clear();
CategoryTransport transport = session.getCategoryTree(hideNonEditables);
// get the category dump, save it
dump = transport.getTree();
if (debug)
{
System.err.println("gclient.buildTree(): got root category: " + dump.getName());
}
recurseDownCategories(null, dump);
if (debug)
{
System.err.println("gclient.buildTree(): Refreshing tree");
}
tree.refresh();
if (debug)
{
System.err.println("gclient.buildTree(): Done building tree,");
}
}
/**
* Recurses down the category tree obtained from the server, loading
* the client's tree with category and object folder nodes.
*/
void recurseDownCategories(CatTreeNode node, Category c) throws RemoteException
{
Vector
children;
CategoryNode cNode;
treeNode
prevNode;
/* -- */
children = c.getNodes();
prevNode = null;
for (int i = 0; i < children.size(); i++)
{
// find the CategoryNode at this point in the server's category tree
cNode = (CategoryNode) children.elementAt(i);
if (cNode instanceof Base)
{
Base base = (Base) cNode;
if (base.isEmbedded())
{
continue; // we don't want to present embedded objects
}
}
// if we have a single category at this level, we don't want
// to bodily insert it into the tree.. we'll just continue to
// recurse down with it.
if ((cNode instanceof Category) && (children.size() == 1))
{
recurseDownCategories(node, (Category) cNode);
}
else
{
prevNode = insertCategoryNode(cNode, prevNode, node);
if (prevNode instanceof CatTreeNode)
{
recurseDownCategories((CatTreeNode)prevNode, (Category) cNode);
}
}
}
}
/**
* Helper method for building tree
*/
treeNode insertCategoryNode(CategoryNode node, treeNode prevNode, treeNode parentNode) throws RemoteException
{
treeNode newNode = null;
if (node instanceof Base)
{
Base base = (Base)node;
boolean canCreate = base.canCreate(getSession());
BaseNode newBaseNode = new BaseNode(parentNode, base.getName(), base, prevNode,
true,
OPEN_BASE,
CLOSED_BASE,
canCreate ? pMenuEditableCreatable : pMenuEditable,
canCreate);
newNode = newBaseNode;
newBaseNode.showAll(!hideNonEditables);
shortToBaseNodeHash.put(newBaseNode.getTypeID(), newBaseNode);
}
else if (node instanceof Category)
{
Category category = (Category)node;
newNode = new CatTreeNode(parentNode, category.getName(), category,
prevNode, true,
OPEN_CAT,
CLOSED_CAT,
null);
}
else
{
// for FindBugs. Shouldn't happen due to tree structure
throw new RuntimeException("gclient.insertCategoryNode(): Unknown instance: " + node);
}
if ((newNode.getParent() == null) && (newNode.getPrevSibling() == null))
{
tree.setRoot(newNode);
}
else
{
tree.insertNode(newNode, false);
}
return newNode;
}
/**
* This method is used to update the list of object nodes under a given
* base node in our object selection tree, synchronizing the tree with
* the actual objects on the server.
*
* @param node Tree node corresponding to the object type being refreshed
* in the client's tree.
* @param doRefresh If true, causes the tree to update its display.
*/
void refreshObjects(BaseNode node, boolean doRefresh) throws RemoteException
{
Invid invid = null;
String label = null;
InvidNode oldNode, newNode, fNode;
ObjectHandle handle = null;
Vector<ObjectHandle> objectHandles;
objectList objectlist = null;
Short Id;
/* -- */
Id = node.getTypeID();
// get the object list.. this call will automatically handle
// caching for us.
objectlist = getObjectList(Id, node.isShowAll());
objectHandles = objectlist.getObjectHandles(true, node.isShowAll()); // include inactives
// **
//
// The loop below goes over the sorted list of objectHandles and
// the sorted list of nodes in the tree under this particular baseNode,
// comparing as the loop progresses, adding or removing nodes from the
// tree to match the current contents of the objectHandles list
//
// The important variables in the loop are fNode, which points to the
// current node in the subtree that we're examining, and i, which
// is counting our way through the objectHandles Vector.
//
// **
oldNode = null;
fNode = (InvidNode) node.getChild();
int i = 0;
while ((i < objectHandles.size()) || (fNode != null))
{
if (i < objectHandles.size())
{
handle = objectHandles.get(i);
if (!node.isShowAll() && !handle.isEditable())
{
i++; // skip this one, we're not showing non-editables
continue;
}
invid = handle.getInvid();
label = handle.getLabel();
}
else
{
// We've gone past the end of the list of objects in this
// object list.. from here on out, we're going to wind up
// removing anything we find in this subtree
handle = null;
label = null;
invid = null;
}
// insert a new node in the tree, change the label, or remove
// a node
if ((fNode == null) ||
((invid != null) &&
((label.compareToIgnoreCase(fNode.getText())) < 0)))
{
// If we have an invid/label in the object list that's not
// in the tree, we need to insert it
// "{0} (inactive)"
InvidNode objNode = new InvidNode(node,
handle.isInactive() ? ts.l("global.inactive_pattern", label):label,
invid,
oldNode, false,
handle.isEditable() ? OPEN_FIELD : OBJECTNOWRITE,
handle.isEditable() ? CLOSED_FIELD : OBJECTNOWRITE,
handle.isEditable() ? (node.canInactivate()
? objectInactivatePM : objectRemovePM) : objectViewPM,
handle);
if (invid != null)
{
invidNodeHash.put(invid, objNode);
if (createdObjectsWithoutNodes.containsKey(invid))
{
if (false)
{
System.err.println("Found this object in the creating objectsWithoutNodes hash: " +
handle.getLabel());
}
// "New Object"
createHash.put(invid, new CacheInfo(node.getTypeID(),
(handle.getLabel() == null) ? ts.l("global.new_object") : handle.getLabel(),
null, handle));
createdObjectsWithoutNodes.remove(invid);
}
setIconForNode(invid);
}
tree.insertNode(objNode, false);
oldNode = objNode;
fNode = (InvidNode) oldNode.getNextSibling();
i++;
}
else if ((invid == null) ||
((label.compareToIgnoreCase(fNode.getText())) > 0))
{
// We've found a node in the tree without a matching
// node in the object list. Delete it!
newNode = (InvidNode) fNode.getNextSibling();
tree.deleteNode(fNode, false);
fNode = newNode;
}
else if (fNode.getInvid().equals(invid))
{
// we've got a node in the tree that matches the
// invid of the current object in the object list,
// but the label may possibly have changed, so we'll
// go ahead and re-set the label, just to be sure
if (handle.isInactive())
{
// {0} (inactive)
fNode.setText(ts.l("global.inactive_pattern", label));
}
else
{
fNode.setText(label);
}
oldNode = fNode;
fNode = (InvidNode) oldNode.getNextSibling();
setIconForNode(invid);
i++;
}
}
if (doRefresh)
{
tree.refresh();
}
}
/**
* <p>Updates the tree for the nodes that might have changed.</p>
*
* <p>This method fixes all the icons, removing icons that were
* marked as to-be-deleted or dropped, and cleans out the various
* hashes. Only call this when commit is clicked. This replaces
* refreshTree(boolean committed), because all the refreshing to be
* done after a cancel is now handled in the cancelTransaction()
* method directly.</p>
*
* <p>This method is precisely analagous in function to {@link
* arlut.csd.ganymede.client.gclient#cleanUpAfterCancel()
* cleanUpAfterCancel()}, except for use after a commit.</p>
*/
void refreshTreeAfterCommit() throws RemoteException
{
//
// First get rid of deleted nodes
//
synchronized (deleteHash)
{
for (Invid invid: deleteHash.keySet())
{
InvidNode node = invidNodeHash.get(invid);
if (node != null)
{
if (debug)
{
System.err.println("gclient.refreshTreeAfterCommit(): Deleting node: " + node.getText());
}
tree.deleteNode(node, false);
invidNodeHash.remove(invid);
}
// and be sure we close any view windows held open that
// show the deleted object
wp.closeInvidWindows(invid);
}
deleteHash.clear();
}
//
// Now change the created nodes
//
Vector<Invid> changedInvids = new Vector<Invid>();
changedInvids.addAll(createHash.keySet());
changedInvids.addAll(changedSet);
createHash.clear();
changedSet.clear();
createdObjectsWithoutNodes.clear();
if (changedInvids.size() > 0)
{
if (debug)
{
System.err.println("gclient.refreshTreeAfterCommit(): refreshing created objects");
}
refreshChangedObjectHandles(changedInvids, true);
if (debug)
{
System.err.println("gclient.refreshTreeAfterCommit(): done refreshing created objects");
}
}
tree.refresh();
}
/**
* <p>Queries the server for status information on a vector of
* {@link arlut.csd.ganymede.common.Invid invid}'s that were touched
* in some way by the client during the recent transaction.
* The results from the queries are used to update the icons
* in the tree.</p>
*
* <p>Called by refreshTreeAfterCommit().</p>
*
* <p>This method is called from
* {@link arlut.csd.ganymede.client.gclient#refreshTreeAfterCommit() refreshTreeAfterCommit()}.</p>
*
* @param paramVect Vector of invid's to refresh.
* @param afterCommit If true, this method will update the client's status
* bar as it progresses.
*/
public void refreshChangedObjectHandles(Vector paramVect, boolean afterCommit)
{
Invid invid;
Short objectTypeKey = null;
/* -- */
try
{
QueryResult result = session.queryInvids(paramVect);
// now get the results
Vector<ObjectHandle> handleList = result.getHandles();
// and update anything we've got in the tree
for (ObjectHandle newHandle: handleList)
{
invid = newHandle.getInvid();
objectTypeKey = Short.valueOf(invid.getType());
InvidNode nodeToUpdate = invidNodeHash.get(invid);
if (nodeToUpdate != null)
{
if (debug)
{
System.err.println("gclient.refreshChangedObjectHandles(): got object handle refresh for " +
newHandle.debugDump());
}
nodeToUpdate.setHandle(newHandle);
if (paramVect == null)
{
changedSet.remove(newHandle.getInvid());
}
setIconForNode(newHandle.getInvid());
}
else if (debug)
{
System.err.println("gclient.refreshChangedObjectHandles(): null node for " +
newHandle.debugDump());
}
// and update our tree cache for this item
objectList list = cachedLists.getList(objectTypeKey);
if (list != null)
{
list.removeInvid(newHandle.getInvid());
list.addObjectHandle(newHandle);
}
}
}
catch (Exception ex)
{
// "Couldn''t refresh object tree"
processExceptionRethrow(ex, ts.l("refreshChangedObjectHandles.exception"));
}
}
/**
* This method does the same thing as refreshChangedObjectHandles(), but
* for a single object only.
*/
public void refreshChangedObject(Invid invid)
{
Vector paramVec = new Vector();
paramVec.addElement(invid);
refreshChangedObjectHandles(paramVec, false);
}
/**
* <p>Updates a database object's icon in the tree display. This
* method uses the various client-side caches and hashes to
* determine the proper icon for the node.</p>
*
* <p>This method does not actually induce the tree to refresh
* itself, and may be called in bulk for a lot of nodes
* efficiently.</p>
*/
public void setIconForNode(Invid invid)
{
final boolean treeNodeDebug = false;
InvidNode node = (InvidNode) invidNodeHash.get(invid);
if (node == null)
{
return;
}
ObjectHandle handle = node.getHandle();
// if we can't edit it, assume it'll never be anything other
// than inaccessible
if (!handle.isEditable())
{
node.setImages(OBJECTNOWRITE, OBJECTNOWRITE);
node.setMenu(objectViewPM);
return;
}
// The order here matters, because it might be in more than
// one hash. So put the most important stuff first
if (deleteHash.containsKey(invid))
{
if (treeNodeDebug)
{
System.err.println("Setting icon to delete.");
}
node.setImages(OPEN_FIELD_DELETE, CLOSED_FIELD_DELETE);
}
else if (createHash.containsKey(invid))
{
if (treeNodeDebug)
{
System.err.println("Setting icon to create.");
}
node.setImages(OPEN_FIELD_CREATE, CLOSED_FIELD_CREATE);
}
else
{
if (handle.isInactive())
{
if (treeNodeDebug)
{
System.err.println("inactivate");
}
// "{0} (inactive)"
node.setText(ts.l("global.inactive_pattern", handle.getLabel()));
node.setMenu(objectReactivatePM);
node.setImages(OPEN_FIELD_REMOVESET, CLOSED_FIELD_REMOVESET);
}
else
{
node.setText(handle.getLabel());
BaseDump bd = (BaseDump) getBaseMap().get(Short.valueOf(node.getInvid().getType()));
if (bd.canInactivate())
{
node.setMenu(objectInactivatePM);
}
else
{
node.setMenu(objectRemovePM);
}
// now take care of the rest of the menus.
if (handle.isExpirationSet())
{
if (treeNodeDebug)
{
System.err.println("isExpirationSet");
}
node.setImages(OPEN_FIELD_EXPIRESET, CLOSED_FIELD_EXPIRESET);
}
else if (handle.isRemovalSet())
{
if (treeNodeDebug)
{
System.err.println("isRemovalSet()");
}
node.setImages(OPEN_FIELD_REMOVESET, CLOSED_FIELD_REMOVESET);
}
else if (changedSet.contains(invid))
{
if (treeNodeDebug)
{
System.err.println("Setting icon to edit.");
}
node.setImages(OPEN_FIELD_CHANGED, CLOSED_FIELD_CHANGED);
}
else // nothing special in handle
{
node.setImages(OPEN_FIELD, CLOSED_FIELD);
}
}
}
}
/**
* This method is called to prompt the user for an XML file to be
* submitted to the server, which it will proceed to do.
*/
public void processXMLSubmission()
{
File file = null;
JFileChooser chooser = new JFileChooser();
/* -- */
chooser.setDialogType(JFileChooser.OPEN_DIALOG);
chooser.setDialogTitle(ts.l("processXMLSubmission.file_dialog_title")); // "Ganymede XML File"
if (this.prefs != null)
{
String defaultPath = this.prefs.get("file_load_default_dir", null);
if (defaultPath != null)
{
chooser.setCurrentDirectory(new File(defaultPath));
}
}
int returnValue = chooser.showDialog(this, null);
if (!(returnValue == JFileChooser.APPROVE_OPTION))
{
return;
}
file = chooser.getSelectedFile();
File directory = chooser.getCurrentDirectory();
setWaitCursor();
try
{
if (this.prefs != null)
{
try
{
this.prefs.put("file_load_default_dir", directory.getCanonicalPath());
}
catch (java.io.IOException ex)
{
// we don't really care if we can't save the directory
// path in our preferences all that much.
}
}
String result = xmlclient.submitXML(this, file);
StringDialog resultDialog = new StringDialog(this,
ts.l("processXMLSubmission.results_title"), // "XML Submission Results"
result,
false, StandardDialog.ModalityType.DOCUMENT_MODAL);
resultDialog.showDialog();
}
finally
{
setNormalCursor();
}
}
/*
* actions on objects.
*
*
* These are the methods to use to do something to an object.
*/
/**
* <p>Opens a new {@link arlut.csd.ganymede.client.framePanel framePanel}
* window to allow the user to edit an object.</p>
*
* <p>Use this to edit objects, so gclient can keep track of the
* caches, tree nodes, and all the other dirty work. This should be
* the only place windowPanel.addWindow() is called for editing
* purposes.</p>
*
* @param invid id for the object to be edited in the new window.
*/
public void editObject(Invid invid)
{
editObject(invid, null);
}
/**
* <p>Opens a new {@link arlut.csd.ganymede.client.framePanel framePanel}
* window to allow the user to edit an object.</p>
*
* <p>Use this to edit objects, so gclient can keep track of the
* caches, tree nodes, and all the other dirty work. This should be
* the only place windowPanel.addWindow() is called for editing
* purposes.</p>
*
* @param invid id for the object to be edited in the new window.
* @param originalWindow The framePanel that we are replacing with
* the editing version. If null, we won't do any frame
* replacement.. we'll just create a new frame.
*/
public void editObject(Invid invid, framePanel originalWindow)
{
if (deleteHash.containsKey(invid))
{
// "{0} has already been deleted.\n\nCancel this transaction if you do not wish to delete this object, after all."
showErrorMessage(ts.l("editObject.already_deleted", getObjectDesignation(invid)));
return;
}
if (wp.isOpenForEdit(invid))
{
// "Object Already Being Edited"
// "You already have a window open to edit {0}."
showErrorMessage(ts.l("editObject.already_editing_subj"),
ts.l("editObject.already_editing_txt", getObjectDesignation(invid)));
return;
}
ObjectHandle handle = getObjectHandle(invid);
if (handle != null && handle.isInactive())
{
Hashtable dialogResults = null;
// "Edit or Reactivate?"
// "Warning, {0} is currently inactivated. If you are seeking
// to reactivate this object, it is recommended that you use
// the server's reactivation wizard rather than manually editing it.
//
// Can I go ahead and shift you over to the server's
// reactivation wizard?"
// "Yes, Reactivate"
// "No, I want to edit it!"
DialogRsrc rsrc = new DialogRsrc(this,
ts.l("editObject.reactivate_subj"),
ts.l("editObject.reactivate_txt", getObjectDesignation(invid)),
ts.l("editObject.reactivate_yes"),
ts.l("editObject.reactivate_no"),
"question.gif", null);
StringDialog verifyDialog = new StringDialog(rsrc, StandardDialog.ModalityType.DOCUMENT_MODAL);
dialogResults = verifyDialog.showDialog();
if (dialogResults != null)
{
reactivateObject(invid);
return;
}
}
try
{
ReturnVal rv = handleReturnVal(session.edit_db_object(invid));
db_object o = (db_object) rv.getObject();
if (o == null)
{
// handleReturnVal threw up a dialog for us if needed
return;
}
wp.addWindow(invid, o, true, originalWindow);
changedSet.add(invid);
// we don't need to do a full refresh of it, since we've just
// checked it out..
setIconForNode(invid);
tree.refresh();
}
catch(Exception rx)
{
// "Could not edit object"
processExceptionRethrow(rx, ts.l("editObject.exception_txt"));
}
}
/**
* Clones an object based on origInvid on the server and opens a new
* client {@link arlut.csd.ganymede.client.framePanel framePanel}
* window to allow the user to edit the new object.
*
* @param origInvid ID of object to be cloned
*/
public void cloneObject(Invid origInvid)
{
Invid invid = null;
db_object obj = null;
/* -- */
if (deleteHash.containsKey(origInvid))
{
// "Can't Clone a Deleted Object"
// "{0} has already been deleted.\n\nCancel this transaction if you do not wish to delete this object after all."
showErrorMessage(ts.l("cloneObject.deleted_subj"),
ts.l("cloneObject.deleted_txt", getObjectDesignation(origInvid)));
return;
}
// if the admin is a member of more than one owner group, ask what
// owner groups they want new objects to be placed in
if (!defaultOwnerChosen)
{
if (!chooseDefaultOwner(false))
{
// They manually closed the default object dialog chooser, so
// we won't proceed with the object cloning.
return;
}
}
setWaitCursor();
try
{
final Invid local_origInvid = origInvid;
obj = null;
ReturnVal rv = (ReturnVal) FoxtrotAdapter.post(new foxtrot.Task()
{
public Object run() throws Exception
{
return session.clone_db_object(local_origInvid);
}
});
rv = handleReturnVal(rv);
obj = (db_object) rv.getObject();
// we'll depend on handleReturnVal() above showing the user a rejection
// dialog if the object create was rejected
if (obj == null)
{
return;
}
try
{
invid = obj.getInvid();
}
catch (Exception rx)
{
processExceptionRethrow(rx);
}
// "New Object"
ObjectHandle handle = new ObjectHandle(ts.l("global.new_object"), invid, false, false, false, true);
wp.addWindow(invid, obj, true, true, null);
Short typeShort = Short.valueOf(invid.getType());
if (cachedLists.containsList(typeShort))
{
objectList list = cachedLists.getList(typeShort);
list.addObjectHandle(handle);
}
// If the base node is open, deal with the node.
BaseNode baseN = null;
if (shortToBaseNodeHash.containsKey(typeShort))
{
baseN = (BaseNode)shortToBaseNodeHash.get(typeShort);
if (baseN.isLoaded())
{
InvidNode objNode = new InvidNode(baseN,
handle.getLabel(),
invid,
null, false,
OPEN_FIELD_CREATE,
CLOSED_FIELD_CREATE,
baseN.canInactivate() ? objectInactivatePM : objectRemovePM,
handle);
createHash.put(invid, new CacheInfo(typeShort, handle.getLabel(), null, handle));
invidNodeHash.put(invid, objNode);
setIconForNode(invid);
tree.insertNode(objNode, true); // the true means the tree will refresh
}
else
{
// this hash is used when creating the node for the object
// in the tree. This way, if a new object is created
// before the base node is expanded, the new object will
// have the correct icon.
createdObjectsWithoutNodes.put(invid, baseN);
}
}
somethingChanged();
}
finally
{
setNormalCursor();
}
}
/**
* Creates a new object on the server and opens a new
* client {@link arlut.csd.ganymede.client.framePanel framePanel}
* window to allow the user to edit the new object.
*
* @param type Type of object to be created
*/
public db_object createObject(short type)
{
Invid invid = null;
db_object obj = null;
/* -- */
// if the admin is a member of more than one owner group, ask what
// owner groups they want new objects to be placed in
if (!defaultOwnerChosen)
{
if (!chooseDefaultOwner(false))
{
// They manually closed the default object dialog chooser, so
// we won't proceed with object creation.
return null;
}
}
setWaitCursor();
try
{
try
{
final short local_type = type;
ReturnVal rv;
/*
Use foxtrot to keep the GUI refreshing while we're
waiting for the server to create the object for us.
*/
rv = (ReturnVal) FoxtrotAdapter.post(new foxtrot.Task()
{
public Object run() throws Exception
{
return session.create_db_object(local_type);
}
});
rv = handleReturnVal(rv);
obj = (db_object) rv.getObject();
}
catch (Exception rx)
{
// "Exception encountered creating new object"
processExceptionRethrow(rx, ts.l("createObject.exception_txt"));
}
// we'll depend on handleReturnVal() above showing the user a rejection
// dialog if the object create was rejected
if (obj == null)
{
return null;
}
try
{
invid = obj.getInvid();
}
catch (Exception rx)
{
processExceptionRethrow(rx);
}
// "New Object"
ObjectHandle handle = new ObjectHandle(ts.l("global.new_object"), invid, false, false, false, true);
wp.addWindow(invid, obj, true, true, null);
Short typeShort = Short.valueOf(type);
if (cachedLists.containsList(typeShort))
{
objectList list = cachedLists.getList(typeShort);
list.addObjectHandle(handle);
}
// If the base node is open, deal with the node.
BaseNode baseN = null;
if (shortToBaseNodeHash.containsKey(typeShort))
{
baseN = (BaseNode)shortToBaseNodeHash.get(typeShort);
if (baseN.isLoaded())
{
InvidNode objNode = new InvidNode(baseN,
handle.getLabel(),
invid,
null, false,
OPEN_FIELD_CREATE,
CLOSED_FIELD_CREATE,
baseN.canInactivate() ? objectInactivatePM : objectRemovePM,
handle);
createHash.put(invid, new CacheInfo(typeShort, handle.getLabel(), null, handle));
invidNodeHash.put(invid, objNode);
setIconForNode(invid);
tree.insertNode(objNode, true); // the true means the tree will refresh
}
else
{
// this hash is used when creating the node for the object
// in the tree. This way, if a new object is created
// before the base node is expanded, the new object will
// have the correct icon.
createdObjectsWithoutNodes.put(invid, baseN);
}
}
somethingChanged();
}
finally
{
setNormalCursor();
}
return obj;
}
/**
* Opens a new {@link arlut.csd.ganymede.client.framePanel framePanel}
* window to view the object corresponding to the given invid.
*/
public void viewObject(Invid invid)
{
if (deleteHash.containsKey(invid))
{
// "{0} has already been deleted.\n\nCancel this transaction if you do not wish to delete this object after all."
showErrorMessage(ts.l("viewObject.deleted_txt", getObjectDesignation(invid)));
return;
}
try
{
ReturnVal rv = handleReturnVal(session.view_db_object(invid));
db_object object = (db_object) rv.getObject();
// we'll assume handleReturnVal() will display any rejection
// dialogs from the server
if (object == null)
{
return;
}
wp.addWindow(invid, object, false);
}
catch (Exception rx)
{
// "Could not view object"
processExceptionRethrow(rx, ts.l("viewObject.exception_txt"));
}
}
/**
* <p>Marks an object on the server as deleted. The object will not
* actually be removed from the database until the transaction is
* committed.</p>
*
* <p>This method does a fair amount of internal bookkeeping to manage
* the client's tree display, status caching, etc.</p>
*
* @param invid The object invid identifier to be deleted
* @param showDialog If true, we'll show a dialog box asking the user
* if they are sure they want to delete the object in question.
*/
public void deleteObject(Invid invid, boolean showDialog)
{
ReturnVal retVal;
boolean ok = false;
String label = null;
/* -- */
if (deleteHash.containsKey(invid))
{
// "Object Already Deleted"
// "{0} has already been marked as deleted.\n\nYou can hit the
// commit button to permanently get rid of this object, or you
// can hit the cancel button to undo everything."
showErrorMessage(ts.l("deleteObject.deleted_subj"),
ts.l("deleteObject.deleted_txt", getObjectDesignation(invid)));
return;
}
// we can delete objects if they are newly created.. the server
// has support for discarding newly created objects, in fact. If
// the user attempted to close a created object window, the
// framePanel.vetoableChange() method will have set
// closingApproved on the editing frame, which will cause the
// wp.isApprovedForClosing(invid) check here to be true, and we'll
// skip this question.
if (wp.isOpenForEdit(invid) && !wp.isApprovedForClosing(invid))
{
// "Object being edited"
// "You are currently editing {0}. I can''t delete this
// object while you are actively editing it.\n\nYou must
// commit or cancel this transaction before this object can be
// deleted."
showErrorMessage(ts.l("deleteObject.edited_subj"),
ts.l("deleteObject.edited_txt", getObjectDesignation(invid)));
return;
}
if (showDialog)
{
// "Verify Object Deletion"
// "Are you sure you want to delete {0}?"
StringDialog d = new StringDialog(this,
ts.l("deleteObject.verify_subj"),
ts.l("deleteObject.verify_txt", getObjectDesignation(invid)),
StringDialog.getDefaultOk(),
StringDialog.getDefaultCancel(),
getQuestionImage(), StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = d.showDialog();
if (result == null)
{
// "Canceled!"
setStatus(ts.l("deleteObject.canceled"));
return;
}
}
setWaitCursor();
try
{
Short id = Short.valueOf(invid.getType());
if (debug)
{
System.err.println("Deleting invid= " + invid);
}
// Delete the object
retVal = handleReturnVal(session.remove_db_object(invid));
if (ReturnVal.didSucceed(retVal))
{
// InvidNode node = (InvidNode)invidNodeHash.get(invid);
// Check out the deleteHash. If this one is already on there,
// then I don't know what to do. If it isn't, then add a new
// cache info. I guess maybe update the name or something,
// if it is on there.
CacheInfo info = null;
label = session.viewObjectLabel(invid);
// Take this object out of the cachedLists, if it is in there
if (cachedLists.containsList(id))
{
if (debug)
{
System.err.println("This base has been hashed. Removing: " + label);
}
objectList list = cachedLists.getList(id);
ObjectHandle h = list.getObjectHandle(invid);
list.removeInvid(invid);
info = new CacheInfo(id, label, null, h);
}
else
{
info = new CacheInfo(id, label, null);
}
if (deleteHash.containsKey(invid))
{
if (debug)
{
System.err.println("already deleted, nothing to change, right?");
}
}
else
{
deleteHash.put(invid, info);
}
if (invidNodeHash.containsKey(invid))
{
setIconForNode(invid);
tree.refresh();
}
// "{0} will be deleted when commit is clicked."
setStatus(ts.l("deleteObject.ready_to_delete", getObjectDesignation(invid)));
somethingChanged();
}
else
{
// "Delete Failed"
setStatus(ts.l("deleteObject.failed"));
}
}
catch (Exception rx)
{
// "Error attempting to delete {0}. Object not deleted."
processExceptionRethrow(rx, ts.l("deleteObject.exception_txt", getObjectDesignation(invid)));
}
finally
{
setNormalCursor();
}
return;
}
/**
* <p>Marks an object on the server as inactivated. The object will
* not actually be removed from the database until the transaction
* is committed. Note that the inactivation request will typically
* cause a dialog to come back from the server requesting the user
* fill in parameters describing how the object is to be
* inactivated.</p>
*
* <p>This method does a fair amount of internal bookkeeping to
* manage the client's tree display, status caching, etc.</p>
*/
public void inactivateObject(Invid invid)
{
boolean ok = false;
ReturnVal retVal;
/* -- */
if (deleteHash.containsKey(invid))
{
// "Object Already Deleted"
// "{0} has already been deleted.\n\nCancel this transaction if you do not wish to delete this object after all."
showErrorMessage(ts.l("inactivateObject.deleted_subj"),
ts.l("inactivateObject.deleted_txt", getObjectDesignation(invid)));
return;
}
if (wp.isOpenForEdit(invid))
{
// "Object Being Edited"
// "I can''t inactivate this object while you have a window open to edit {0}."
showErrorMessage(ts.l("inactivateObject.edited_subj"),
ts.l("inactivateObject.edited_txt", getObjectDesignation(invid)));
return;
}
String designation = getObjectDesignation(invid);
// "Inactivating {0}."
setStatus(ts.l("inactivateObject.inactivating", designation), 2);
setWaitCursor();
try
{
retVal = session.inactivate_db_object(invid);
if (retVal != null)
{
retVal = handleReturnVal(retVal);
if (retVal == null)
{
ok = true;
}
else
{
ok = retVal.didSucceed();
}
}
else
{
ok = true;
}
if (ok)
{
// remember that we changed this object for the refreshChangedObjectHandles
changedSet.add(invid);
// refresh it now
refreshChangedObject(invid);
// and update the tree
tree.refresh();
// "{0} inactivated."
setStatus(ts.l("inactivateObject.success", designation), 2);
somethingChanged();
}
else
{
// "Could not inactivate {0}."
setStatus(ts.l("inactivateObject.failure", designation));
}
}
catch (Exception rx)
{
// "Could not inactivate {0}."
processExceptionRethrow(rx, ts.l("inactivateObject.failure", designation));
}
finally
{
setNormalCursor();
}
}
/**
* <p>Reactivates an object that was previously inactivated. The
* object's status will not actually be changed in the database
* until the transaction is committed. Note that the reactivation
* request will typically cause a dialog to come back from the
* server requesting the user fill in parameters describing how the
* object is to be reactivated.</p>
*
* <p>Typically reactivating an object involves clearing the removal
* date from the removal date panel.</p>
*/
public boolean reactivateObject(Invid invid)
{
ReturnVal retVal;
boolean ok = false;
/* -- */
try
{
setWaitCursor();
retVal = session.reactivate_db_object(invid);
if (retVal == null)
{
// It worked
ok = true;
}
else
{
retVal = handleReturnVal(retVal);
if (retVal == null)
{
ok = true;
}
else
{
ok = retVal.didSucceed();
}
}
}
catch (Exception rx)
{
// "Could not reactivate {0}."
processExceptionRethrow(rx, ts.l("reactivateObject.failure", getObjectDesignation(invid)));
}
if (ok)
{
somethingChanged();
// "{0} reactivated."
setStatus(ts.l("reactivateObject.success", getObjectDesignation(invid)), 2);
// remember that this invid has been edited, and will need
// to be refreshed on commit
changedSet.add(invid);
refreshChangedObject(invid);
tree.refresh();
setNormalCursor();
}
return ok;
}
/**
* Show the create object dialog, let the user choose
* to create or not create an object.
*/
void createObjectDialog()
{
// The dialog is modal, and will set itself visible when created.
// If we have already created it, we'll just pack it and make it
// visible
if (createDialog == null)
{
createDialog = new createObjectDialog(this);
}
else
{
createDialog.pack(); // force it to re-center itself.
createDialog.setVisible(true);
}
}
/**
* <p>Opens a dialog to let the user choose an object for editing, and
* if cancel is not chosen, the object is opened for editing.</p>
*
* <p>If an object node is selected in the client's tree, the dialog will
* be pre-loaded with the type and name of the selected node.</p>
*/
void editObjectDialog()
{
openObjectDialog dialog = new openObjectDialog(this);
// "Open object for editing"
dialog.setText(ts.l("editObjectDialog.dialog_txt"));
dialog.setIcon(new ImageIcon(pencil));
dialog.setReturnEditableOnly(true);
Invid invid = dialog.chooseInvid();
if (invid == null)
{
if (debug)
{
System.err.println("Canceled");
}
}
else
{
editObject(invid, null);
}
}
/**
* <p>Opens a dialog to let the user choose an object for viewing,
* and if cancel is not chosen, the object is opened for
* viewing.</p>
*
* <p>If an object node is selected in the client's tree, the dialog
* will be pre-loaded with the type and name of the selected
* node.</p>
*/
void viewObjectDialog()
{
openObjectDialog dialog = new openObjectDialog(this);
// "Open object for viewing"
dialog.setText(ts.l("viewObjectDialog.dialog_txt"));
dialog.setIcon(new ImageIcon(search));
dialog.setReturnEditableOnly(false);
Invid invid = dialog.chooseInvid();
if (invid == null)
{
if (debug)
{
System.err.println("Canceled");
}
}
else
{
viewObject(invid);
}
}
/**
* <p>Opens a dialog to let the user choose an object for
* inactivation, and if cancel is not chosen, the object is opened
* for inactivation.</p>
*
* <p>If an object node is selected in the client's tree, the dialog
* will be pre-loaded with the type and name of the selected
* node.</p>
*/
void inactivateObjectDialog()
{
openObjectDialog dialog = new openObjectDialog(this);
// "Choose object to be inactivated"
dialog.setText(ts.l("inactivateObjectDialog.dialog_txt"));
dialog.setIcon(null);
dialog.setReturnEditableOnly(true);
Invid invid = dialog.chooseInvid();
inactivateObject(invid);
}
/**
* <p>Opens a dialog to let the user choose an object for deletion,
* and if cancel is not chosen, the object is opened for
* deletion.</p>
*
* <p>If a node is selected in the client's tree, the dialog will be
* pre-loaded with the type and name of the selected object.</p>
*/
void deleteObjectDialog()
{
openObjectDialog dialog = new openObjectDialog(this);
// "Choose object to be deleted"
dialog.setText(ts.l("deleteObjectDialog.dialog_txt"));
dialog.setIcon(new ImageIcon(trash));
dialog.setReturnEditableOnly(true);
Invid invid = dialog.chooseInvid();
if (invid == null)
{
if (debug)
{
System.err.println("Canceled");
}
}
else
{
deleteObject(invid, true);
}
}
/**
* <p>Opens a dialog to let the user choose an object for cloning,
* and if cancel is not chosen, the object is opened for
* cloning.</p>
*
* <p>If a node is selected in the client's tree, the dialog will be
* pre-loaded with the type and name of the selected object.</p>
*/
void cloneObjectDialog()
{
openObjectDialog dialog = new openObjectDialog(this);
// "Choose object to be cloned"
dialog.setText(ts.l("cloneObjectDialog.dialog_txt"));
dialog.setIcon(new ImageIcon(cloneIcon));
dialog.setReturnEditableOnly(false);
Invid invid = dialog.chooseInvid();
if (invid == null)
{
if (debug)
{
System.err.println("Canceled");
}
}
else
{
cloneObject(invid);
}
}
/**
* <p>Creates and presents a dialog to let the user change their
* selected persona.</p>
*
* <p>gclient's personaListener reacts to events from the persona
* change dialog and will react appropriately as needed. This
* method doesn't actually do anything other than display the
* dialog.</p>
*
* <p>PersonaDialog is modal, however, so this method will block
* until the user makes a choice in the dialog box.</p>
*/
void changePersona(boolean requirePassword)
{
personaDialog = new PersonaDialog(client, requirePassword);
personaDialog.pack(); // force it to re-center itself.
personaDialog.setVisible(true); // block
}
/**
* Returns a reference to the most recently created persona dialog.
*/
PersonaDialog getPersonaDialog()
{
return personaDialog;
}
/**
* <p>Logs out from the client.</p>
*
* <p>This method does not do any checking, it just logs out.</p>
*/
void logout()
{
this.logout(true);
}
/**
* <p>Logs out from the client and optionally quits the client.</p>
*
* <p>This method does not do any checking, it just logs out.</p>
*/
void logout(boolean andQuit)
{
// glogin's logout method will call our cleanUp() method on the
// GUI thread.
sizer.saveSize(this);
_myglogin.logout(andQuit);
}
/**
* <p>Create a custom query filter.</p>
*
* <p>The filter is used to limit the output on a query, so that
* supergash can see the world through the eyes of a less-privileged
* persona. This seemed like a good idea at one point, not sure how
* valuable this really is anymore.</p>
*/
public void chooseFilter()
{
if (filterDialog == null)
{
filterDialog = new JFilterDialog(this);
}
else
{
filterDialog.setVisible(true);
}
}
/**
* This method is called by the {@link
* arlut.csd.ganymede.client.JFilterDialog JFilterDialog} class when
* the owner list filter is changed, to refresh the tree's display
* of all object lists loaded into the client so that only those
* objects matching the owner list filter are visible.
*/
public void updateAfterFilterChange()
{
clearCaches();
updateTreeAfterFilterChange(tree.getRoot());
tree.refresh();
// update all our object editing windows so that we can refresh
// the choice lists.. ? -- not sure why this logic is being done
// here, actually.. this dates back to revision 4596, Oct 30, 2001
wp.refreshObjectWindows(null, null);
}
/**
* This method updates all category and base nodes at or under
* the given node, and all category and base nodes that are nextSiblings
* to the given node.
*/
private void updateTreeAfterFilterChange(treeNode node)
{
if (node == null)
{
return;
}
if (debug)
{
System.err.println("updateAfterFilterChange examining: " + node);
}
if (node instanceof BaseNode)
{
while (node instanceof BaseNode)
{
if (node.getChild() != null)
{
if (debug)
{
System.err.println("Updating " + node);
}
try
{
refreshObjects((BaseNode) node, false);
}
catch (Exception ex)
{
// "Could not refresh object base {0}"
processExceptionRethrow(ex, ts.l("updateTreeAfterFilterChange.exception_txt", node.getText()));
}
}
node = node.getNextSibling();
}
}
if (node instanceof CatTreeNode)
{
updateTreeAfterFilterChange(node.getChild());
updateTreeAfterFilterChange(node.getNextSibling());
}
}
/**
* <p>Chooses the default owner group for a newly created object.</p>
*
* <p>This must be called before {@link
* arlut.csd.ganymede.rmi.Session#create_db_object(short)} is
* called.</p>
*/
public boolean chooseDefaultOwner(boolean forcePopup)
{
ReturnVal retVal = null;
if (ownerGroups == null)
{
try
{
ownerGroups = session.getOwnerGroups().getListHandles();
if (ownerGroups == null)
{
throw new NullPointerException();
}
}
catch (Exception rx)
{
// "Exception encountered attempting to load owner groups from the server"
processExceptionRethrow(rx, ts.l("chooseDefaultOwner.exception_txt"));
}
}
// we know that the supergash owner group, at least, should exist
// on the server, so if we see zero ownerGroups, we know the user
// just doesn't have the required permissions.
if (ownerGroups.size() == 0)
{
// "Permissions Error"
// "Your account doesn''t have permission to access any owner groups on the server.\n\nYou cannot create new objects with this account."
showErrorMessage(ts.l("chooseDefaultOwner.permissions_subj"),
ts.l("chooseDefaultOwner.permissions_txt"));
return false;
}
else if (!forcePopup && (ownerGroups.size() == 1))
{
// if forcePopup is false and there's only one group
// available, we'll go ahead and pick the default owner for
// objects created by this user ourselves.
defaultOwnerChosen = true;
Vector owners = new Vector();
for (int i = 0; i < ownerGroups.size(); i++)
{
owners.addElement(((listHandle)ownerGroups.elementAt(i)).getObject());
}
try
{
handleReturnVal(session.setDefaultOwner(owners));
}
catch (Exception rx)
{
// "Exception encountered attempting to set the default owner group for newly created objects."
processExceptionRethrow(rx, ts.l("chooseDefaultOwner.exception2_txt"));
}
return true;
}
defaultOwnerDialog = new JDefaultOwnerDialog(this, ownerGroups);
retVal = defaultOwnerDialog.chooseOwner();
handleReturnVal(retVal);
if ((retVal == null) || (retVal.didSucceed()))
{
defaultOwnerChosen = true;
}
else
{
// if the user voluntarily popped up the choose default owner
// dialog (i.e., by using the menu) and then forcibly closed
// the dialog window rather than clicking 'Ok', we'll get a
// negative return value from the chooseOwner() method, but we
// shouldn't take that as meaning that the defaultOwnerChosen
// flag should be set false, as it might previously have been
// appropriately set.
if (!forcePopup)
{
defaultOwnerChosen = false;
}
}
return defaultOwnerChosen;
}
/**
* True if a default owner has already been chosen.
*/
public boolean defaultOwnerChosen()
{
return defaultOwnerChosen;
}
/**
* <p>Check for changes in the database before logging out.</p>
*
* <p>This checks to see if anything has been changed. Basically,
* if edit panels are open and have been changed in any way, then
* somethingChanged will be true and the user will be warned. If
* edit panels are open but have not been changed, then it will
* return true(it is ok to proceed).</p>
*/
boolean OKToProceed()
{
// we're using the enabled status of the logoutMI as a flag to
// control whether we're in the middle of bringing up an internal
// frame. by gating on this, we can prevent the client from
// throwing an exception due to a mistimed logout/window close.
if (!logoutMI.isEnabled())
{
return false;
}
if (wizardActive > 0)
{
if (debug)
{
System.err.println("gclient: wizard is active, not ok to logout.");
}
return false;
}
if (getSomethingChanged())
{
// "Warning: Changes have been made."
// "You have made changes in objects on the server that have
// not been committed. If you log out now, those changes will
// be lost.\n\nAre you sure you want to log out and lose these
// changes?"
// "Yes, Discard Changes"
StringDialog dialog = new StringDialog(this,
ts.l("OKToProceed.changes_warning_subj"),
ts.l("OKToProceed.changes_warning_txt"),
ts.l("OKToProceed.yes"),
StringDialog.getDefaultCancel(), StandardDialog.ModalityType.DOCUMENT_MODAL);
// if showDialog is null, cancel was clicked So return will be
// false if cancel was clicked
return (dialog.showDialog() != null);
}
else
{
return true;
}
}
/**
* <p>Updates the note panels in the open windows.</p>
*
* <p>The note panel doesn't have a listener on the TextArea, so
* when a transaction is committed, this must be called on each
* notePanel in order to update the server.</p>
*
* <p>This basically does a field.setValue(notesArea.getValue()) on
* each notesPanel.</p>
*
* <p>THIS IS A PRETTY BIG HACK.</p>
*/
void updateNotePanels()
{
Vector windows = wp.getEditables();
for (int i = 0; i < windows.size(); i++)
{
if (debug)
{
System.err.println("Updating window number " + i);
}
framePanel fp = (framePanel)windows.elementAt(i);
if (fp == null)
{
if (debug)
{
System.err.println("null frame panel in updateNotesPanels");
}
}
else
{
notesPanel np = fp.getNotesPanel();
if (np == null)
{
if (debug)
{
System.err.println("null notes panel in frame panel");
}
}
else
{
if (debug)
{
System.err.println("Calling update notes.");
}
np.updateNotes();
}
}
}
}
/**
* <p>Commits the currently open transaction on the server. All
* changes made by the user since the last openNewTransaction() call
* will be integrated into the database on the Ganymede server.</p>
*
* <p>For various reasons, the server may reject the transaction as
* incomplete. Usually this will be a non-fatal error.. the user
* will see a dialog telling him what else needs to be filled out in
* order to commit the transaction. In this case,
* commitTransaction() will have had no effect and the user is free
* to try again.</p>
*
* <p>If the transaction is committed successfully, the relevant
* object nodes in the tree will be fixed up to reflect their state
* after the transaction is committed. commitTransaction() will
* close all open editing windows, and will call openNewTransaction()
* to prepare the server for further changes by the user.</p>
*/
public void commitTransaction(boolean promptForComment)
{
ReturnVal retVal = null;
boolean succeeded = false;
/* -- */
if (promptForComment || this.comment != null)
{
// "Comment:"
String fieldName = ts.l("commitTransaction.comment_dialog_comment_label");
// "Transaction Commit"
// "Commit"
DialogRsrc r = new DialogRsrc(this, ts.l("commitTransaction.comment_dialog_title"), "",
ts.l("init.commit_button"),
StringDialog.getDefaultCancel());
r.addMultiString(fieldName, this.comment);
StringDialog d = new StringDialog(r, StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = d.showDialog();
if (result == null)
{
// the user hit cancel
return;
}
else
{
this.comment = (String) result.get(fieldName);
if (this.comment != null && this.comment.trim().equals(""))
{
this.comment = null;
}
}
}
setWaitCursor();
try
{
// We need to check to see if any notes panels need to
// have their text flushed to the server..
updateNotePanels();
retVal = session.commitTransaction(false, this.comment);
if (retVal != null)
{
retVal = handleReturnVal(retVal);
}
succeeded = ReturnVal.didSucceed(retVal);
// if we succeed, we clean up. If we don't,
// retVal.doNormalProcessing can be false, in which case the
// serve aborted our transaction utterly. If
// retVal.doNormalProcessing is true, the user can do
// something to make the transaction able to complete
// successfully. In this case, handleReturnVal() will have
// displayed a dialog telling the user what needs to be done.
if (succeeded)
{
// "Transaction successfully commited."
setStatus(ts.l("commitTransaction.success"));
wp.closeEditables();
wp.refreshTableWindows();
openNewTransaction();
//
// This fixes all the icons in the tree, and closes any
// view windows on objects that we have deleted
//
refreshTreeAfterCommit();
if (debug)
{
System.err.println("Done committing");
}
}
else if (!retVal.doNormalProcessing)
{
// "Transaction aborted on the server."
setStatus(ts.l("commitTransaction.failure"));
// This is just like a cancel. Something went wrong, and
// the server canceled our transaction. We don't need to
// call cancelTransaction ourselves.
// "Commit Failure"
// "The server experienced a failure committing this transaction, and was forced to abort the transaction entirely."
showErrorMessage(ts.l("commitTransaction.error_subj"),
ts.l("commitTransaction.error_txt"));
}
}
catch (Exception ex)
{
// "An exception was caught while attempting to commit a transaction."
processExceptionRethrow(ex, ts.l("commitTransaction.exception_txt"));
}
finally
{
setNormalCursor();
if (!succeeded && (retVal == null || !retVal.doNormalProcessing))
{
wp.closeEditables();
cleanUpAfterCancel();
openNewTransaction();
}
}
}
/**
* Cancels the current transaction. Any changes made by the user since
* the last openNewTransaction() call will be forgotten as if they
* never happened. The client's tree display will be reverted to the
* state it was when the transaction was started, and all open windows
* will be closed.
*/
public synchronized void cancelTransaction()
{
ReturnVal retVal;
/* -- */
// close all the client windows.. this causes the windows to cancel
// their loading activity
wp.closeAll(true);
try
{
retVal = session.abortTransaction();
if (retVal != null)
{
retVal = handleReturnVal(retVal);
}
if (retVal == null || retVal.didSucceed())
{
// "Transaction canceled."
setStatus(ts.l("cancelTransaction.canceled"), 3);
if (debug)
{
System.err.println("Cancel succeeded");
}
}
else
{
// "Error on server, transaction cancel failed."
setStatus(ts.l("cancelTransaction.error_canceling"));
if (debug)
{
System.err.println("Everytime I think I'm out, they pull me back in! " +
"Something went wrong with the cancel.");
}
return;
}
}
catch (Exception rx)
{
// "An exception was caught while attempting to cancel a transaction."
processExceptionRethrow(rx, ts.l("cancelTransaction.exception_txt"));
}
cleanUpAfterCancel();
openNewTransaction();
}
/**
* <p>Cleans up the tree and gclient's caches.</p>
*
* <p>This method is precisely analagous in function to {@link
* arlut.csd.ganymede.client.gclient#refreshTreeAfterCommit()
* refreshTreeAfterCommit()}, except for use after a cancel, when
* nodes marked as deleted are not removed from the tree, and nodes
* marked as created are not kept.</p>
*/
private synchronized void cleanUpAfterCancel()
{
ObjectHandle handle;
Invid invid;
InvidNode node;
objectList list;
CacheInfo info;
/* -- */
synchronized (deleteHash)
{
Iterator<Invid> it = deleteHash.keySet().iterator();
while (it.hasNext())
{
invid = it.next();
info = deleteHash.get(invid);
list = cachedLists.getList(info.getBaseID());
if (list != null)
{
if (createHash.containsKey(invid))
{
if (debug)
{
System.err.println("Can't fool me: you just created this object!");
}
}
else
{
if (debug)
{
System.err.println("This one is hashed, sticking it back in.");
}
handle = info.getOriginalObjectHandle();
if (handle != null)
{
list.addObjectHandle(handle);
node = invidNodeHash.get(invid);
if (node != null)
{
node.setHandle(handle);
}
}
}
}
it.remove();
setIconForNode(invid);
}
}
invid = null;
node = null;
list = null;
info = null;
// Next up is created list: remove all the added stuff.
synchronized (createHash)
{
Iterator<Invid> it = createHash.keySet().iterator();
while (it.hasNext())
{
invid = it.next();
info = createHash.get(invid);
list = cachedLists.getList(info.getBaseID());
if (list != null)
{
if (debug)
{
System.err.println("This one is hashed, taking a created object out.");
}
list.removeInvid(invid);
}
it.remove();
node = invidNodeHash.get(invid);
if (node != null)
{
tree.deleteNode(node, false);
invidNodeHash.remove(invid);
}
}
}
createdObjectsWithoutNodes.clear();
// Now go through changed list and revert any names that may be needed
Vector<Invid> changedInvids = new Vector<Invid>(changedSet);
changedSet.clear();
refreshChangedObjectHandles(changedInvids, true);
if (debug && createHash.isEmpty() && deleteHash.isEmpty())
{
System.err.println("Woo-woo the hashes are all empty");
}
tree.refresh(); // To catch all the icon changing.
}
/**
* Initializes a new transaction on the server
*/
private void openNewTransaction()
{
this.comment = null;
try
{
// "Ganymede GUI Client"
ReturnVal rv = session.openTransaction(ts.l("openNewTransaction.client_name"));
handleReturnVal(rv);
if ((rv != null) && (!rv.didSucceed()))
{
// "Could not open a new transaction on the Ganymede server."
showErrorMessage(ts.l("openNewTransaction.error_txt"));
}
tree.refresh();
}
catch (Exception rx)
{
// "Could not open a new transaction on the Ganymede server."
processExceptionRethrow(rx, ts.l("openNewTransaction.error_txt"));
}
setSomethingChanged(false);
cancel.setEnabled(false);
commit.setEnabled(false);
tree.requestFocus();
clearCaches();
}
/**
* toggles the toolbar on and off
*/
void toggleToolBar()
{
if (toolToggle == true)
{
if (((BasicToolBarUI)toolBar.getUI()).isFloating())
{
((BasicToolBarUI)toolBar.getUI()).setFloating(false, new Point(0,0));
}
toolBar.setVisible(false);
toolToggle = false;
}
else if (toolToggle == false)
{
toolBar.setVisible(true);
toolToggle = true;
}
getContentPane().validate();
}
// ActionListener Methods
/**
* Handles button and menu picks. Includes logic for threading
* out queries and message panels to avoid locking the Java GUI
* thread.
*/
public void actionPerformed(java.awt.event.ActionEvent event)
{
Object source = event.getSource();
String command = event.getActionCommand();
/* -- */
if (debug)
{
System.err.println("Action: " + command);
}
if (source == cancel)
{
if (debug)
{
System.err.println("cancel button clicked");
}
cancelTransaction();
}
else if (source == commit)
{
if (debug)
{
System.err.println("commit button clicked");
}
commitTransaction(promptForComments ^ altKeyDown);
}
else if ((source == menubarQueryMI) || (command.equals(query_action)))
{
postQuery(null);
}
else if (source == clearTreeMI)
{
clearTree();
}
else if (source == submitXMLMI)
{
processXMLSubmission();
}
else if (source == promptForCommentsMI)
{
promptForComments = promptForCommentsMI.getState();
if (this.prefs != null)
{
prefs.putBoolean("prompt_for_comments", promptForComments);
}
refreshCommitLabel();
}
else if (source == hideNonEditablesMI)
{
hideNonEditables = hideNonEditablesMI.getState();
if (this.prefs != null)
{
prefs.putBoolean("hide_non_editable_objects", hideNonEditables);
}
clearTree();
}
else if (source == logoutMI)
{
if (OKToProceed())
{
logout();
}
}
else if (source == toggleToolBarMI)
{
toggleToolBar();
}
else if (command.equals(persona_action))
{
changePersona(false);
}
else if (command.equals(create_action))
{
createObjectDialog();
}
else if (command.equals(edit_action))
{
editObjectDialog();
}
else if (command.equals(view_action))
{
viewObjectDialog();
}
else if (command.equals(delete_action))
{
deleteObjectDialog();
}
else if (command.equals(clone_action))
{
cloneObjectDialog();
}
else if (command.equals(inactivate_action))
{
inactivateObjectDialog();
}
else if (command.equals(access_invid_action))
{
openAnInvid();
}
else if (command.equals(owner_filter_action))
{
chooseFilter();
}
else if (command.equals(default_owner_action))
{
chooseDefaultOwner(true);
}
else if (command.equals(help_action))
{
// XXX at the present time, nothing triggers this, and the
// window the user would get wouldn't be helpful if something
// did.
showHelpWindow();
}
else if (command.equals(java_version_action))
{
Thread thread = new Thread(new Runnable() {
public void run() {
showJavaVersion();
}});
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
else if (command.equals(about_action))
{
Thread thread = new Thread(new Runnable() {
public void run() {
showAboutMessage();
}});
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
else if (command.equals(motd_action))
{
Thread thread = new Thread(new Runnable() {
public void run() {
showMOTD();
}});
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
else
{
// "Unknown action event generated in Ganymede client.. client error?"
setStatus(ts.l("actionPerformed.unknown"));
}
}
/**
* Pop up the query box
*/
void postQuery(BaseDump base)
{
if (my_querybox == null)
{
// "Query Panel"
my_querybox = new querybox(base, this, this, ts.l("postQuery.query_title"));
}
else if (my_querybox.isVisible())
{
return;
}
if (base != null)
{
my_querybox.selectBase(base);
}
my_querybox.setVisible(true);
}
/**
* <p>This is a debugging hook, to allow the user to enter an invid
* in string form for direct viewing.</p>
*
* <p>Since this is just for debugging, I haven't bothered to
* localize this method.</p>
*/
void openAnInvid()
{
DialogRsrc r = new DialogRsrc(this,
"Open an invid",
"This will open an invid by number. This is for " +
"debugging purposes only. Invid's have the format " +
"number:number, like 21:423");
r.addString("Invid number:");
StringDialog d = new StringDialog(r, StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = d.showDialog();
/* -- */
if (result == null)
{
if (debug)
{
System.err.println("Ok, nevermind.");
}
return;
}
String invidString = (String)result.get("Invid number:");
if (invidString == null)
{
if (debug)
{
System.err.println("Ok, nevermind.");
}
return;
}
viewObject(Invid.createInvid(invidString));
}
public void addTableWindow(Session session, Query query, DumpResult buffer)
{
wp.addTableWindow(session, query, buffer);
}
protected void processWindowEvent(WindowEvent e)
{
if (e.getID() == WindowEvent.WINDOW_CLOSING)
{
if (debug)
{
System.err.println("Window closing");
}
if (OKToProceed())
{
if (debug)
{
System.err.println("It's ok to log out.");
}
logout();
sizer.saveSize(this);
super.processWindowEvent(e);
}
else if (debug)
{
System.err.println("No log out!");
}
return;
}
if (e.getID() == WindowEvent.WINDOW_ACTIVATED ||
e.getID() == WindowEvent.WINDOW_DEACTIVATED)
{
altKeyDown = false;
refreshCommitLabel();
}
super.processWindowEvent(e);
}
// Callbacks
/**
* This method comprises the JsetValueCallback interface, and is how
* some data-carrying components notify us when something changes.
*
* @see arlut.csd.JDataComponent.JsetValueCallback
* @see arlut.csd.JDataComponent.JValueObject
*/
public boolean setValuePerformed(JValueObject o)
{
if (o instanceof JErrorValueObject)
{
showErrorMessage((String)o.getValue());
}
else if (o instanceof JSetValueObject && o.getSource() == LandFMenu)
{
sizer.saveLookAndFeel();
SwingUtilities.updateComponentTreeUI(_myglogin);
if (about != null)
{
SwingUtilities.updateComponentTreeUI(about);
}
if (motd != null)
{
SwingUtilities.updateComponentTreeUI(motd);
}
if (java_ver_dialog != null)
{
SwingUtilities.updateComponentTreeUI(java_ver_dialog);
}
if (filterDialog != null)
{
SwingUtilities.updateComponentTreeUI(filterDialog);
}
if (createDialog != null)
{
SwingUtilities.updateComponentTreeUI(createDialog);
}
if (defaultOwnerDialog != null)
{
SwingUtilities.updateComponentTreeUI(defaultOwnerDialog);
}
if (my_querybox != null)
{
SwingUtilities.updateComponentTreeUI(my_querybox);
}
if (personaDialog != null)
{
SwingUtilities.updateComponentTreeUI(personaDialog);
}
}
else
{
if (debug)
{
System.err.println("I don't know what to do with this setValuePerformed: " + o);
}
return false;
}
return true;
}
// treeCallback methods
/**
* <p>Called when a node is expanded, to allow the user of the tree
* to dynamically load the information at that time.</p>
*
* @param node The node opened in the tree.
*/
public void treeNodeExpanded(treeNode node)
{
if (node instanceof BaseNode && !((BaseNode) node).isLoaded())
{
// "Loading objects for base {0}."
setStatus(ts.l("treeNodeExpanded.loading", node.getText()), 1);
setWaitCursor();
try
{
refreshObjects((BaseNode)node, true);
}
catch (Exception ex)
{
// "Exception encountered loading objects for base {0}."
processExceptionRethrow(ex, ts.l("treeNodeExpanded.exception_txt", node.getText()));
}
// "Done loading objects for base {0}."
setStatus(ts.l("treeNodeExpanded.loaded", node.getText()), 1);
((BaseNode) node).markLoaded();
setNormalCursor();
}
}
/**
* <p>Called when a node is closed.</p>
*/
public void treeNodeContracted(treeNode node)
{
}
/**
* <p>Called when an item in the tree is selected</p>
*
* @param node The node selected in the tree.
*/
public void treeNodeSelected(treeNode node)
{
selectedNode = node;
validate();
}
public void treeNodeDoubleClicked(treeNode node)
{
if (node instanceof InvidNode)
{
viewObject(((InvidNode)node).getInvid());
}
}
/**
* <p>Called when an item in the tree is unselected</p>
*
* @param node The node selected in the tree.
* @param otherNode If true, this node is being unselected by the selection
* of another node.
*/
public void treeNodeUnSelected(treeNode node, boolean otherNode)
{
selectedNode = null;
}
/**
* <p>Called when a popup menu item is selected on a treeNode</p>
*
* @param node The node selected in the tree.
*/
public void treeNodeMenuPerformed(treeNode node,
java.awt.event.ActionEvent event)
{
boolean treeMenuDebug = false;
if (event.getActionCommand().equals(create_pop_action))
{
if (treeMenuDebug)
{
System.err.println("createMI");
}
if (node instanceof BaseNode)
{
BaseNode baseN = (BaseNode)node;
short id = baseN.getTypeID().shortValue();
createObject(id);
}
else
{
System.err.println("not a base node, can't create");
}
}
else if ((event.getActionCommand().equals(report_edit_pop_action)) ||
(event.getActionCommand().equals(report_pop_action)))
{
if (treeMenuDebug)
{
System.err.println("viewMI/viewAllMI");
}
if (node instanceof BaseNode)
{
BaseNode baseN = (BaseNode)node;
Query listQuery = null;
if (event.getActionCommand().equals(report_edit_pop_action))
{
listQuery = baseN.getEditableQuery();
}
else
{
listQuery = baseN.getAllQuery();
}
// we still want to filter
listQuery.setFiltered(true);
// inner classes can only refer to final method variables,
// so we'll make some final references to keep our inner
// class happy.
final Query q = listQuery;
final gclient thisGclient = this;
final String tempText = node.getText();
Thread t = new Thread(new Runnable() {
public void run() {
thisGclient.wp.addWaitWindow(this);
DumpResult buffer = null;
try
{
try
{
buffer = thisGclient.getSession().dump(q);
}
catch (Exception ex)
{
processExceptionRethrow(ex);
}
catch (Error ex)
{
// "Could not complete query. We may have run out of memory in the client.\n\n{0}"
new JErrorDialog(thisGclient,
ts.l("treeNodeMenuPerformed.error", ex.getMessage()), StandardDialog.ModalityType.DOCUMENT_MODAL);
throw ex;
}
if (buffer == null)
{
// "No results found from query operation on base {0}."
setStatus(ts.l("treeNodeMenuPerformed.empty_results", tempText),2);
}
else
{
// "Results returned from server query on base {0} - building table widget."
setStatus(ts.l("treeNodeMenuPerformed.results", tempText), 1);
thisGclient.wp.addTableWindow(thisGclient.getSession(), q, buffer);
}
}
finally
{
thisGclient.wp.removeWaitWindow(this);
}
}});
t.setPriority(Thread.NORM_PRIORITY);
t.start();
// "Sending query on base {0} to server."
setStatus(ts.l("treeNodeMenuPerformed.sending", node.getText()), 0);
}
else
{
System.err.println("viewMI from a node other than a BaseNode");
}
}
else if (event.getActionCommand().equals(query_pop_action))
{
if (treeMenuDebug)
{
System.err.println("queryMI");
}
if (node instanceof BaseNode)
{
setWaitCursor();
BaseDump base = (BaseDump)((BaseNode) node).getBase();
setNormalCursor();
postQuery(base);
}
}
else if (event.getActionCommand().equals(show_pop_action) && (node instanceof BaseNode))
{
BaseNode bn = (BaseNode) node;
/* -- */
if (treeMenuDebug)
{
System.err.println("show all objects");
}
setWaitCursor();
try
{
bn.showAll(true);
node.setMenu(bn.canCreate() ? pMenuAllCreatable : pMenuAll);
if (bn.isOpen())
{
try
{
if (treeMenuDebug)
{
System.err.println("Refreshing objects");
}
refreshObjects(bn, true);
}
catch (Exception ex)
{
processExceptionRethrow(ex);
}
}
}
finally
{
setNormalCursor();
}
}
else if (event.getActionCommand().equals(hide_pop_action) && (node instanceof BaseNode))
{
BaseNode bn = (BaseNode) node;
/* -- */
bn.showAll(false);
bn.setMenu(bn.canCreate() ? pMenuEditableCreatable : pMenuEditable);
if (bn.isOpen())
{
// this makes the ratchet operation in refreshObjects() faster
// in the common case where there are many more editables than
// non-editables.
tree.removeChildren(bn, false);
tree.expandNode(bn, false);
try
{
refreshObjects(bn, true);
}
catch (Exception ex)
{
processExceptionRethrow(ex);
}
}
}
else if (event.getActionCommand().equals(view_pop_action))
{
if (node instanceof InvidNode)
{
InvidNode invidN = (InvidNode)node;
viewObject(invidN.getInvid());
}
}
else if (event.getActionCommand().equals(edit_pop_action))
{
if (treeMenuDebug)
{
System.err.println("objEditMI");
}
if (node instanceof InvidNode)
{
InvidNode invidN = (InvidNode)node;
editObject(invidN.getInvid(), null);
}
}
else if (event.getActionCommand().equals(clone_pop_action))
{
if (treeMenuDebug)
{
System.err.println("objCloneMI");
}
if (node instanceof InvidNode)
{
InvidNode invidN = (InvidNode)node;
cloneObject(invidN.getInvid());
}
}
else if (event.getActionCommand().equals(delete_pop_action))
{
// Need to change the icon on the tree to an X or something to show that it is deleted
if (treeMenuDebug)
{
System.err.println("Deleting object");
}
if (node instanceof InvidNode)
{
InvidNode invidN = (InvidNode)node;
Invid invid = invidN.getInvid();
if ((event.getModifiers() & java.awt.event.ActionEvent.SHIFT_MASK) != 0)
{
deleteObject(invid, false); // don't prompt for confirmation
}
else
{
deleteObject(invid, true);
}
}
}
else if (event.getActionCommand().equals(inactivate_pop_action))
{
if (treeMenuDebug)
{
System.err.println("objInactivateMI");
}
if (node instanceof InvidNode)
{
inactivateObject(((InvidNode)node).getInvid());
}
}
else if (event.getActionCommand().equals(reactivate_pop_action))
{
if (treeMenuDebug)
{
System.err.println("Reactivate item.");
}
if (node instanceof InvidNode)
{
reactivateObject(((InvidNode)node).getInvid());
}
}
else
{
System.err.println("Unknown MI chosen");
}
}
// Utilities
/**
* <p>Sorts a vector of listHandles in place.</p>
*
* @param v Vector to be sorted
* @return Vector of sorted listHandles(sorted by label)
*/
public Vector<listHandle> sortListHandleVector(Vector<listHandle> v)
{
(new VecQuickSort(v,
new Comparator() {
public int compare(Object a, Object b)
{
listHandle aF, bF;
aF = (listHandle) a;
bF = (listHandle) b;
int comp = 0;
comp = aF.toString().compareToIgnoreCase(bF.toString());
if (comp < 0)
{
return -1;
}
else if (comp > 0)
{
return 1;
}
else
{
return 0;
}
}
})).sort();
return v;
}
/**
* <p>Sort a vector of Strings in-place.</p>
*
* @return Vector of sorted Strings.
*/
public Vector<String> sortStringVector(Vector<String> v)
{
new VecQuickSort(v, null).sort();
return v;
}
/**
* <p>This method updates the commit button's label, with the text
* set according to whether the user has the 'Prompt for Comments on
* Commit' menu item checked and whether or not the Alt/Option key
* is held down.</p>
*/
public void refreshCommitLabel()
{
if (commit != null)
{
commit.setText(promptForComments ^ altKeyDown ? commitCommentButtonText : commitButtonText);
}
}
/**
* <p>This method does all the clean up required to let garbage
* collection tear everything completely down.</p>
*
* <p>This method must be called from the Java GUI thread.</p>
*/
public void cleanUp()
{
if (debug)
{
System.err.println("gclient.cleanUp()");
}
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(altKeyListener);
altKeyListener = null;
this.removeAll();
client = null;
session = null;
_myglogin = null;
dump = null;
currentPersonaString = null;
emptyBorder5 = null;
emptyBorder10 = null;
raisedBorder = null;
loweredBorder = null;
lineBorder = null;
statusBorder = null;
statusBorderRaised = null;
if (changedSet != null)
{
changedSet.clear();
changedSet = null;
}
if (deleteHash != null)
{
deleteHash.clear();
deleteHash = null;
}
if (createHash != null)
{
createHash.clear();
createHash = null;
}
if (createdObjectsWithoutNodes != null)
{
createdObjectsWithoutNodes.clear();
createdObjectsWithoutNodes = null;
}
if (shortToBaseNodeHash != null)
{
shortToBaseNodeHash.clear();
shortToBaseNodeHash = null;
}
if (invidNodeHash != null)
{
invidNodeHash.clear();
invidNodeHash = null;
}
if (cachedLists != null)
{
cachedLists.clearCaches();
cachedLists = null;
}
if (loader != null)
{
loader.cleanUp();
loader = null;
}
help = null;
motd = null;
about = null;
if (personae != null)
{
personae.setSize(0);
personae = null;
}
if (ownerGroups != null)
{
ownerGroups.setSize(0);
ownerGroups = null;
}
toolBar = null;
if (filterDialog != null)
{
filterDialog.dispose();
filterDialog = null;
}
if (personaDialog != null)
{
personaDialog.dispose();
personaDialog = null;
}
if (defaultOwnerDialog != null)
{
defaultOwnerDialog.dispose();
defaultOwnerDialog = null;
}
if (createDialog != null)
{
createDialog.dispose();
createDialog = null;
}
images = null;
commit = null;
cancel = null;
if (statusPanel != null)
{
statusPanel.removeAll();
statusPanel = null;
}
buildLabel = null;
tree = null;
selectedNode = null;
errorImage = null;
questionImage = null;
search = null;
queryIcon = null;
cloneIcon = null;
pencil = null;
personaIcon = null;
inactivateIcon = null;
treepencil = null;
trash = null;
treetrash = null;
creation = null;
treecreation = null;
newToolbarIcon = null;
ganymede_logo = null;
createDialogImage = null;
idleIcon = null;
buildIcon = null;
buildIcon2 = null;
wp.closeAll(true);
wp = null;
objectViewPM = null;
objectReactivatePM = null;
objectInactivatePM = null;
objectRemovePM = null;
pMenuAll = null;
pMenuEditable= null;
pMenuEditableCreatable = null;
pMenuAllCreatable = null;
menubar = null;
logoutMI = null;
clearTreeMI = null;
filterQueryMI = null;
defaultOwnerMI = null;
showHelpMI = null;
toggleToolBarMI = null;
hideNonEditablesMI = null;
changePersonaMI = null;
editObjectMI = null;
viewObjectMI = null;
createObjectMI = null;
deleteObjectMI = null;
inactivateObjectMI = null;
menubarQueryMI = null;
my_username = null;
if (actionMenu != null)
{
actionMenu.removeAll();
actionMenu = null;
}
if (windowMenu != null)
{
windowMenu.removeAll();
windowMenu = null;
}
if (fileMenu != null)
{
fileMenu.removeAll();
fileMenu = null;
}
if (helpMenu != null)
{
helpMenu.removeAll();
helpMenu = null;
}
if (PersonaMenu != null)
{
PersonaMenu.removeAll();
PersonaMenu = null;
}
if (LandFMenu != null)
{
LandFMenu.removeAll();
LandFMenu = null;
}
personaListener = null;
if (my_querybox != null)
{
my_querybox.dispose();
my_querybox = null;
}
if (statusThread != null)
{
try
{
statusThread.shutdown();
}
catch (NullPointerException ex)
{
}
statusThread = null;
}
if (securityThread != null)
{
try
{
securityThread.shutdown();
}
catch (NullPointerException ex)
{
}
securityThread = null;
}
}
}
/*------------------------------------------------------------------------------
class
AltKeyListener
------------------------------------------------------------------------------*/
/**
* KeyEventDispatcher class to relabel the Ganymede client's commit
* button when alt/option is pressed.
*/
class AltKeyListener implements KeyEventDispatcher {
public boolean dispatchKeyEvent(KeyEvent e)
{
if (e.getID() == KeyEvent.KEY_PRESSED && (e.getKeyCode() == KeyEvent.VK_ALT || e.getKeyCode() == KeyEvent.VK_META))
{
gclient.client.altKeyDown = true;
}
else if (e.getID() == KeyEvent.KEY_RELEASED && (e.getKeyCode() == KeyEvent.VK_ALT || e.getKeyCode() == KeyEvent.VK_META))
{
gclient.client.altKeyDown = false;
}
gclient.client.refreshCommitLabel();
return false;
}
}
/*------------------------------------------------------------------------------
class
PersonaListener
------------------------------------------------------------------------------*/
/**
* Listener class to handle interaction with the client's persona selection
* dialog.
*/
class PersonaListener implements ActionListener {
/**
* TranslationService object for handling string localization in the
* Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.PersonaListener");
Session session;
gclient
gc;
// ---
PersonaListener(Session session, gclient parent)
{
this.session = session;
this.gc = parent;
}
public void actionPerformed(ActionEvent event)
{
// Check to see if we need to commit the transaction first.
String newPersona = null;
if (event.getSource() instanceof JRadioButton)
{
newPersona = event.getActionCommand();
gc.getPersonaDialog().updatePassField(newPersona);
}
else if (event.getSource() instanceof JButton)
{
newPersona = gc.getPersonaDialog().getNewPersona();
if (!gc.getPersonaDialog().requirePassword && newPersona.equals(gc.currentPersonaString))
{
return;
}
// Deal with trying to change w/ uncommitted transactions
if (gc.getSomethingChanged() && !gc.getPersonaDialog().requirePassword)
{
// "Changing Personae"
// "Changing personae requires that the current transaction be closed out.\n\nWould you like to commit this transaction now?"
// "Yes, Commit My Changes"
// "No, Never Mind"
StringDialog d = new StringDialog(gc,
ts.l("actionPerformed.commit_dialog_subj"),
ts.l("actionPerformed.commit_dialog_txt"),
ts.l("actionPerformed.commit_dialog_yes"),
ts.l("actionPerformed.commit_dialog_no"), StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = d.showDialog();
if (result == null)
{
// "Persona Change Canceled"
gc.setStatus(ts.l("actionPerformed.canceled"));
return;
}
else
{
// "Committing Transaction"
gc.setStatus(ts.l("actionPerformed.committing"));
gc.commitTransaction(false);
}
}
// Now change the persona
String password = null;
// All admin level personae have a : in them. Only admin level
// personae need passwords, unless we are forcing a password
if (gc.getPersonaDialog().requirePassword || newPersona.indexOf(":") > 0)
{
password = gc.getPersonaDialog().getPasswordField();
}
if (!setPersona(newPersona, password))
{
if (gc.getPersonaDialog().requirePassword)
{
return;
// gc.showErrorMessage("Wrong password");
}
else
{
// "Persona Change Failed
// "Your attempt to change personae was
// unsuccessful.\n\nThis is probably due to your
// password being entered incorrectly."
gc.showErrorMessage(ts.l("actionPerformed.error_subj"),
ts.l("actionPerformed.error_txt"));
}
}
else
{
gc.getPersonaDialog().changedOK = true;
gc.getPersonaDialog().setHidden(true);
}
}
}
public synchronized boolean setPersona(String newPersona, String password)
{
boolean personaChangeSuccessful = false;
try
{
personaChangeSuccessful = session.selectPersona(newPersona, password);
// when we change personae, we lose our filter. Clear the
// reference to our filterDialog so that we will recreate it
// from scratch if we need to.
gc.filterDialog = null;
if (personaChangeSuccessful)
{
gc.setWaitCursor();
glogin.active_username = newPersona;
glogin.active_passwd = password;
gc.createDialog = null;
JDefaultOwnerDialog.clear(); // forget our default owner groups selection
// "Changing Persona"
gc.setStatus(ts.l("setPersona.changing_status"));
// "Ganymede Client: {0} logged in"
gc.setTitle(ts.l("setPersona.new_window_title", newPersona));
// List of creatable object types might have changed.
gc.ownerGroups = null;
gc.clearCaches();
gc.loader.clear(); // This reloads the hashes on a new background thread
gc.cancelTransaction();
gc.buildTree();
gc.currentPersonaString = newPersona;
gc.defaultOwnerChosen = false; // force a new default owner to be chosen
try
{
gc.ownerGroups = session.getOwnerGroups().getListHandles();
if (gc.ownerGroups.size() == 1)
{
gc.chooseDefaultOwner(false);
gc.defaultOwnerMI.setEnabled(false);
}
else if (gc.ownerGroups.size() > 1)
{
gc.defaultOwnerMI.setEnabled(true);
}
else if (gc.ownerGroups.size() == 0)
{
gc.defaultOwnerMI.setEnabled(false);
}
}
catch (RemoteException ex)
{
gc.processExceptionRethrow(ex);
}
gc.setNormalCursor();
// "Successfully changed persona to {0}."
gc.setStatus(ts.l("setPersona.changed_status", newPersona));
return true;
}
else
{
return false;
}
}
catch (Exception rx)
{
// "Exception encountered trying to change persona to {0}:\n"
gc.processException(rx, ts.l("setPersona.exception_txt", newPersona));
return false;
}
}
public void softTimeOutHandler()
{
setPersona(gc.my_username, null);
gc.changePersona(true);
}
}
/*------------------------------------------------------------------------------
class
CacheInfo
------------------------------------------------------------------------------*/
/**
* Client-side cache object, used by the
* {@link arlut.csd.ganymede.client.gclient gclient} class to track object status for
* nodes in the client tree display.
*/
class CacheInfo {
private String
originalLabel,
currentLabel;
private Short
baseID;
private ObjectHandle
originalHandle = null,
handle;
private static final boolean debug = false;
/* -- */
public CacheInfo(Short baseID, String originalLabel, String currentLabel)
{
this(baseID, originalLabel, currentLabel, null);
}
public CacheInfo(Short baseID, String originalLabel, String currentLabel, ObjectHandle handle)
{
this(baseID, originalLabel, currentLabel, handle, null);
if (handle != null)
{
try
{
originalHandle = (ObjectHandle) handle.clone();
if (debug)
{
System.err.println("a cloned handle.");
}
}
catch (Exception x)
{
originalHandle = null;
if (debug)
{
System.err.println("Clone is not supported: " + x);
}
}
}
else
{
originalHandle = null;
if (debug)
{
System.err.println("a null handle.");
}
}
}
public CacheInfo(Short baseID, String originalLabel,
String currentLabel, ObjectHandle handle, ObjectHandle originalHandle)
{
this.baseID = baseID;
this.originalLabel = originalLabel;
this.currentLabel = currentLabel;
this.handle = handle;
this.originalHandle = originalHandle;
}
public void setOriginalLabel(String label)
{
originalLabel = label;
}
public void changeLabel(String newLabel)
{
currentLabel = newLabel;
}
public Short getBaseID()
{
return baseID;
}
public String getOriginalLabel()
{
return originalLabel;
}
public String getCurrentLabel()
{
return currentLabel;
}
public ObjectHandle getObjectHandle()
{
return handle;
}
public ObjectHandle getOriginalObjectHandle()
{
return originalHandle;
}
}
/*------------------------------------------------------------------------------
class
StatusClearThread
------------------------------------------------------------------------------*/
/**
* Background thread designed to clear the status label in
* {@link arlut.csd.ganymede.client.gclient gclient}
* some seconds after the setClock() method is called.
*/
class StatusClearThread extends Thread {
final static boolean debug = false;
boolean done = false;
boolean resetClock = false;
private String defaultMessage = "";
JLabel statusLabel;
int sleepSecs = 0;
/* -- */
public StatusClearThread(JLabel statusLabel)
{
this.statusLabel = statusLabel;
}
public synchronized void run()
{
while (!done)
{
if (debug)
{
System.err.println("StatusClearThread.run(): entering loop");
}
resetClock = false;
try
{
if (sleepSecs > 0)
{
if (debug)
{
System.err.println("StatusClearThread.run(): waiting " + sleepSecs + " seconds");
}
wait(sleepSecs * 1000);
}
else
{
if (debug)
{
System.err.println("StatusClearThread.run(): waiting indefinitely");
}
wait();
}
}
catch (InterruptedException ex)
{
}
if (!resetClock && !done)
{
// this has to be invokeLater or else we'll risk getting
// deadlocked.
if (debug)
{
System.err.println("StatusClearThread.run(): invoking label clear");
}
EventQueue.invokeLater(new Runnable() {
public void run() {
statusLabel.setText(defaultMessage);
statusLabel.paintImmediately(statusLabel.getVisibleRect());
}
});
sleepSecs = 0;
}
}
}
public synchronized void setDefaultMessage(String message)
{
this.defaultMessage = message;
if (sleepSecs == 0)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
statusLabel.setText(defaultMessage);
statusLabel.paintImmediately(statusLabel.getVisibleRect());
}
});
}
}
/**
* This method resets the clock in the StatusClearThread, such that
* the status label will be cleared in countDown seconds, unless
* another setClock follows on closely enough to interrupt the
* countdown, effectively.
*
* @param countDown seconds to wait before clearing the status field. If
* countDown is zero or negative, the timer will suspend until a later
* call to setClock sets a positive countdown.
*/
public synchronized void setClock(int countDown)
{
if (debug)
{
System.err.println("StatusClearThread.setClock(" + countDown + ")");
}
resetClock = true;
sleepSecs = countDown;
notifyAll();
if (debug)
{
System.err.println("StatusClearThread.setClock(" + countDown + ") - done");
}
}
/**
* This method causes the run() method to gracefully terminate
* without taking any further action.
*/
public synchronized void shutdown()
{
this.done = true;
notifyAll();
}
}
/*------------------------------------------------------------------------------
class
SecurityLaunderThread
------------------------------------------------------------------------------*/
/**
* Background client thread designed to launder build status messages
* from the server on a non-RMI thread. We do this so that RMI calls
* from the server are granted permission to put events on the GUI
* thread for apropriately synchronized icon setitng. Set up and torn
* down by the {@link arlut.csd.ganymede.client.gclient gclient}
* class.
*/
class SecurityLaunderThread extends Thread {
gclient client;
boolean done = false;
boolean messageSet = false;
int buildPhase = -1; // unknown
/* -- */
public SecurityLaunderThread(gclient client)
{
this.client = client;
// assume we were constructed on the GUI thread by the main
// gclient constructor
switch (client.getBuildPhase())
{
case 0:
client.buildLabel.setIcon(client.idleIcon);
break;
case 1:
client.buildLabel.setIcon(client.buildIcon);
break;
case 2:
client.buildLabel.setIcon(client.buildIcon2);
break;
default:
client.buildLabel.setIcon(client.buildUnknownIcon);
}
client.buildLabel.validate();
}
public synchronized void run()
{
while (!done)
{
try
{
wait();
}
catch (InterruptedException ex)
{
}
if (messageSet)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
switch (buildPhase)
{
case 0:
client.buildLabel.setIcon(client.idleIcon);
break;
case 1:
client.buildLabel.setIcon(client.buildIcon);
break;
case 2:
client.buildLabel.setIcon(client.buildIcon2);
break;
default:
client.buildLabel.setIcon(client.buildUnknownIcon);
}
client.buildLabel.validate();
}
});
messageSet = false;
}
}
// done!
client = null;
}
/**
* This method is called to trigger a build status icon update.
* Called by {@link arlut.csd.ganymede.client.gclient#setBuildStatus(java.lang.String) gclient.setBuildStatus()}.
*/
public synchronized void setBuildStatus(int phase)
{
this.messageSet = true;
this.buildPhase = phase;
this.notifyAll(); // wakey-wakey!
}
/**
* This method causes the run() method to gracefully terminate
* without taking any further action.
*/
public synchronized void shutdown()
{
this.done = true;
notifyAll();
}
}
/*------------------------------------------------------------------------------
class
ClientExceptionHandler
------------------------------------------------------------------------------*/
/**
* An UncaughtExceptionHandler used in the Ganymede client to catch
* any otherwise uncaught exceptions and prompt the user to send them
* to the server for recording.
*/
class ClientExceptionHandler implements Thread.UncaughtExceptionHandler {
public ClientExceptionHandler()
{
}
public void uncaughtException(Thread thread, Throwable ex)
{
// Apple seems to have a bug in their AquaInternalFramePaneUI
// class that throws a (otherwise silent) NullPointerException on
// the GUI event thread if the user clicks on the background of
// the JDesktopPane.
if (ex instanceof NullPointerException)
{
for (StackTraceElement el: ex.getStackTrace())
{
if (el.getClassName().equals("com.apple.laf.AquaInternalFramePaneUI") &&
el.getMethodName().equals("mousePressed"))
{
return; // silently eat the exception
}
}
}
if (gclient.client != null)
{
try
{
gclient.client.processException(ex);
}
catch (Throwable ex2)
{
ex.printStackTrace();
ex2.printStackTrace();
}
}
else
{
ex.printStackTrace();
}
}
}