blob: 01381a805ae3ad0333f801006666c9c8ce27aa6f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.ui.internal.mappings.db;
import java.util.Iterator;
import org.eclipse.jpt.core.JpaNode;
import org.eclipse.jpt.core.JpaProject;
import org.eclipse.jpt.db.Catalog;
import org.eclipse.jpt.db.Column;
import org.eclipse.jpt.db.ConnectionListener;
import org.eclipse.jpt.db.ConnectionProfile;
import org.eclipse.jpt.db.Database;
import org.eclipse.jpt.db.ForeignKey;
import org.eclipse.jpt.db.Schema;
import org.eclipse.jpt.db.Sequence;
import org.eclipse.jpt.db.Table;
import org.eclipse.jpt.ui.WidgetFactory;
import org.eclipse.jpt.ui.internal.Tracing;
import org.eclipse.jpt.ui.internal.mappings.JptUiMappingsMessages;
import org.eclipse.jpt.ui.internal.util.SWTUtil;
import org.eclipse.jpt.ui.internal.widgets.AbstractPane;
import org.eclipse.jpt.utility.internal.ClassTools;
import org.eclipse.jpt.utility.internal.CollectionTools;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.model.value.PropertyValueModel;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
/**
* This abstract implementation keeps a combo in sync with the database objects
* when a connection is active.
*
* @see CatalogCombo
* @see ColumnCombo
* @see SchemaCombo
* @see TableCombo
*
* @version 2.0
* @since 2.0
*/
@SuppressWarnings("nls")
public abstract class AbstractDatabaseObjectCombo<T extends JpaNode> extends AbstractPane<T>
{
/**
* The main widget of this pane.
*/
private CCombo combo;
/**
* The listener added to the <code>ConnectionProfile</code> responsible to
* keep the combo in sync with the database metadata.
*/
private ConnectionListener connectionListener;
/**
* Creates a new <code>AbstractDatabaseObjectCombo</code>.
*
* @param parentPane The parent container of this one
* @param parent The parent container
*/
protected AbstractDatabaseObjectCombo(AbstractPane<? extends T> parentPane,
Composite parent) {
super(parentPane, parent);
}
/**
* Creates a new <code>AbstractDatabaseObjectCombo</code>.
*
* @param parentPane The parent container of this one
* @param subjectHolder The holder of this pane's subject
* @param parent The parent container
*/
protected AbstractDatabaseObjectCombo(AbstractPane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent) {
super(parentPane, subjectHolder, parent);
}
/**
* Creates a new <code>AbstractDatabaseObjectCombo</code>.
*
* @param subjectHolder The holder of the subject
* @param parent The parent container
* @param widgetFactory The factory used to create various common widgets
*/
protected AbstractDatabaseObjectCombo(PropertyValueModel<? extends T> subjectHolder,
Composite parent,
WidgetFactory widgetFactory)
{
super(subjectHolder, parent, widgetFactory);
}
private ConnectionListener buildConnectionListener() {
return new ConnectionListener() {
public void aboutToClose(ConnectionProfile profile) {
log(Tracing.UI_DB, "aboutToClose");
}
public void catalogChanged(ConnectionProfile profile,
final Catalog catalog) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "catalogChanged: " + catalog.getName());
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.catalogChanged(catalog);
}
}
});
}
public void closed(ConnectionProfile profile) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "closed");
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.repopulate();
}
}
});
}
public void columnChanged(ConnectionProfile profile,
final Column column) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "columnChanged: " + column.getName());
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.columnChanged(column);
}
}
});
}
public void databaseChanged(ConnectionProfile profile,
Database database) {
log(Tracing.UI_DB, "databaseChanged");
}
public void foreignKeyChanged(ConnectionProfile profile,
final ForeignKey foreignKey) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "foreignKeyChanged: " + foreignKey.getName());
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.foreignKeyChanged(foreignKey);
}
}
});
}
public void modified(ConnectionProfile profile) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "modified");
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.repopulate();
}
}
});
}
public boolean okToClose(ConnectionProfile profile) {
log(Tracing.UI_DB, "okToClose");
return true;
}
public void opened(ConnectionProfile profile) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "opened");
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.repopulate();
}
}
});
}
public void schemaChanged(ConnectionProfile profile,
final Schema schema) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "schemaChanged: " + schema.getName());
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.schemaChanged(schema);
}
}
});
}
public void sequenceChanged(ConnectionProfile profile,
final Sequence sequence) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "sequenceChanged: " + sequence.getName());
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.sequenceChanged(sequence);
}
}
});
}
public void tableChanged(ConnectionProfile profile,
final Table table) {
SWTUtil.asyncExec(new Runnable() {
public void run() {
log(Tracing.UI_DB, "tableChanged: " + table.getName());
if (!getCombo().isDisposed()) {
AbstractDatabaseObjectCombo.this.tableChanged(table);
}
}
});
}
};
}
private ModifyListener buildModifyListener() {
return new ModifyListener() {
public void modifyText(ModifyEvent e) {
if (!isPopulating()) {
CCombo combo = (CCombo) e.widget;
if (combo.getData("populating") != Boolean.TRUE) {//check !TRUE because null is a possibility as well
valueChanged(combo.getText());
}
}
}
};
}
/**
* If the value changes but the subject is null, then is invoked in order to
* create the subject.
*/
protected void buildSubject() {
}
/**
* The
*
* @param catalog
*/
protected void catalogChanged(Catalog catalog) {
}
/**
* Makes sure the combo shows nothing instead of the default value because
* the focus is still on the combo. The user can start typing something and
* we don't want to start the typing after the default value.
*/
private void clearDefaultValue() {
if (this.combo.isFocusControl()) {
setPopulating(true);
try {
combo.setText("");
}
finally {
setPopulating(false);
}
}
}
/**
* The
*
* @param column
*/
protected void columnChanged(Column column) {
}
/**
* Returns the JPA project's connection profile, which is never
* <code>null</code>.
*
* @return The connection set in the project's properties or <code>null</code>
* if it could not being retrieved
*/
protected final ConnectionProfile connectionProfile() {
JpaProject jpaProject = jpaProject();
if (jpaProject != null) {
return jpaProject.getConnectionProfile();
}
return null;
}
/**
* Returns the database associated with the active connection profile.
*
* @return The online database or a <code>null</code> instance if no
* connection profile was set or the
*/
protected final Database database() {
return connectionProfile().getDatabase();
}
/**
* Returns the default value, or <code>null</code> if no default is
* specified.
*
* @return The value that represents the default when no value was specified
*/
protected abstract String defaultValue();
/*
* (non-Javadoc)
*/
@Override
protected void disengageListeners(T subject) {
super.disengageListeners(subject);
JpaProject jpaProject = jpaProject();
if (jpaProject != null) {
jpaProject.getConnectionProfile().removeConnectionListener(this.connectionListener);
}
}
/*
* (non-Javadoc)
*/
@Override
protected void doPopulate() {
super.doPopulate();
populateCombo();
}
/*
* (non-Javadoc)
*/
@Override
public void enableWidgets(boolean enabled) {
super.enableWidgets(enabled);
if (!this.combo.isDisposed()) {
this.combo.setEnabled(enabled);
}
}
/*
* (non-Javadoc)
*/
@Override
protected void engageListeners(T subject) {
super.engageListeners(subject);
JpaProject jpaProject = jpaProject();
if (jpaProject != null) {
jpaProject.getConnectionProfile().addConnectionListener(this.connectionListener);
}
}
/**
* The
*
* @param foreignKey
*/
protected void foreignKeyChanged(ForeignKey foreignKey) {
}
public final CCombo getCombo() {
return this.combo;
}
/*
* (non-Javadoc)
*/
@Override
protected void initialize() {
super.initialize();
this.connectionListener = buildConnectionListener();
}
/*
* (non-Javadoc)
*/
@Override
protected void initializeLayout(Composite container) {
this.combo = buildEditableCCombo(container);
this.combo.addModifyListener(buildModifyListener());
SWTUtil.attachDefaultValueHandler(this.combo);
}
/**
* Determines if the subject should be created when the value changes.
*
* @return <code>false</code> is the default behavior
*/
protected boolean isBuildSubjectAllowed() {
return false;
}
/**
* Retrives the <code>IJpaProject</code> that is required to register a
* <code>ConnectionListener</code> in order to keep the combo in sync with
* the associated online database.
*
* @return The JPA project
*/
protected JpaProject jpaProject() {
return subject() == null ? null : subject().getJpaProject();
}
/*
* (non-Javadoc)
*/
@Override
protected void log(String flag, String message) {
super.log(flag, message);
if (Tracing.UI_DB.equals(flag) &&
Tracing.booleanDebugOption(Tracing.UI_DB))
{
Class<?> thisClass = getClass();
String className = ClassTools.shortNameFor(thisClass);
if (thisClass.isAnonymousClass()) {
className = className.substring(0, className.indexOf('$'));
className += "->" + ClassTools.shortNameFor(thisClass.getSuperclass());
}
Tracing.log(className + ": " + message);
}
}
/**
* Populates the combo's list by adding first the default value is available
* and then the possible choices.
*/
private void populateCombo() {
combo.removeAll();
populateDefaultValue();
ConnectionProfile connectionProfile = connectionProfile();
if ((connectionProfile != null) && connectionProfile.isActive()) {
for (Iterator<String> iter = CollectionTools.sort(values()); iter.hasNext(); ) {
combo.add(iter.next());
}
}
updateSelectedItem();
}
/**
* Adds the default value to the combo if one exists.
*/
private void populateDefaultValue() {
String defaultValue = (subject() != null) ? defaultValue() : null;
if (defaultValue != null) {
combo.add(NLS.bind(
JptUiMappingsMessages.ColumnComposite_defaultWithOneParam,
defaultValue
));
}
else {
combo.add(JptUiMappingsMessages.ColumnComposite_defaultEmpty);
}
}
/*
* (non-Javadoc)
*/
@Override
protected void propertyChanged(String propertyName) {
super.propertyChanged(propertyName);
updateSelectedItem();
}
/**
* The
*
* @param schema
*/
protected void schemaChanged(Schema schema) {
}
/**
* The
*
* @param sequence
*/
protected void sequenceChanged(Sequence sequence) {
}
/**
* Sets the given value as the new value.
*
* @param value The new value to send to the model object
*/
protected abstract void setValue(String value);
/**
* The
*
* @param table
*/
protected void tableChanged(Table table) {
}
/**
* Updates the selected item by selected the current value, if not
* <code>null</code>, or select the default value if one is available,
* otherwise remove the selection.
* <p>
* <b>Note:</b> It seems the text can be shown as truncated, changing the
* selection to (0, 0) makes the entire text visible.
*/
private void updateSelectedItem() {
T subject = subject();
String value = (subject != null) ? value() : null;
String defaultValue = (subject != null) ? defaultValue() : null;
String displayString = JptUiMappingsMessages.ColumnComposite_defaultEmpty;
if (defaultValue != null) {
displayString = NLS.bind(
JptUiMappingsMessages.ColumnComposite_defaultWithOneParam,
defaultValue
);
}
// Make sure the default value is up to date
if (!combo.getItem(0).equals(displayString)) {
combo.remove(0);
combo.add(displayString, 0);
}
// Select the new value
if (value != null) {
if (!value.equals(combo.getText())) {
//this prevents the cursor from being set back to the beginning of the line (bug 234418).
//The reason we are hitting this updateSelectedItem() code at all
//is because the context model is updating from the resource model
//in a way that causes change notifications to be fired (the annotation is added
//to the resource model, change notification occurs on the update thread,
//and then the name is set, these 2 threads can get in the wrong order).
//The valueChanged() method sets the populating flag to true, but in this case
//it is already set back to false when we receive notification back from the model
//because it has moved to the update thread and then jumps back on the UI thread.
combo.setText(value);
}
}
// Select the default value
else {
combo.select(0);
//i think we can remove this, I don't believe the problem explained
//in the comments of this method is happening anymore. Not removing it now because we are working on 2.0RC3
combo.setSelection(new Point(0, 0));
}
}
/**
* Requests the current value from the model object.
*
* @return The current value
*/
protected abstract String value();
/**
* The selection has changed, update the model if required.
*
* @param value The new value
*/
protected void valueChanged(String value) {
JpaNode subject = subject();
if ((subject == null) && !isBuildSubjectAllowed()) {
return;
}
String oldValue = (subject != null) ? value() : null;
// Check for null value
if (StringTools.stringIsEmpty(value)) {
value = null;
if (StringTools.stringIsEmpty(oldValue)) {
return;
}
}
// Convert the default value to null
if (value != null &&
getCombo().getItemCount() > 0 &&
value.equals(getCombo().getItem(0)))
{
value = null;
}
// Nothing to change
if ((oldValue == value) && value == null) {
clearDefaultValue();
return;
}
// Build the subject before setting the value
if (subject == null) {
buildSubject();
}
// Set the new value
if ((value != null) && (oldValue == null) ||
((oldValue != null) && !oldValue.equals(value))) {
setPopulating(true);
combo.setData("populating", Boolean.TRUE);
try {
setValue(value);
}
finally {
setPopulating(false);
combo.setData("populating", Boolean.FALSE);
}
if (value == null) {
clearDefaultValue();
}
}
}
/**
* Retrieves the possible values, which will be added to the combo during
* population.
*
* @return A non-<code>null</code> <code>Iterator</code> of the possible
* choices to be added to the combo
*/
protected abstract Iterator<String> values();
}