/*
framePanel.java
The individual Ganymede Object view/edit frames in the windowPanel.
Created: 4 September 1997
Module By: Michael Mulvaney
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2014
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.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import arlut.csd.JDialog.StandardDialog;
import arlut.csd.JDialog.StringDialog;
import arlut.csd.Util.booleanSemaphore;
import arlut.csd.Util.PackageResources;
import arlut.csd.Util.StringUtils;
import arlut.csd.Util.TranslationService;
import arlut.csd.ganymede.common.BaseDump;
import arlut.csd.ganymede.common.FieldInfo;
import arlut.csd.ganymede.common.FieldTemplate;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.Query;
import arlut.csd.ganymede.common.QueryDataNode;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.SchemaConstants;
import arlut.csd.ganymede.rmi.date_field;
import arlut.csd.ganymede.rmi.db_object;
import arlut.csd.ganymede.rmi.invid_field;
import arlut.csd.ganymede.rmi.string_field;
import arlut.csd.ganymede.rmi.FileTransmitter;
import arlut.csd.ganymede.rmi.Session;
/*------------------------------------------------------------------------------
class
framePanel
------------------------------------------------------------------------------*/
/**
* <p>An internal client window displaying and/or editing a particular
* database object from the Ganymede server. A framePanel is a
* JInternalFrame which contains a tabbed pane which incorporates a
* {@link arlut.csd.ganymede.client.containerPanel containerPanel} for
* viewing/editing a server-side database object, as well as several
* auxiliary panes such as an {@link
* arlut.csd.ganymede.client.ownerPanel ownerPanel}, {@link
* arlut.csd.ganymede.client.historyPanel historyPanel}, and other
* panels as appropriate for specific object types.</p>
*
* @author Michael Mulvaney
*/
public class framePanel extends JInternalFrame implements ChangeListener, ActionListener,
VetoableChangeListener, InternalFrameListener {
/**
* TranslationService object for handling string localization in
* the Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.framePanel");
/**
* Java 1.4 preferences API key to save the default location to save
* object summary.
*/
static final String OBJECT_SAVE = "object_save_default_dir";
// ---
/**
* This will be loaded from gclient anyway.
*/
boolean debug = false;
/**
* <p>Used with vetoableChange() to work around Swing 1.1 bug
* preventing setDefaultCloseOperation(DO_NOTHING_ON_CLOSE) from
* doing anything useful.</p>
*
* <p>This variable needs to be set to true in order for setClosed()
* calls in windowPanel to avoid bringing up the dialogs.</p>
*
* <p>Actually, this variable has now been overloaded with an
* additional function. When we are closing a newly created window,
* we set closingApproved to true so that the gclient deleteObject()
* method won't balk at our deleting the newly created objects.</p>
*
* <p>This is totally like biological evolution, in which a
* pre-existing feature is adapted for a new purpose over time.</p>
*
* <p>Hot-cha-cha.</p>
*/
boolean closingApproved = false;
/**
* <p>Used with internalFrameClosed() to make our JInternalFrame
* close interception hack from Swing 1.1 work with Kestrel.</p>
*
* <p>If this variable is set to true, internalFrameClosed() will
* not attempt to call dispose().</p>
*/
private booleanSemaphore closed = new booleanSemaphore(false);
private booleanSemaphore running = new booleanSemaphore(false);
private booleanSemaphore stopped = new booleanSemaphore(false);
/**
* We'll show a progressBar while the general panel is loading. The
* progressBar is contained in the progressPanel, which will be
* removed when the general panel is finished loading.
*/
JProgressBar
progressBar;
/**
* Panel to hold the progressBar while we are loading the fields for
* this object.
*/
JPanel
progressPanel;
/**
* The tabbed pane holding our various panels.
*/
JTabbedPane
pane;
/**
* <p>A vector of {@link arlut.csd.ganymede.client.containerPanel}s,
* used to allow the gclient to refresh containerPanels on demand,
* and to allow the gclient to order any containerPanels contained
* in this framePanel to stop loading on a transaction cancel.</p>
*
* <p>Note that the cleanUp() method in this class can null out
* this reference, so all methods that loop over containerPanels
* should be synchronized. This is also why containerPanels
* is kept private.</p>
*/
private Vector<containerPanel> containerPanels = new Vector<containerPanel>();
/**
* Vector of {@link arlut.csd.ganymede.common.FieldTemplate
* FieldTemplate}s used by the save() and save_history() methods to
* enumerate this object's fields.
*/
Vector<FieldTemplate> templates;
/**
* Vector of {@link arlut.csd.ganymede.client.clientTab} objects
* representing tabs that need to be created from the server-side
* tab definitions.
*/
private Vector<clientTab> tabList = new Vector<clientTab>();
private imageTab image_tab;
private personaeTab personae_tab;
private ownerTab owner_tab;
private objectsOwnedTab objects_owned_tab;
private notesTab notes_tab;
private historyTab history_tab;
private expirationRemovalTab expiration_tab;
private expirationRemovalTab removal_tab;
/**
* RMI references to server-side fields that we'll consult to render
* the various fixed information fields in our object window.
*/
date_field
exp_field,
rem_field;
boolean
editable;
boolean
expiration_Editable;
boolean
removal_Editable;
/**
* Remote reference to the server-side object we are viewing or editing.
*/
db_object
server_object;
/**
* Reference to the desktop pane containing the client's internal windows. Used to access
* some GUI resources and to provide to new containerPanels created for embedded objects.
*/
windowPanel
wp;
/**
* Reference to the client's main class, used for some utility functions.
*/
gclient
gc;
/**
* Invid of the object edited. DO NOT access invid directly; use
* getObjectInvid(). invid will be null until the first time
* getObjectInvid() is called.
*/
private Invid invid = null;
/**
* If true, this is a newly created object we're editing. We care about this
* because we need to handle the user clicking on this window's close box
* a bit differently.
*/
boolean isCreating;
/**
* If true, we've already done clean up on this framePanel.
*/
boolean isCleaned;
/* -- */
/**
* @param object RMI reference to a server-side database object
* @param editable If true, the database object is being edited by this window
* @param winP The JDesktopPane container for this window
* @param isCreating if true, this window is for a newly created object, and will
* be treated specially when closing this window.
*/
public framePanel(Invid invid, db_object object, boolean editable, windowPanel winP, boolean isCreating)
{
this.invid = invid.intern();
this.wp = winP;
this.server_object = object;
this.editable = editable;
this.gc = winP.gc;
this.isCreating = isCreating;
// are we running the client in debug mode?
if (!debug)
{
debug = wp.gc.debug;
}
// "Building window."
setStatus(ts.l("init.building_window_status"), 1);
// Window properties
setMaximizable(true);
setResizable(true);
setClosable(true);
setIconifiable(true);
/*
we want to be able to take control of closing ourselves.
Unfortunately, the setDefaultCloseOperation() method is useless
on JInternalFrames in Swing 1.1. We'll use a
VetoableChangeListener instead.
// setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
This came around in JDK 1.2/Swing 1.1.1.. bug parade #4176136,
but the use of a VetoableChangeListener still works just fine,
so we'll stick with this implementation.
*/
this.addVetoableChangeListener(this);
this.addInternalFrameListener(this);
progressPanel = new JPanel(); // flow layout by default
progressBar = new JProgressBar();
progressPanel.add(new JLabel(ts.l("init.loading_label"))); // "Loading..."
progressPanel.add(progressBar);
setContentPane(progressPanel);
load();
}
/**
* Communicates with the server to download all of the information
* needed to present the database object associated with this window
* to the user. Some of this data (types of fields defined in objects
* of this type, for instance) will have been already loaded into
* {@link arlut.csd.ganymede.client.gclient gclient}, but this method
* is reponsible for loading all data specific to the object being
* viewed and/or edited.
*
* This method also handles the creation of this window's tabbed
* pane, and adding the various tabs to it. The actual panels
* attached to the various tabs will not actually be created and
* initialized unless and until the user selects the appropriate tab
* at some point. The only panel actually created by this method is
* the general panel, which shows all of the non-built-in fields of
* the object we're talking to.
*/
public void load()
{
Vector<FieldInfo> infoVector = null;
/* -- */
running.set(true);
try
{
// windowPanel wants to know if framePanel is changed
addInternalFrameListener(getWindowPanel());
// Now setup the framePanel layout
pane = new JTabbedPane();
pane.setOpaque(true);
// the client Loader thread should have already downloaded and
// cached the field template vector we're getting here. If not,
// we'll block here while the Loader gets the information we need.
short id = getObjectInvid().getType();
templates = gc.getTemplateVector(id);
try
{
infoVector = getObject().getFieldInfoVector();
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx);
}
// loop over the field templates and identify the
// server-defined tabs
serverTab newTab = null;
serverTab oldTab = null;
for (FieldTemplate template: templates)
{
// make sure that we don't create a tab if the field isn't
// actually present in this instance of the object
short fieldID = template.getID();
boolean field_present = false;
for (int j = 0; !field_present && j < infoVector.size(); j++)
{
FieldInfo field_info = infoVector.get(j);
if (field_info.getID() == fieldID)
{
field_present = true;
}
}
if (field_present && !template.isBuiltIn() &&
(id != SchemaConstants.UserBase || fieldID != SchemaConstants.UserAdminPersonae))
{
if (oldTab == null)
{
oldTab = new serverTab(this, pane, template.getTabName());
oldTab.setInfoVector(infoVector);
oldTab.addToPane(tabList);
}
else
{
if (!StringUtils.stringEquals(template.getTabName(), oldTab.getTabName()))
{
newTab = new serverTab(this, pane, template.getTabName());
newTab.setInfoVector(infoVector);
newTab.addToPane(tabList);
oldTab = newTab;
}
}
}
}
// okay, now we we've added the server--side tabs. time to
// start in on the predefined tabs.
// for user objects, see if we have an image to display
try
{
if (id == SchemaConstants.UserBase)
{
String image_url = getObject().getImageURL();
if (image_url != null)
{
image_tab = new imageTab(this, pane, ts.l("load.image_tab"), image_url); // "Photo"
image_tab.addToPane(tabList);
}
}
}
catch (RemoteException ex)
{
gc.processExceptionRethrow(ex, "Could not check for user image: ");
}
// Check to see if this gets an objects_owned panel (for Owner
// Group objects) or an Admin Personae panel (for User
// objects)
//
// Note that the supergash OwnerBase does not get an
// objects_owned panel.
try
{
if (id == SchemaConstants.OwnerBase)
{
if (!getObjectInvid().equals(Invid.createInvid((short)0, 1)))
{
if (stopped.isSet())
{
return;
}
objects_owned_tab = new objectsOwnedTab(this, pane, ts.l("load.objects_owned_tab")); // "Objects Owned"
objects_owned_tab.addToPane(tabList);
}
}
else if (id == SchemaConstants.UserBase)
{
invid_field persona_field = getObject().getInvidField(SchemaConstants.UserAdminPersonae);
// If the field is null, then this must be a view-only
// object with no persona field defined (because
// editable objects always have all valid fields
// instantiated)
if (persona_field != null)
{
if (stopped.isSet())
{
return;
}
personae_tab = new personaeTab(this, pane, ts.l("load.personae_tab")); // "Personae"
personae_tab.addToPane(tabList);
}
}
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx, "Could not process object-specific tab: ");
}
// Only add the expiration and removal date panels if the date
// has been set. In order to set the date, use the menu
// items.
if (stopped.isSet())
{
return;
}
try
{
exp_field = getObject().getDateField(SchemaConstants.ExpirationField);
rem_field = getObject().getDateField(SchemaConstants.RemovalField);
expiration_Editable = editable && exp_field.isEditable();
removal_Editable = editable && rem_field.isEditable();
if ((exp_field != null) && (exp_field.getValue() != null))
{
addExpirationDateTab();
}
if ((rem_field != null) && (rem_field.getValue() != null))
{
addRemovalDateTab();
}
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx, "Could not get date fields");
}
// then the always present tabs.. owner, notes, history.
if (stopped.isSet())
{
return;
}
owner_tab = new ownerTab(this, pane, ts.l("load.owner_tab")); // "Owner"
owner_tab.addToPane(tabList);
// Add the notes panel
if (stopped.isSet())
{
return;
}
addNotesTab();
// Add the history tab
if (stopped.isSet())
{
return;
}
history_tab = new historyTab(this, pane, ts.l("load.history_tab")); // "History"
history_tab.addToPane(tabList);
pane.addChangeListener(this);
if (stopped.isSet())
{
return;
}
// and let's initialize and show our main tab before we make
// the tab set visible
((clientTab) tabList.elementAt(0)).showTab();
setContentPane(pane);
setJMenuBar(createMenuBar(editable));
pane.invalidate();
validate();
}
finally
{
if (closed.isSet())
{
cleanUp();
}
running.set(false);
}
}
/**
* Returns true if this framePanel is editing/creating an object, false if it
* is viewing only.
*/
public boolean isEditable()
{
return editable;
}
/**
* This method returns true if the window is in the middle of closing,
* which only happens if it has been approved by vetoableChange.
*/
public boolean isApprovedForClosing()
{
return closingApproved;
}
/**
* Returns the invid of the object contained in this frame panel.
*
* If the invid has not been loaded, this method will load it first.
*
* @return The invid of the object in this frame panel.
*/
public Invid getObjectInvid()
{
if (invid == null)
{
try
{
invid = getObject().getInvid().intern();
}
catch (Exception rx)
{
// "Could not get object invid"
gc.processExceptionRethrow(rx, ts.l("getObjectInvid.error_msg"));
}
}
return invid;
}
/**
* Returns the type name of the object contained in this frame panel.
*
* If the invid has not been loaded, this method will load it first.
*
* @return The name of the type of the object in this frame panel.
*/
public String getObjectTypeName()
{
getObjectInvid();
return gc.getObjectType(invid);
}
/**
* Used by gclient.commitTransaction to get access to our notes panel.
* gclient does this so that it can survey any open notes panels to make
* sure that the contents are updated to the server. This is ugly as
* sin, but we don't currently put a change listener on the notes panels,
* so it needs to be done.
*
* This method will often return null if the user hasn't visited the
* notes panel tab.
*/
public notesPanel getNotesPanel()
{
if (notes_tab == null)
{
return null;
}
return notes_tab.getNotesPanel();
}
/**
* Uses the Ganymede server to e-mail a summary of this object to one
* or more email addresses.
*/
private void mail_obj()
{
SaveObjDialog dialog;
boolean showHistory = false;
boolean showTransactions = false;
Date startDate = null;
String address;
String subject;
StringBuffer body;
/* -- */
// "Mail Object XML for {0} {1}"
// "Object XML for {0} {1}"
dialog = new SaveObjDialog(gc,
ts.l("mail_obj.mailobj_title", getObjectType(), getObjectLabel()),
false, true,
ts.l("mail_obj.mailobj_subject", getObjectType(), getObjectLabel()),
server_object);
gc.setWaitCursor();
try
{
if (!dialog.showDialog())
{
return;
}
showTransactions = dialog.isShowTransactions();
startDate = dialog.getStartDate();
subject = dialog.getSubject();
address = dialog.getRecipients();
if ((address == null) || (address.equals("")))
{
// "You must specify at least one recipient."
gc.showErrorMessage(ts.l("mail_obj.no_recipient_msg"));
return;
}
body = encodeObjectToXML();
if (debug)
{
System.out.println("Mailing: \nTo: " + address + "\n\n" + body.toString());
}
try
{
gc.getSession().sendMail(address, subject, body);
}
catch (Exception rx)
{
// "Sending Mail"
gc.processExceptionRethrow(rx, ts.l("mail_obj.error_rethrow"));
}
}
finally
{
gc.setNormalCursor();
}
}
/**
* Sends email containing log information concerning this object.
*/
private void mail_history()
{
SaveObjDialog dialog;
boolean showHistory = false;
boolean showTransactions = false;
Date startDate = null;
String address;
String subject;
StringBuffer body;
/* -- */
// "Mail History for {0} {1}"
// "Object History for {0} {1}"
dialog = new SaveObjDialog(gc, ts.l("mail_history.dialog_title", getObjectType(), getObjectLabel()),
true, true, ts.l("mail_history.mail_subject", getObjectType(), getObjectLabel()),
server_object);
gc.setWaitCursor();
try
{
if (!dialog.showDialog())
{
return;
}
showTransactions = dialog.isShowTransactions();
startDate = dialog.getStartDate();
subject = dialog.getSubject();
address = dialog.getRecipients();
if ((address == null) || (address.equals("")))
{
// "You must specify at least one recipient."
gc.showErrorMessage(ts.l("mail_obj.no_recipient_msg"));
return;
}
StringWriter writer = null;
PrintWriter pWriter = null;
writer = new StringWriter();
pWriter = new PrintWriter(writer);
try
{
pWriter.println(gc.getSession().viewObjectHistory(getObjectInvid(),
startDate, showTransactions));
}
catch (RemoteException ex)
{
gc.processExceptionRethrow(ex, "Couldn't receive history from server.");
}
finally
{
pWriter.close();
}
body = new StringBuffer(writer.toString());
try
{
gc.getSession().sendMail(address, subject, body);
}
catch (Exception rx)
{
// "Sending Mail"
gc.processExceptionRethrow(rx, ts.l("mail_obj.error_rethrow"));
}
}
finally
{
gc.setNormalCursor();
}
}
/**
* Saves an XML summary of this object to disk. Only available if
* the Ganymede client was run as an application.
*/
private void save()
{
JFileChooser chooser = new JFileChooser();
int returnValue;
Date startDate = null;
boolean showHistory = false;
boolean showTransactions = false;
File file;
FileOutputStream fos = null;
PrintWriter writer = null;
/* -- */
gc.setWaitCursor();
try
{
chooser.setDialogType(JFileChooser.SAVE_DIALOG);
chooser.setDialogTitle(ts.l("save.file_dialog_title")); // "Save Object XML as"
if (gclient.prefs != null)
{
String defaultPath = gclient.prefs.get(OBJECT_SAVE, null);
if (defaultPath != null)
{
chooser.setCurrentDirectory(new File(defaultPath));
}
}
returnValue = chooser.showDialog(gc, null);
if (!(returnValue == JFileChooser.APPROVE_OPTION))
{
return;
}
file = chooser.getSelectedFile();
File directory = chooser.getCurrentDirectory();
if (gclient.prefs != null)
{
try
{
gclient.prefs.put(OBJECT_SAVE, 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.
}
}
if (file.exists())
{
// "Warning"
// "{0} already exists. Are you sure you want to replace this file?"
// "Overwrite"
// "Cancel"
StringDialog d = new StringDialog(gc,
ts.l("save.warning_title"),
ts.l("save.conflict_warning", file.getName()),
ts.l("save.overwrite_button"),
ts.l("global.cancel"),
null, StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = d.showDialog();
if (result == null)
{
if (debug)
{
System.out.println("The file exists, and I am backing out.");
}
return;
}
}
try
{
fos = new FileOutputStream(file);
writer = new PrintWriter(fos);
}
catch (java.io.IOException e)
{
// "Save Error"
// "Could not open the file for writing:\n{0}"
gc.showErrorMessage(ts.l("save.io_error_title"),
ts.l("save.io_error_text", e.toString()));
return;
}
writer.println(encodeObjectToXML());
writer.close();
}
finally
{
gc.setNormalCursor();
}
}
/**
* Saves a text file containing log information concerning this
* object to disk. Only available if the Ganymede client was run as
* an application.
*/
public void save_history()
{
SaveObjDialog dialog;
JFileChooser chooser = new JFileChooser();
int returnValue;
Date startDate = null;
boolean showHistory = false;
boolean showTransactions = false;
File file;
FileOutputStream fos = null;
PrintWriter writer = null;
/* -- */
// "Save Object History for {0} {1}"
dialog = new SaveObjDialog(gc,
ts.l("save_history.saveobj_title", getObjectType(), getObjectLabel()),
true, false, null, server_object);
if (!dialog.showDialog())
{
if (debug)
{
System.out.println("dialog returned false, returning");
}
return;
}
gc.setWaitCursor();
startDate = dialog.getStartDate();
showTransactions = dialog.isShowTransactions();
chooser.setDialogType(JFileChooser.SAVE_DIALOG);
chooser.setDialogTitle(ts.l("save_history.file_dialog_title")); // "Save Object History as"
if (gclient.prefs != null)
{
String defaultPath = gclient.prefs.get(OBJECT_SAVE, null);
if (defaultPath != null)
{
chooser.setCurrentDirectory(new File(defaultPath));
}
}
returnValue = chooser.showDialog(gc, null);
if (!(returnValue == JFileChooser.APPROVE_OPTION))
{
return;
}
file = chooser.getSelectedFile();
File directory = chooser.getCurrentDirectory();
if (gclient.prefs != null)
{
try
{
gclient.prefs.put(OBJECT_SAVE, 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.
}
}
if (file.exists())
{
// "Warning"
// "{0} already exists. Are you sure you want to replace this file?"
// "Overwrite"
// "Cancel"
StringDialog d = new StringDialog(gc,
ts.l("save.warning_title"),
ts.l("save.conflict_warning", file.getName()),
ts.l("save.overwrite_button"),
ts.l("global.cancel"),
null, StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = d.showDialog();
if (result == null)
{
if (debug)
{
System.out.println("The file exists, and I am backing out.");
}
return;
}
}
try
{
fos = new FileOutputStream(file);
writer = new PrintWriter(fos);
}
catch (java.io.IOException e)
{
// "Save Error"
// "Could not open the file for writing:\n{0}"
gc.showErrorMessage(ts.l("save.io_error_title"),
ts.l("save.io_error_text", e.toString()));
return;
}
try
{
writer.println(gc.getSession().viewObjectHistory(getObjectInvid(),
startDate, showTransactions));
}
catch (RemoteException ex)
{
gc.processExceptionRethrow(ex, "Couldn't receive history from server.");
}
finally
{
writer.close();
gc.setNormalCursor();
}
}
/**
* Generates an XML representation of this object for save()/mail().
*/
private StringBuffer encodeObjectToXML()
{
Session session = gc.getSession();
StringBuffer buffer = null;
Query query = new Query(this.invid.getType(), new QueryDataNode((short) -2, QueryDataNode.EQUALS, this.invid), false);
try
{
ReturnVal retVal = session.runXMLQuery(query);
if (!ReturnVal.didSucceed(retVal))
{
return null;
}
FileTransmitter transmitter = retVal.getFileTransmitter();
if (transmitter != null)
{
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
byte[] chunk = transmitter.getNextChunk();
while (chunk != null)
{
outstream.write(chunk, 0, chunk.length);
chunk = transmitter.getNextChunk();
}
transmitter.end();
buffer = new StringBuffer(outstream.toString());
}
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx);
}
return buffer;
}
/**
* Generates a String representation of this object's log history
*/
private StringBuffer getHistory(boolean showTransactions, Date startDate)
{
StringBuffer buffer = new StringBuffer();
if (showTransactions)
{
// "\nTransactional History:\n\n"
buffer.append(ts.l("encodeObjectToStringBuffer.transaction_history_header"));
}
else
{
// "\nHistory:\n\n"
buffer.append(ts.l("encodeObjectToStringBuffer.history_header"));
}
try
{
buffer.append(gc.getSession().viewObjectHistory(getObjectInvid(),
startDate, showTransactions).toString());
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx);
}
return buffer;
}
public Image getWaitImage()
{
return wp.getWaitImage();
}
/* Private stuff */
JMenuBar createMenuBar(boolean editable)
{
JMenuBar menuBar = new JMenuBar();
// "Object"
JMenu fileM = new JMenu(ts.l("createMenuBar.object_menu"));
if (ts.hasPattern("createMenuBar.object_menu_key_optional"))
{
fileM.setMnemonic((int) ts.l("createMenuBar.object_menu_key_optional").charAt(0)); // "o"
}
menuBar.add(fileM);
if (gc.isApplet())
{
JMenuItem mailXmlMI = new JMenuItem(ts.l("createMenuBar.object_menu_1a")); // "Mail XML..";
mailXmlMI.setActionCommand("mail_obj");
if (ts.hasPattern("createMenuBar.object_menu_1a_key_optional"))
{
mailXmlMI.setMnemonic((int) ts.l("createMenuBar.object_menu_1_key_optional").charAt(0)); // "m"
}
if (ts.hasPattern("createMenuBar.object_menu_1a_tip_optional"))
{
// "Mails an XML dump of this object''s state"
mailXmlMI.setToolTipText(ts.l("createMenuBar.object_menu_1a_tip_optional"));
}
mailXmlMI.addActionListener(this);
fileM.add(mailXmlMI);
JMenuItem mailHistoryMI = new JMenuItem(ts.l("createMenuBar.object_menu_2a")); // "Mail History.."
mailHistoryMI.setActionCommand("mail_history");
if (ts.hasPattern("createMenuBar.object_menu_2_key_optional"))
{
mailHistoryMI.setMnemonic((int) ts.l("createMenuBar.object_menu_2a_key_optional").charAt(0)); // "h"
}
if (ts.hasPattern("createMenuBar.object_menu_2_tip_optional"))
{
// "Mails the history of this object"
mailHistoryMI.setToolTipText(ts.l("createMenuBar.object_menu_2a_tip_optional"));
}
mailHistoryMI.addActionListener(this);
fileM.add(mailHistoryMI);
}
else
{
JMenuItem saveMI = new JMenuItem(ts.l("createMenuBar.object_menu_1")); // "Save XML..";
saveMI.setActionCommand("save_obj");
if (ts.hasPattern("createMenuBar.object_menu_1_key_optional"))
{
saveMI.setMnemonic((int) ts.l("createMenuBar.object_menu_1_key_optional").charAt(0)); // "s"
}
if (ts.hasPattern("createMenuBar.object_menu_1_tip_optional"))
{
// "Saves an XML dump of this object''s state to disk"
saveMI.setToolTipText(ts.l("createMenuBar.object_menu_1_tip_optional"));
}
saveMI.addActionListener(this);
fileM.add(saveMI);
JMenuItem historyMI = new JMenuItem(ts.l("createMenuBar.object_menu_2")); // "Save History.."
historyMI.setActionCommand("save_history");
if (ts.hasPattern("createMenuBar.object_menu_2_key_optional"))
{
historyMI.setMnemonic((int) ts.l("createMenuBar.object_menu_2_key_optional").charAt(0)); // "h"
}
if (ts.hasPattern("createMenuBar.object_menu_2_tip_optional"))
{
// "Saves the history of this object to disk"
historyMI.setToolTipText(ts.l("createMenuBar.object_menu_2_tip_optional"));
}
historyMI.addActionListener(this);
fileM.add(historyMI);
}
if (editable)
{
boolean sepAdded = false;
try
{
if (expiration_Editable && getObject().canInactivate())
{
fileM.addSeparator();
sepAdded = true;
// "Set Expiration Date"
JMenuItem setExpirationMI = new JMenuItem(ts.l("createMenuBar.object_menu_3"));
setExpirationMI.setActionCommand("set_expiration");
if (ts.hasPattern("createMenuBar.object_menu_3_key_optional"))
{
setExpirationMI.setMnemonic((int) ts.l("createMenuBar.object_menu_3_key_optional").charAt(0)); // "x"
}
if (ts.hasPattern("createMenuBar.object_menu_3_tip_optional"))
{
// "Set a date for this object to be inactivated"
setExpirationMI.setToolTipText(ts.l("createMenuBar.object_menu_3_tip_optional"));
}
setExpirationMI.addActionListener(this);
fileM.add(setExpirationMI);
}
}
catch (Exception ex)
{
gc.processExceptionRethrow(ex);
}
if (removal_Editable)
{
if (!sepAdded)
{
fileM.addSeparator();
}
// "Set Removal Date"
JMenuItem setRemovalMI = new JMenuItem(ts.l("createMenuBar.object_menu_4"));
setRemovalMI.setActionCommand("set_removal");
if (ts.hasPattern("createMenuBar.object_menu_4_key_optional"))
{
setRemovalMI.setMnemonic((int) ts.l("createMenuBar.object_menu_4_key_optional").charAt(0)); // "v"
}
if (ts.hasPattern("createMenuBar.object_menu_4_tip_optional"))
{
// "Set a date for this object to be removed from the database"
setRemovalMI.setToolTipText(ts.l("createMenuBar.object_menu_4_tip_optional"));
}
setRemovalMI.addActionListener(this);
fileM.add(setRemovalMI);
}
}
if (!editable)
{
fileM.addSeparator();
String typeName = getObjectTypeName();
// "Edit this {0}"
JMenuItem editObjMI = new JMenuItem(ts.l("createMenuBar.object_menu_5", typeName));
if (ts.hasPattern("createMenuBar.object_menu_5_key_optional"))
{
editObjMI.setMnemonic((int) ts.l("createMenuBar.object_menu_5_key_optional").charAt(0)); // "e"
}
if (ts.hasPattern("createMenuBar.object_menu_5_tip_optional"))
{
editObjMI.setToolTipText(ts.l("createMenuBar.object_menu_5_tip_optional"));
}
editObjMI.setActionCommand("edit_obj");
editObjMI.addActionListener(this);
fileM.add(editObjMI);
BaseDump bd = (BaseDump) gc.getBaseMap().get(Short.valueOf(getObjectInvid().getType()));
if (bd.canInactivate())
{
// "Inactivate this {0}"
JMenuItem inactObjMI = new JMenuItem(ts.l("createMenuBar.object_menu_6", typeName));
if (ts.hasPattern("createMenuBar.object_menu_6_key_optional"))
{
inactObjMI.setMnemonic((int) ts.l("createMenuBar.object_menu_6_key_optional").charAt(0)); // "i"
}
if (ts.hasPattern("createMenuBar.object_menu_6_tip_optional"))
{
inactObjMI.setToolTipText(ts.l("createMenuBar.object_menu_6_tip_optional"));
}
inactObjMI.setActionCommand("inact_obj");
inactObjMI.addActionListener(this);
fileM.add(inactObjMI);
}
// "Delete this {0}"
JMenuItem delObjMI = new JMenuItem(ts.l("createMenuBar.object_menu_7", typeName));
if (ts.hasPattern("createMenuBar.object_menu_7_key_optional"))
{
delObjMI.setMnemonic((int) ts.l("createMenuBar.object_menu_7_key_optional").charAt(0)); // "d"
}
if (ts.hasPattern("createMenuBar.object_menu_7_tip_optional"))
{
delObjMI.setToolTipText(ts.l("createMenuBar.object_menu_7_tip_optional"));
}
delObjMI.setActionCommand("del_obj");
delObjMI.addActionListener(this);
fileM.add(delObjMI);
// "Clone this {0}"
JMenuItem cloneObjMI = new JMenuItem(ts.l("createMenuBar.object_menu_8", typeName));
if (ts.hasPattern("createMenuBar.object_menu_8_key_optional"))
{
cloneObjMI.setMnemonic((int) ts.l("createMenuBar.object_menu_8_key_optional").charAt(0)); // "c"
}
if (ts.hasPattern("createMenuBar.object_menu_8_tip_optional"))
{
cloneObjMI.setToolTipText(ts.l("createMenuBar.object_menu_8_tip_optional"));
}
cloneObjMI.setActionCommand("clone_obj");
cloneObjMI.addActionListener(this);
fileM.add(cloneObjMI);
}
if (debug)
{
println("Returning menubar.");
}
return menuBar;
}
/**
* These add the tabs to the framePanel, but they don't create the content
*
* The create_ methods create the actual panes, after the pane is selected.
* If you want to create a panel, call addWhateverPanel, then showWhateverPanel.
*/
public void addExpirationDateTab()
{
if (expiration_tab != null)
{
return;
}
if (debug)
{
println("Adding expiration tab");
}
expiration_tab = new expirationRemovalTab(this, pane, ts.l("addExpirationDateTab.expiration_tab")); // "Expiration"
expiration_tab.setImageIcon(new ImageIcon((Image) PackageResources.getImageResource(this,
"expire.gif",
getClass())));
expiration_tab.setPanelTitle(ts.l("addExpirationDateTab.panel_title")); // "Expiration date"
expiration_tab.setDateField(exp_field);
expiration_tab.addToPane(tabList);
}
public void addRemovalDateTab()
{
if (removal_tab != null)
{
return;
}
if (debug)
{
println("Adding removal date tabs");
}
removal_tab = new expirationRemovalTab(this, pane, ts.l("addRemovalDateTab.removal_tab")); // "Expiration"
removal_tab.setImageIcon(new ImageIcon((Image) PackageResources.getImageResource(this,
"expire.gif",
getClass())));
removal_tab.setPanelTitle(ts.l("addRemovalDateTab.panel_title")); // "Expiration date"
removal_tab.setDateField(rem_field);
removal_tab.addToPane(tabList);
}
public void addNotesTab()
{
if (notes_tab != null)
{
return;
}
if (debug)
{
println("Adding notes tab");
}
string_field notes_field = null;
try
{
notes_field = getObject().getStringField(SchemaConstants.NotesField);
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx, "Could not get notes_field: ");
}
ImageIcon noteIcon = null;
try
{
if (notes_field != null)
{
String notesText = (String)notes_field.getValue();
if ((notesText != null) && (!notesText.trim().equals("")))
{
if (debug)
{
println("Setting notes test to *" + notesText + "*.");
}
noteIcon = new ImageIcon((Image) PackageResources.getImageResource(this,
"note02.gif",
getClass()));
}
}
}
catch (Exception rx)
{
gc.processExceptionRethrow(rx);
}
notes_tab = new notesTab(this, pane, ts.l("addNotesTab.notes_panel_name")); // "Notes"
notes_tab.setImageIcon(noteIcon);
notes_tab.addToPane(tabList);
}
/**
* This method is called by {@link
* arlut.csd.ganymede.client.containerPanel#update(java.util.Vector)}
* to cause the expiration date panel to be refreshed whenever we
* get a {@link arlut.csd.ganymede.common.ReturnVal} back from the
* server so ordering us.
*/
public void refresh_expiration_date_panel()
{
if (expiration_tab != null)
{
expiration_tab.update();
}
else
{
addExpirationDateTab();
}
}
/**
* This method is called by {@link
* arlut.csd.ganymede.client.containerPanel#update(java.util.Vector)}
* to cause the removal date panel to be refreshed whenever we get a
* {@link arlut.csd.ganymede.common.ReturnVal} back from the server
* so ordering us.
*/
public void refresh_removal_date_panel()
{
if (removal_tab != null)
{
removal_tab.update();
}
else
{
addRemovalDateTab();
}
}
public void actionPerformed(ActionEvent e)
{
if (debug)
{
println("Menu item action: " + e.getActionCommand());
}
if (e.getActionCommand().equals("save_obj"))
{
if (debug)
{
println("Saving...:");
}
save();
}
else if (e.getActionCommand().equals("save_history"))
{
save_history();
}
else if (e.getActionCommand().equals("mail_obj"))
{
mail_obj();
}
else if (e.getActionCommand().equals("mail_history"))
{
mail_history();
}
else if (e.getActionCommand().equals("set_expiration"))
{
addExpirationDateTab();
expiration_tab.showTab();
}
else if (e.getActionCommand().equals("set_removal"))
{
addRemovalDateTab();
removal_tab.showTab();
}
else if (e.getActionCommand().equals("edit_obj"))
{
gc.editObject(getObjectInvid(), this);
}
else if (e.getActionCommand().equals("inact_obj"))
{
gc.inactivateObject(getObjectInvid());
}
else if (e.getActionCommand().equals("del_obj"))
{
gc.deleteObject(getObjectInvid(), true);
}
else if (e.getActionCommand().equals("clone_obj"))
{
gc.cloneObject(getObjectInvid());
}
else
{
System.err.println("Unknown action event: " + e);
}
}
// For the ChangeListener
public void stateChanged(ChangeEvent e)
{
// make sure we tell the tab to create itself.
int index = pane.getSelectedIndex();
clientTab tab = (clientTab) tabList.elementAt(index);
tab.showTab(); // causes the tab's contents to be
// created if it is not already
}
// Convienence methods
final gclient getgclient()
{
return getWindowPanel().getgclient();
}
final windowPanel getWindowPanel()
{
return wp;
}
final db_object getObject()
{
return this.server_object;
}
private final void setStatus(String status, int seconds)
{
wp.gc.setStatus(status,seconds);
}
String getObjectType()
{
String result = null;
try
{
result = getObject().getTypeName();
}
catch (Exception ex)
{
gc.processExceptionRethrow(ex, "Problem in getObjectType()");
}
return result;
}
String getObjectLabel()
{
String result = null;
try
{
result = getObject().getLabel();
}
catch (Exception ex)
{
gc.processExceptionRethrow(ex, "Problem in getObjectLabel()");
}
return result;
}
/**
* Use this instead of System.out.println, in case we want to direct
* that stuff somewhere else sometime. Plus, it is easier to type.
*/
private void println(String s)
{
System.out.println(s);
}
private void showErrorMessage(String title, String message)
{
gc.showErrorMessage(title, message);
}
public synchronized void addContainerPanel(containerPanel cp)
{
if (containerPanels != null)
{
containerPanels.add(cp);
}
}
public synchronized void removeContainerPanel(containerPanel cp)
{
if (containerPanels != null)
{
containerPanels.remove(cp);
}
}
/**
* This method is called to force an update on this framePanel. All
* fields will be refreshed and the choice lists reloaded.
*/
public void updateContainerPanels()
{
this.updateContainerPanels(null, null);
}
/**
* This method is called to force an update on this framePanel in accordance
* with the rescan instructions encoded in retVal. The invid passed is the
* one we are interested in updating in this method call. If the invid
* is null, all contained panels will be forced to update. Likewise, if
* the retVal passed is null, all fields in the container panels will
* be refreshed.
*/
public synchronized void updateContainerPanels(Invid invid, ReturnVal retVal)
{
if (containerPanels == null)
{
return;
}
// Loop over each containerPanel in the framePanel window.. there
// may be more than one due to embedded objects and multiple
// server tabs
// we count down here so that we can handle things if the
// cp.update*() call causes the count of containerPanels in this
// frame to decrement (as if an embedded object panel's field is
// made invisible) we'll be able to handle it.
// if the count of containerPanels increments during this
// loop, we'll just not see the new panel(s), which is of
// course just fine.
for (int i = containerPanels.size() - 1; i >= 0; i--)
{
if (i > containerPanels.size() - 1)
{
i = containerPanels.size() - 1;
}
containerPanel cp = containerPanels.get(i);
if (debug)
{
System.out.println("gclient.handleReturnVal(): Checking containerPanel number " + i);
System.out.println("\tcp.invid= " + cp.getObjectInvid() +
" lookng for: " + invid);
}
if (invid == null || cp.getObjectInvid().equals(invid))
{
if (debug)
{
System.out.println(" Found container panel for " + invid +
": " + cp.frame.getTitle());
}
if (retVal == null || retVal.rescanAll(invid))
{
cp.updateAll();
}
else
{
cp.update(retVal.getRescanList(invid));
}
}
}
}
/**
* <p>If this object window contains any editable containerPanels,
* this method will examine all Invid fields contained within and
* change the label of the Invid parameter to the newLabel.</p>
*
* <p>This method will also relabel the object window to reflect the
* new title, if we're open for editing.</p>
*/
public synchronized void relabelObject(Invid invid, String newLabel)
{
if (this.invid.equals(invid))
{
if (editable)
{
String newTitle = wp.getWindowTitle(editable, isCreating, false, gc.getObjectType(invid), newLabel);
wp.setWindowTitle(this, newTitle);
}
return; // don't bother trying to relabel fields in self, though
}
if (containerPanels == null)
{
return;
}
// Loop over each containerPanel in the framePanel window.. there
// may be more than one due to embedded objects and multiple
// server tabs
// we count down here so that we can handle things if the
// cp.update*() call causes the count of containerPanels in this
// frame to decrement (as if an embedded object panel's field is
// made invisible) we'll be able to handle it.
// if the count of containerPanels increments during this
// loop, we'll just not see the new panel(s), which is of
// course just fine.
for (int i = containerPanels.size() - 1; i >= 0; i--)
{
if (i > containerPanels.size() - 1)
{
i = containerPanels.size() - 1;
}
containerPanel cp = containerPanels.get(i);
cp.updateInvidLabels(invid, newLabel);
}
}
// InternalFrameListener methods
public void internalFrameActivated(InternalFrameEvent event)
{
}
/**
* When the internalFrame closes, we need to shut down any auxiliary
* internalFrames associated with fields in any contained container panels.
*/
public synchronized void internalFrameClosed(InternalFrameEvent event)
{
if (debug)
{
System.err.println("framePanel.internalFrameClosed(): frame closed");
}
if (this.closed.set(true))
{
if (debug)
{
System.err.println("framePanel.internalFrameClosed(): frame already closed, doing nothing.");
}
return;
}
if (debug)
{
System.err.println("framePanel.internalFrameClosed(): going invisible");
}
if (debug)
{
System.err.println("framePanel.internalFrameClosed(): disposing");
}
this.cleanUp();
this.removeAll();
if (debug)
{
System.err.println("framePanel.internalFrameClosed(): disposed");
}
// finally, shut down any secondary windows and null out all
// references to make sure that we don't cascade any leaks.. if
// the load method is still going, we'll leave this alone and let
// run() take care of this as it leaves
if (!running.isSet())
{
cleanUp();
}
}
public void internalFrameClosing(InternalFrameEvent event)
{
if (debug)
{
System.err.println("framePanel.internalFrameClosing(): Called");
}
if (!closingApproved)
{
return;
}
if (debug)
{
System.err.println("framePanel.internalFrameClosing(): Ok, closing the created window");
}
}
public void internalFrameDeactivated(InternalFrameEvent event)
{
}
public void internalFrameDeiconified(InternalFrameEvent event)
{
}
public void internalFrameIconified(InternalFrameEvent event)
{
}
public void internalFrameOpened(InternalFrameEvent event)
{
}
/**
* This is a vetoableChangeListener implementation method, and is used
* to allow the framePanel to intercede in window close attempts for
* editable objects. We use this intercession to explain to the user
* what the meaning of closing an edit-object or create-object window
* is, and to allow them to think again.
*/
public void vetoableChange(PropertyChangeEvent pce) throws PropertyVetoException
{
if (debug)
{
System.err.println("framePanel.vetoableChange()");
}
if (pce.getPropertyName().equals(IS_CLOSED_PROPERTY) &&
pce.getOldValue().equals(Boolean.FALSE) &&
pce.getNewValue().equals(Boolean.TRUE))
{
if (!closingApproved && editable)
{
StringDialog okToKill;
if (isCreating)
{
// "Ok to discard {0}?"
// "If you close this newly created window before
// committing this transaction, this newly created
// object will be forgotten and abandoned on commit."
okToKill = new StringDialog(gclient.client,
ts.l("vetoableChange.discard_title", getTitle()),
ts.l("vetoableChange.discard_text"),
ts.l("vetoableChange.discard_button"),
ts.l("global.cancel"),
gclient.client.getQuestionImage(), StandardDialog.ModalityType.DOCUMENT_MODAL);
Hashtable result = okToKill.showDialog();
if (result == null)
{
throw new PropertyVetoException("Cancelled", null);
}
if (debug)
{
System.err.println("framePanel.vetoableChange(): setting closingApproved true");
}
// set closingApproved to true before we call
// deleteObject() so that the gclient class will
// consider this deletion approved.
closingApproved = true;
gclient.client.deleteObject(getObjectInvid(), false);
}
}
}
}
/**
* This method is intended to stop any container panels from loading, in the
* event that the user has pressed the transaction cancel button in the gclient.
*/
public final void stopNow()
{
// try to interrupt the loading thread, and also turn off any
// loading methods that might run in any container panels in this
// window
stopped.set(true);
// block until we are sure this window has stopped loading
running.waitForCleared();
}
/**
* This method may be called by objects of other classes who want to check to
* see if this framePanel has asserted a stop on all loading activities.
*/
public final boolean isStopped()
{
return stopped.isSet();
}
/**
* This method provides a handy way to null out data structures held in
* relationship to this framePanel, particularly network reference
* resources.
*
* This method should be called on the Java GUI thread.
*/
public final synchronized void cleanUp()
{
if (debug)
{
System.err.println("framePanel.cleanUp()");
}
if (isCleaned)
{
return;
}
// let everyone know that we'll do no more loading in this window
stopNow();
this.setVisible(false);
this.removeVetoableChangeListener(this);
this.removeInternalFrameListener(this);
if (getWindowPanel() != null)
{
this.removeInternalFrameListener(getWindowPanel());
}
if (pane != null)
{
pane.removeChangeListener(this);
pane = null;
}
if (tabList != null)
{
synchronized (tabList)
{
for (int i = 0; i < tabList.size(); i++)
{
clientTab tab = (clientTab) tabList.elementAt(i);
tab.dispose();
}
}
tabList = null;
}
progressBar = null;
progressPanel = null;
personae_tab = null;
owner_tab = null;
objects_owned_tab = null;
notes_tab = null;
history_tab = null;
expiration_tab = null;
removal_tab = null;
if (containerPanels != null)
{
for (containerPanel cp: containerPanels)
{
cp.cleanup();
}
containerPanels = null;
}
templates = null;
exp_field = null;
rem_field = null;
server_object = null;
wp = null;
gc = null;
invid = null;
isCleaned = true;
}
/**
* Give access to the protected paramString() method of our
* ancestors for debug.
*/
public String paramString()
{
return super.paramString();
}
}