/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.ui.trans.step;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.SourceToTargetMapping;
import org.pentaho.di.core.database.DatabaseInterface;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LoggingObjectInterface;
import org.pentaho.di.core.logging.LoggingObjectType;
import org.pentaho.di.core.logging.SimpleLoggingObject;
import org.pentaho.di.core.plugins.PluginInterface;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.plugins.StepPluginType;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.core.variables.Variables;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.laf.BasePropertyHandler;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositoryElementMetaInterface;
import org.pentaho.di.shared.SharedObjects;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStepMeta;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.di.ui.core.ConstUI;
import org.pentaho.di.ui.core.PropsUI;
import org.pentaho.di.ui.core.database.dialog.DatabaseDialog;
import org.pentaho.di.ui.core.database.wizard.CreateDatabaseWizard;
import org.pentaho.di.ui.core.dialog.EnterMappingDialog;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.gui.GUIResource;
import org.pentaho.di.ui.core.gui.WindowProperty;
import org.pentaho.di.ui.core.widget.ComboVar;
import org.pentaho.di.ui.core.widget.TableView;
import org.pentaho.di.ui.util.DialogUtils;
import org.pentaho.di.ui.util.HelpUtils;
import org.pentaho.metastore.api.IMetaStore;
import com.google.common.annotations.VisibleForTesting;
/**
* This class provides functionality common to Step Dialogs.
*/
public class BaseStepDialog extends Dialog {
/**
* The package name used for internationalization
*/
private static Class<?> PKG = StepInterface.class; // for i18n purposes, needed by Translator2!!
/**
* The logging object interface for this dialog.
*/
public static final LoggingObjectInterface loggingObject = new SimpleLoggingObject(
"Step dialog", LoggingObjectType.STEPDIALOG, null );
/**
* The variable bindings for this dialog.
*/
protected static VariableSpace variables = new Variables();
/**
* The step name.
*/
protected String stepname;
/**
* The Step name label.
*/
protected Label wlStepname;
/**
* The Step name UI component.
*/
protected Text wStepname;
/**
* The FormData for the step name and its label.
*/
protected FormData fdlStepname, fdStepname;
/**
* Common dialog buttons.
*/
protected Button wOK, wGet, wPreview, wSQL, wCreate, wCancel;
/**
* FormData for the common dialog buttons.
*/
protected FormData fdOK, fdGet, fdPreview, fdSQL, fdCreate, fdCancel;
/**
* Listeners for the common dialog buttons.
*/
protected Listener lsOK, lsGet, lsPreview, lsSQL, lsCreate, lsCancel;
/**
* The metadata for the associated transformation.
*/
protected TransMeta transMeta;
/**
* A reference to the shell.
*/
protected Shell shell;
/**
* A listener adapter for default widget selection.
*/
protected SelectionAdapter lsDef;
/**
* A listener for dialog resizing.
*/
protected Listener lsResize;
/**
* Whether the dialog (and its backup) have changed.
*/
protected boolean changed, backupChanged;
/**
* The base step meta.
*/
protected StepMetaInterface baseStepMeta;
/**
* The UI properties.
*/
protected PropsUI props;
/**
* The associated repository.
*/
protected Repository repository;
/**
* The MetaStore to use
*/
protected IMetaStore metaStore;
/**
* The step meta for this dialog.
*/
protected StepMeta stepMeta;
/**
* The log channel for this dialog.
*/
protected LogChannel log;
/**
* A constant indicating a center button alignment.
*/
protected static final int BUTTON_ALIGNMENT_CENTER = 0;
/**
* A constant indicating a left button alignment.
*/
protected static final int BUTTON_ALIGNMENT_LEFT = 1;
/**
* A constant indicating a right button alignment.
*/
protected static final int BUTTON_ALIGNMENT_RIGHT = 2;
/**
* The button alignment (defaults to center).
*/
protected static int buttonAlignment = BUTTON_ALIGNMENT_CENTER;
/**
* A reference to a database dialog.
*/
protected DatabaseDialog databaseDialog;
static {
// Get the button alignment
buttonAlignment = getButtonAlignment();
}
/**
* Instantiates a new base step dialog.
*
* @param parent the parent shell
* @param baseStepMeta the associated base step metadata
* @param transMeta the associated transformation metadata
* @param stepname the step name
*/
public BaseStepDialog( Shell parent, BaseStepMeta baseStepMeta, TransMeta transMeta, String stepname ) {
super( parent, SWT.NONE );
this.log = new LogChannel( baseStepMeta );
this.transMeta = transMeta;
this.stepname = stepname;
this.stepMeta = transMeta.findStep( stepname );
this.baseStepMeta = (StepMetaInterface) baseStepMeta;
this.backupChanged = baseStepMeta.hasChanged();
this.props = PropsUI.getInstance();
}
/**
* Instantiates a new base step dialog.
*
* @param parent the parent shell
* @param baseStepMeta the associated base step metadata
* @param transMeta the associated transformation metadata
* @param stepname the step name
*/
public BaseStepDialog( Shell parent, StepMetaInterface baseStepMeta, TransMeta transMeta, String stepname ) {
super( parent, SWT.NONE );
this.log = new LogChannel( baseStepMeta );
this.transMeta = transMeta;
this.stepname = stepname;
this.stepMeta = transMeta.findStep( stepname );
this.baseStepMeta = baseStepMeta;
this.backupChanged = baseStepMeta.hasChanged();
this.props = PropsUI.getInstance();
}
/**
* Instantiates a new base step dialog.
*
* @param parent the parent shell
* @param nr the number of rows
* @param in the base step metadata
* @param tr the transformation metadata
*/
public BaseStepDialog( Shell parent, int nr, BaseStepMeta in, TransMeta tr ) {
this( parent, in, tr, null );
}
/**
* Sets the shell image.
*
* @param shell the shell
* @param stepMetaInterface the step meta interface (because of the legacy code)
*/
public void setShellImage( Shell shell, StepMetaInterface stepMetaInterface ) {
setShellImage( shell );
}
/**
* Dispose this dialog.
*/
public void dispose() {
WindowProperty winprop = new WindowProperty( shell );
props.setScreen( winprop );
shell.dispose();
}
/**
* Set the shell size, based upon the previous time the geometry was saved in the Properties file.
*/
public void setSize() {
setSize( shell );
}
/**
* Sets the button positions.
*
* @param buttons the buttons
* @param margin the margin between buttons
* @param lastControl the last control
*/
protected void setButtonPositions( Button[] buttons, int margin, Control lastControl ) {
BaseStepDialog.positionBottomButtons( shell, buttons, margin, lastControl );
}
/**
* Position the specified buttons at the bottom of the parent composite. Also, make the buttons all the same width:
* the width of the largest button.
* <p/>
* The default alignment for buttons in the system will be used. This is set as an LAF property with the key
* <code>Button_Position</code> and has the valid values of <code>left, center, right</code> with <code>center</code>
* being the default.
*
* @param composite the composite
* @param buttons The buttons to position.
* @param margin The margin between the buttons in pixels
* @param lastControl the last control
*/
public static final void positionBottomButtons( Composite composite, Button[] buttons, int margin,
Control lastControl ) {
// call positionBottomButtons method the system button alignment
positionBottomButtons( composite, buttons, margin, buttonAlignment, lastControl );
}
public static final void positionBottomRightButtons( Composite composite, Button[] buttons, int margin,
Control lastControl ) {
positionBottomButtons( composite, buttons, margin, BUTTON_ALIGNMENT_RIGHT, lastControl );
}
private static final void positionBottomButtons( Composite composite, Button[] buttons, int margin, int alignment,
Control lastControl ) {
// Determine the largest button in the array
Rectangle largest = null;
for ( int i = 0; i < buttons.length; i++ ) {
buttons[ i ].pack( true );
Rectangle r = buttons[ i ].getBounds();
if ( largest == null || r.width > largest.width ) {
largest = r;
}
// Also, set the tooltip the same as the name if we don't have one...
if ( buttons[ i ].getToolTipText() == null ) {
buttons[ i ].setToolTipText( Const.replace( buttons[ i ].getText(), "&", "" ) );
}
}
// Make buttons a bit larger... (nicer)
largest.width += 10;
if ( ( largest.width % 2 ) == 1 ) {
largest.width++;
}
// Compute the left side of the 1st button
switch ( alignment ) {
case BUTTON_ALIGNMENT_CENTER:
centerButtons( buttons, largest.width, margin, lastControl );
break;
case BUTTON_ALIGNMENT_LEFT:
leftAlignButtons( buttons, largest.width, margin, lastControl );
break;
case BUTTON_ALIGNMENT_RIGHT:
rightAlignButtons( buttons, largest.width, margin, lastControl );
break;
default:
break;
}
if ( Const.isOSX() ) {
Shell parentShell = composite.getShell();
final List<TableView> tableViews = new ArrayList<TableView>();
getTableViews( parentShell, tableViews );
for ( final Button button : buttons ) {
// We know the table views
// We also know that if a button is hit, the table loses focus
// In that case, we can apply the content of an open text editor...
//
button.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent e ) {
for ( TableView view : tableViews ) {
view.applyOSXChanges();
}
}
} );
}
}
}
/**
* Gets the table views.
*
* @param parentControl the parent control
* @param tableViews the table views
* @return the table views
*/
private static final void getTableViews( Control parentControl, List<TableView> tableViews ) {
if ( parentControl instanceof TableView ) {
tableViews.add( (TableView) parentControl );
} else {
if ( parentControl instanceof Composite ) {
Control[] children = ( (Composite) parentControl ).getChildren();
for ( Control child : children ) {
getTableViews( child, tableViews );
}
} else {
if ( parentControl instanceof Shell ) {
Control[] children = ( (Shell) parentControl ).getChildren();
for ( Control child : children ) {
getTableViews( child, tableViews );
}
}
}
}
}
/**
* Returns the default alignment for the buttons. This is set in the LAF properties with the key
* <code>Button_Position</code>. The valid values are: <UL> <LI><code>left</code> <LI><code>center</code>
* <LI><code>right</code> </UL> NOTE: if the alignment is not provided or contains an invalid value,
* <code>center</code> will be used as a default
*
* @return a constant which indicates the button alignment
*/
protected static int getButtonAlignment() {
String buttonAlign = BasePropertyHandler.getProperty( "Button_Position", "center" ).toLowerCase();
if ( "center".equals( buttonAlign ) ) {
return BUTTON_ALIGNMENT_CENTER;
} else if ( "left".equals( buttonAlign ) ) {
return BUTTON_ALIGNMENT_LEFT;
} else {
return BUTTON_ALIGNMENT_RIGHT;
}
}
/**
* Creats a default FormData object with the top / bottom / and left set (this is done to cut down on repetative code
* lines.
*
* @param button the button to which this form data will be applied
* @param width the width of the button
* @param margin the margin between buttons
* @param lastControl the last control above the buttons
* @return the newly created FormData object
*/
private static FormData createDefaultFormData( Button button, int width, int margin, Control lastControl ) {
FormData formData = new FormData();
if ( lastControl != null ) {
formData.top = new FormAttachment( lastControl, margin * 3 );
} else {
formData.bottom = new FormAttachment( 100, 0 );
}
formData.right = new FormAttachment( button, width + margin );
return formData;
}
/**
* Aligns the buttons as left-aligned on the dialog.
*
* @param buttons the array of buttons to align
* @param width the standardized width of all the buttons
* @param margin the margin between buttons
* @param lastControl (optional) the bottom most control used for aligning the buttons relative to the bottom of the
* controls on the dialog
*/
protected static void leftAlignButtons( Button[] buttons, int width, int margin, Control lastControl ) {
for ( int i = 0; i < buttons.length; ++i ) {
FormData formData = createDefaultFormData( buttons[ i ], width, margin, lastControl );
// Set the left side of the buttons (either offset from the edge, or relative to the previous button)
if ( i == 0 ) {
formData.left = new FormAttachment( 0, margin );
} else {
formData.left = new FormAttachment( buttons[ i - 1 ], margin );
}
// Apply the layout data
buttons[ i ].setLayoutData( formData );
}
}
/**
* Aligns the buttons as right-aligned on the dialog.
*
* @param buttons the array of buttons to align
* @param width the standardized width of all the buttons
* @param margin the margin between buttons
* @param lastControl (optional) the bottom most control used for aligning the buttons relative to the bottom of the
* controls on the dialog
*/
protected static void rightAlignButtons( Button[] buttons, int width, int margin, Control lastControl ) {
for ( int i = buttons.length - 1; i >= 0; --i ) {
FormData formData = createDefaultFormData( buttons[ i ], width, margin, lastControl );
// Set the right side of the buttons (either offset from the edge, or relative to the previous button)
if ( i == buttons.length - 1 ) {
formData.left = new FormAttachment( 100, -( width + margin ) );
} else {
formData.left = new FormAttachment( buttons[ i + 1 ], -( 2 * ( width + margin ) ) - margin );
}
// Apply the layout data
buttons[ i ].setLayoutData( formData );
}
}
/**
* Aligns the buttons as centered on the dialog.
*
* @param buttons the array of buttons to align
* @param width the standardized width of all the buttons
* @param margin the margin between buttons
* @param lastControl (optional) the bottom most control used for aligning the buttons relative to the bottom of the
* controls on the dialog
*/
protected static void centerButtons( Button[] buttons, int width, int margin, Control lastControl ) {
// Setup the middle button
int middleButtonIndex = buttons.length / 2;
FormData formData = createDefaultFormData( buttons[ middleButtonIndex ], width, margin, lastControl );
// See if we have an even or odd number of buttons...
int leftOffset = 0;
if ( buttons.length % 2 == 0 ) {
// Even number of buttons - the middle is between buttons. The "middle" button is
// actually to the right of middle
leftOffset = margin;
} else {
// Odd number of buttons - tht middle is in the middle of the button
leftOffset = -( width + margin ) / 2;
}
formData.left = new FormAttachment( 50, leftOffset );
buttons[ middleButtonIndex ].setLayoutData( formData );
// Do the buttons to the right of the middle
for ( int i = middleButtonIndex + 1; i < buttons.length; ++i ) {
formData = createDefaultFormData( buttons[ i ], width, margin, lastControl );
formData.left = new FormAttachment( buttons[ i - 1 ], margin );
buttons[ i ].setLayoutData( formData );
}
// Do the buttons to the left of the middle
for ( int i = middleButtonIndex - 1; i >= 0; --i ) {
formData = createDefaultFormData( buttons[ i ], width, margin, lastControl );
formData.left = new FormAttachment( buttons[ i + 1 ], -( 2 * ( width + margin ) ) - margin );
buttons[ i ].setLayoutData( formData );
}
}
/**
* Gets the modify listener tooltip text.
*
* @param textField the text field
* @return the modify listener tooltip text
*/
public static final ModifyListener getModifyListenerTooltipText( final Text textField ) {
return new ModifyListener() {
public void modifyText( ModifyEvent e ) {
// maybe replace this with extra arguments
textField.setToolTipText( variables.environmentSubstitute( textField.getText() ) );
}
};
}
/**
* Adds the databases to the Combo Box component.
*
* @param wConnection the Combo Box component
*/
public void addDatabases( CCombo wConnection ) {
addDatabases( wConnection, null );
}
/**
* Adds the databases with the specified type to the Combo Box component.
*
* @param wConnection the Combo Box component
* @param databaseType the database type
*/
public void addDatabases( CCombo wConnection, Class<? extends DatabaseInterface> databaseType ) {
for ( int i = 0; i < transMeta.nrDatabases(); i++ ) {
DatabaseMeta ci = transMeta.getDatabase( i );
if ( databaseType == null || ci.getDatabaseInterface().getClass().equals( databaseType ) ) {
wConnection.add( ci.getName() );
}
}
}
/**
* Selects the database with the specified name in the Combo Box component.
*
* @param wConnection the Combo Box component
* @param name the name of the database to select
*/
public void selectDatabase( CCombo wConnection, String name ) {
int idx = wConnection.indexOf( name );
if ( idx >= 0 ) {
wConnection.select( idx );
}
}
/**
* Adds the connection line.
*
* @param parent the parent UI component
* @param previous the previous UI component
* @param middle the middle
* @param margin the margin
* @return the the Combo Box component for the given parameters
*/
public CCombo addConnectionLine( Composite parent, Control previous, int middle, int margin ) {
return addConnectionLine( parent, previous, middle, margin, null );
}
/**
* Adds the connection line.
*
* @param parent the parent UI component
* @param previous the previous UI component
* @param middle the middle
* @param margin the margin
* @param databaseType the database type
* @return the Combo Box component for the given parameters
*/
public CCombo addConnectionLine( Composite parent, Control previous, int middle, int margin,
Class<? extends DatabaseInterface> databaseType ) {
return addConnectionLine( parent, previous, middle, margin, new Label( parent, SWT.RIGHT ), new Button(
parent, SWT.PUSH ), new Button( parent, SWT.PUSH ), new Button( parent, SWT.PUSH ), databaseType );
}
/**
* Adds the connection line.
*
* @param parent the parent UI component
* @param previous the previous UI component
* @param middle the middle
* @param margin the margin
* @param wlConnection the connection label
* @param wbnConnection the "new connection" button
* @param wbeConnection the "edit connection" button
* @return the Combo Box component for the given parameters
*/
public CCombo addConnectionLine( Composite parent, Control previous, int middle, int margin, final Label wlConnection,
final Button wbwConnection, final Button wbnConnection,
final Button wbeConnection ) {
return addConnectionLine(
parent, previous, middle, margin, wlConnection, wbwConnection, wbnConnection, wbeConnection, null );
}
/**
* Adds the connection line.
*
* @param parent the parent UI component
* @param previous the previous UI component
* @param middle the middle
* @param margin the margin
* @param wlConnection the connection label
* @param wbnConnection the "new connection" button
* @param wbeConnection the "edit connection" button
* @param databaseType the database type
* @return the Combo Box component for the given parameters
*/
public CCombo addConnectionLine( Composite parent, Control previous, int middle, int margin,
final Label wlConnection, final Button wbwConnection, final Button wbnConnection,
final Button wbeConnection, final Class<? extends DatabaseInterface> databaseType ) {
final CCombo wConnection;
final FormData fdlConnection, fdbConnection, fdeConnection, fdConnection, fdbwConnection;
wConnection = new CCombo( parent, SWT.BORDER | SWT.READ_ONLY );
props.setLook( wConnection );
addDatabases( wConnection );
wlConnection.setText( BaseMessages.getString( PKG, "BaseStepDialog.Connection.Label" ) );
props.setLook( wlConnection );
fdlConnection = new FormData();
fdlConnection.left = new FormAttachment( 0, 0 );
fdlConnection.right = new FormAttachment( middle, -margin );
if ( previous != null ) {
fdlConnection.top = new FormAttachment( previous, margin );
} else {
fdlConnection.top = new FormAttachment( 0, 0 );
}
wlConnection.setLayoutData( fdlConnection );
//
// Wizard button
//
wbwConnection.setText( BaseMessages.getString( PKG, "BaseStepDialog.WizardConnectionButton.Label" ) );
wbwConnection.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent e ) {
CreateDatabaseWizard cdw = new CreateDatabaseWizard();
DatabaseMeta newDBInfo = cdw.createAndRunDatabaseWizard( shell, props, transMeta.getDatabases() );
if ( newDBInfo != null ) {
transMeta.addDatabase( newDBInfo );
reinitConnectionDropDown( wConnection, newDBInfo.getName() );
}
}
} );
fdbwConnection = new FormData();
fdbwConnection.right = new FormAttachment( 100, 0 );
if ( previous != null ) {
fdbwConnection.top = new FormAttachment( previous, margin );
} else {
fdbwConnection.top = new FormAttachment( 0, 0 );
}
wbwConnection.setLayoutData( fdbwConnection );
//
// NEW button
//
wbnConnection.setText( BaseMessages.getString( PKG, "BaseStepDialog.NewConnectionButton.Label" ) );
wbnConnection.addSelectionListener( new AddConnectionListener( wConnection ) );
fdbConnection = new FormData();
fdbConnection.right = new FormAttachment( wbwConnection, -margin );
if ( previous != null ) {
fdbConnection.top = new FormAttachment( previous, margin );
} else {
fdbConnection.top = new FormAttachment( 0, 0 );
}
wbnConnection.setLayoutData( fdbConnection );
//
// Edit button
//
wbeConnection.setText( BaseMessages.getString( PKG, "BaseStepDialog.EditConnectionButton.Label" ) );
wbeConnection.addSelectionListener( new EditConnectionListener( wConnection ) );
fdeConnection = new FormData();
fdeConnection.right = new FormAttachment( wbnConnection, -margin );
if ( previous != null ) {
fdeConnection.top = new FormAttachment( previous, margin );
} else {
fdeConnection.top = new FormAttachment( 0, 0 );
}
wbeConnection.setLayoutData( fdeConnection );
//
// what's left of the line: combo box
//
fdConnection = new FormData();
fdConnection.left = new FormAttachment( middle, 0 );
if ( previous != null ) {
fdConnection.top = new FormAttachment( previous, margin );
} else {
fdConnection.top = new FormAttachment( 0, 0 );
}
fdConnection.right = new FormAttachment( wbeConnection, -margin );
wConnection.setLayoutData( fdConnection );
return wConnection;
}
@VisibleForTesting
String showDbDialogUnlessCancelledOrValid( DatabaseMeta changing, DatabaseMeta origin ) {
changing.shareVariablesWith( transMeta );
DatabaseDialog cid = getDatabaseDialog( shell );
cid.setDatabaseMeta( changing );
cid.setModalDialog( true );
String name = null;
boolean repeat = true;
while ( repeat ) {
name = cid.open();
if ( name == null ) {
// Cancel was pressed
repeat = false;
} else {
name = name.trim();
DatabaseMeta same = transMeta.findDatabase( name );
if ( same == null || same == origin ) {
// OK was pressed and input is valid
repeat = false;
} else {
showDbExistsDialog( changing );
}
}
}
return name;
}
@VisibleForTesting
void showDbExistsDialog( DatabaseMeta changing ) {
DatabaseDialog.showDatabaseExistsDialog( shell, changing );
}
private void reinitConnectionDropDown( CCombo dropDown, String selected ) {
dropDown.removeAll();
addDatabases( dropDown );
selectDatabase( dropDown, selected );
}
/**
* Gets the database dialog.
*
* @param shell the shell
* @return the database dialog
*/
protected DatabaseDialog getDatabaseDialog( Shell shell ) {
if ( databaseDialog == null ) {
databaseDialog = new DatabaseDialog( shell );
}
return databaseDialog;
}
/**
* Store screen size.
*/
public void storeScreenSize() {
props.setScreen( new WindowProperty( shell ) );
}
public String toString() {
return this.getClass().getName();
}
/**
* Gets the repository associated with this dialog.
*
* @return Returns the repository.
*/
public Repository getRepository() {
return repository;
}
/**
* Sets the repository associated with this dialog.
*
* @param repository The repository to set.
*/
public void setRepository( Repository repository ) {
this.repository = repository;
}
/**
* Sets the minimal shell height.
*
* @param shell the shell
* @param controls the controls to measure
* @param margin the margin between the components
* @param extra the extra padding
*/
public static void setMinimalShellHeight( Shell shell, Control[] controls, int margin, int extra ) {
int height = 0;
for ( int i = 0; i < controls.length; i++ ) {
Rectangle bounds = controls[ i ].getBounds();
height += bounds.height + margin;
}
height += extra;
shell.setSize( shell.getBounds().width, height );
}
/**
* Sets the size of this dialog with respect to the given shell.
*
* @param shell the new size
*/
public static void setSize( Shell shell ) {
setSize( shell, -1, -1, true );
}
public static void setSize( Shell shell, int prefWidth, int prefHeight ) {
PropsUI props = PropsUI.getInstance();
WindowProperty winprop = props.getScreen( shell.getText() );
if ( winprop != null ) {
winprop.setShell( shell, prefWidth, prefHeight );
} else {
shell.layout();
winprop = new WindowProperty( shell.getText(), false, new Rectangle( 0, 0, prefWidth, prefHeight ) );
winprop.setShell( shell );
// Now, as this is the first time it gets opened, try to put it in the middle of the screen...
Rectangle shellBounds = shell.getBounds();
Monitor monitor = shell.getDisplay().getPrimaryMonitor();
if ( shell.getParent() != null ) {
monitor = shell.getParent().getMonitor();
}
Rectangle monitorClientArea = monitor.getClientArea();
int middleX = monitorClientArea.x + ( monitorClientArea.width - shellBounds.width ) / 2;
int middleY = monitorClientArea.y + ( monitorClientArea.height - shellBounds.height ) / 2;
shell.setLocation( middleX, middleY );
}
}
/**
* Sets the size of this dialog with respect to the given parameters.
*
* @param shell the shell
* @param minWidth the minimum width
* @param minHeight the minimum height
* @param packIt true to pack the dialog components, false otherwise
*/
public static void setSize( Shell shell, int minWidth, int minHeight, boolean packIt ) {
PropsUI props = PropsUI.getInstance();
WindowProperty winprop = props.getScreen( shell.getText() );
if ( winprop != null ) {
winprop.setShell( shell, minWidth, minHeight );
} else {
if ( packIt ) {
shell.pack();
} else {
shell.layout();
}
// OK, sometimes this produces dialogs that are waay too big.
// Try to limit this a bit, m'kay?
// Use the same algorithm by cheating :-)
//
winprop = new WindowProperty( shell );
winprop.setShell( shell, minWidth, minHeight );
// Now, as this is the first time it gets opened, try to put it in the middle of the screen...
Rectangle shellBounds = shell.getBounds();
Monitor monitor = shell.getDisplay().getPrimaryMonitor();
if ( shell.getParent() != null ) {
monitor = shell.getParent().getMonitor();
}
Rectangle monitorClientArea = monitor.getClientArea();
int middleX = monitorClientArea.x + ( monitorClientArea.width - shellBounds.width ) / 2;
int middleY = monitorClientArea.y + ( monitorClientArea.height - shellBounds.height ) / 2;
shell.setLocation( middleX, middleY );
}
}
/**
* Sets the traverse order for the given controls.
*
* @param controls the new traverse order
*/
public static final void setTraverseOrder( final Control[] controls ) {
for ( int i = 0; i < controls.length; i++ ) {
final int controlNr = i;
if ( i < controls.length - 1 ) {
controls[ i ].addTraverseListener( new TraverseListener() {
public void keyTraversed( TraverseEvent te ) {
te.doit = false;
// set focus on the next control.
// What is the next control?
int thisOne = controlNr + 1;
while ( !controls[ thisOne ].isEnabled() ) {
thisOne++;
if ( thisOne >= controls.length ) {
thisOne = 0;
}
if ( thisOne == controlNr ) {
return; // already tried all others, time to quit.
}
}
controls[ thisOne ].setFocus();
}
} );
} else { // Link last item to first.
controls[ i ].addTraverseListener( new TraverseListener() {
public void keyTraversed( TraverseEvent te ) {
te.doit = false;
// set focus on the next control.
// set focus on the next control.
// What is the next control : 0
int thisOne = 0;
while ( !controls[ thisOne ].isEnabled() ) {
thisOne++;
if ( thisOne >= controls.length ) {
return; // already tried all others, time to quit.
}
}
controls[ thisOne ].setFocus();
}
} );
}
}
}
/**
* Gets unused fields from previous steps and inserts them as rows into a table view.
*
* @param transMeta the transformation metadata
* @param stepMeta the step metadata
* @param tableView the table view
* @param keyColumn the key column
* @param nameColumn the name column
* @param dataTypeColumn the data type column
* @param lengthColumn the length column
* @param precisionColumn the precision column
* @param listener a listener for tables insert events
*/
public static final void getFieldsFromPrevious( TransMeta transMeta, StepMeta stepMeta, TableView tableView,
int keyColumn, int[] nameColumn, int[] dataTypeColumn,
int lengthColumn, int precisionColumn,
TableItemInsertListener listener ) {
try {
RowMetaInterface row = transMeta.getPrevStepFields( stepMeta );
if ( row != null ) {
getFieldsFromPrevious(
row, tableView, keyColumn, nameColumn, dataTypeColumn, lengthColumn, precisionColumn, listener );
}
} catch ( KettleException ke ) {
new ErrorDialog( tableView.getShell(),
BaseMessages.getString( PKG, "BaseStepDialog.FailedToGetFields.Title" ),
BaseMessages.getString( PKG, "BaseStepDialog.FailedToGetFields.Message", stepMeta.getName() ), ke );
}
}
/**
* Gets unused fields from previous steps and inserts them as rows into a table view.
*
* @param row the input fields
* @param tableView the table view to modify
* @param keyColumn the column in the table view to match with the names of the fields, checks for existance if
* >0
* @param nameColumn the column numbers in which the name should end up in
* @param dataTypeColumn the target column numbers in which the data type should end up in
* @param lengthColumn the length column where the length should end up in (if >0)
* @param precisionColumn the length column where the precision should end up in (if >0)
* @param listener A listener that you can use to do custom modifications to the inserted table item, based on
* a value from the provided row
*/
public static final void getFieldsFromPrevious( RowMetaInterface row, TableView tableView, int keyColumn,
int[] nameColumn, int[] dataTypeColumn, int lengthColumn,
int precisionColumn,
TableItemInsertListener listener ) {
if ( row == null || row.size() == 0 ) {
return; // nothing to do
}
Table table = tableView.table;
// get a list of all the non-empty keys (names)
//
List<String> keys = new ArrayList<String>();
for ( int i = 0; i < table.getItemCount(); i++ ) {
TableItem tableItem = table.getItem( i );
String key = tableItem.getText( keyColumn );
if ( !Utils.isEmpty( key ) && keys.indexOf( key ) < 0 ) {
keys.add( key );
}
}
int choice = 0;
if ( keys.size() > 0 ) {
// Ask what we should do with the existing data in the step.
//
MessageDialog md =
new MessageDialog( tableView.getShell(),
BaseMessages.getString( PKG, "BaseStepDialog.GetFieldsChoice.Title" ), // "Warning!"
null,
BaseMessages.getString( PKG, "BaseStepDialog.GetFieldsChoice.Message", "" + keys.size(), "" + row.size() ),
MessageDialog.WARNING, new String[] {
BaseMessages.getString( PKG, "BaseStepDialog.AddNew" ),
BaseMessages.getString( PKG, "BaseStepDialog.Add" ),
BaseMessages.getString( PKG, "BaseStepDialog.ClearAndAdd" ),
BaseMessages.getString( PKG, "BaseStepDialog.Cancel" ), }, 0 );
MessageDialog.setDefaultImage( GUIResource.getInstance().getImageSpoon() );
int idx = md.open();
choice = idx & 0xFF;
}
if ( choice == 3 || choice == 255 ) {
return; // Cancel clicked
}
if ( choice == 2 ) {
tableView.clearAll( false );
}
for ( int i = 0; i < row.size(); i++ ) {
ValueMetaInterface v = row.getValueMeta( i );
boolean add = true;
if ( choice == 0 ) { // hang on, see if it's not yet in the table view
if ( keys.indexOf( v.getName() ) >= 0 ) {
add = false;
}
}
if ( add ) {
TableItem tableItem = new TableItem( table, SWT.NONE );
for ( int c = 0; c < nameColumn.length; c++ ) {
tableItem.setText( nameColumn[ c ], Const.NVL( v.getName(), "" ) );
}
if ( dataTypeColumn != null ) {
for ( int c = 0; c < dataTypeColumn.length; c++ ) {
tableItem.setText( dataTypeColumn[ c ], v.getTypeDesc() );
}
}
if ( lengthColumn > 0 ) {
if ( v.getLength() >= 0 ) {
tableItem.setText( lengthColumn, Integer.toString( v.getLength() ) );
}
}
if ( precisionColumn > 0 ) {
if ( v.getPrecision() >= 0 ) {
tableItem.setText( precisionColumn, Integer.toString( v.getPrecision() ) );
}
}
if ( listener != null ) {
if ( !listener.tableItemInserted( tableItem, v ) ) {
tableItem.dispose(); // remove it again
}
}
}
}
tableView.removeEmptyRows();
tableView.setRowNums();
tableView.optWidth( true );
}
/**
* Gets fields from previous steps and populate a ComboVar.
*
* @param comboVar the Combo Box (with Variables) to populate
* @param transMeta the transformation metadata
* @param stepMeta the step metadata
*/
public static final void getFieldsFromPrevious( ComboVar comboVar, TransMeta transMeta, StepMeta stepMeta ) {
String selectedField = null;
int indexField = -1;
try {
RowMetaInterface r = transMeta.getPrevStepFields( stepMeta );
selectedField = comboVar.getText();
comboVar.removeAll();
if ( r != null && !r.isEmpty() ) {
r.getFieldNames();
comboVar.setItems( r.getFieldNames() );
indexField = r.indexOfValue( selectedField );
}
// Select value if possible...
if ( indexField > -1 ) {
comboVar.select( indexField );
} else {
if ( selectedField != null ) {
comboVar.setText( selectedField );
}
}
} catch ( KettleException ke ) {
new ErrorDialog( comboVar.getShell(),
BaseMessages.getString( PKG, "BaseStepDialog.FailedToGetFieldsPrevious.DialogTitle" ),
BaseMessages.getString( PKG, "BaseStepDialog.FailedToGetFieldsPrevious.DialogMessage" ), ke );
}
}
/**
* Create a new field mapping between source and target steps.
*
* @param shell the shell of the parent window
* @param sourceFields the source fields
* @param targetFields the target fields
* @param fieldMapping the list of source to target mappings to default to (can be empty but not null)
* @throws KettleException in case something goes wrong during the field mapping
*/
public static final void generateFieldMapping( Shell shell, RowMetaInterface sourceFields,
RowMetaInterface targetFields,
List<SourceToTargetMapping> fieldMapping ) throws KettleException {
// Build the mapping: let the user decide!!
String[] source = sourceFields.getFieldNames();
for ( int i = 0; i < source.length; i++ ) {
ValueMetaInterface v = sourceFields.getValueMeta( i );
source[ i ] += EnterMappingDialog.STRING_ORIGIN_SEPARATOR + v.getOrigin() + ")";
}
String[] target = targetFields.getFieldNames();
EnterMappingDialog dialog = new EnterMappingDialog( shell, source, target, fieldMapping );
List<SourceToTargetMapping> newMapping = dialog.open();
if ( newMapping != null ) {
fieldMapping.clear();
fieldMapping.addAll( newMapping );
}
}
/**
* Checks if the log level is basic.
*
* @return true, if the log level is basic, false otherwise
*/
public boolean isBasic() {
return log.isBasic();
}
/**
* Checks if the log level is detailed.
*
* @return true, if the log level is detailed, false otherwise
*/
public boolean isDetailed() {
return log.isDetailed();
}
/**
* Checks if the log level is debug.
*
* @return true, if the log level is debug, false otherwise
*/
public boolean isDebug() {
return log.isDebug();
}
/**
* Checks if the log level is row level.
*
* @return true, if the log level is row level, false otherwise
*/
public boolean isRowLevel() {
return log.isRowLevel();
}
/**
* Log the message at a minimal logging level.
*
* @param message the message to log
*/
public void logMinimal( String message ) {
log.logMinimal( message );
}
/**
* Log the message with arguments at a minimal logging level.
*
* @param message the message
* @param arguments the arguments
*/
public void logMinimal( String message, Object... arguments ) {
log.logMinimal( message, arguments );
}
/**
* Log the message at a basic logging level.
*
* @param message the message
*/
public void logBasic( String message ) {
log.logBasic( message );
}
/**
* Log the message with arguments at a basic logging level.
*
* @param message the message
* @param arguments the arguments
*/
public void logBasic( String message, Object... arguments ) {
log.logBasic( message, arguments );
}
/**
* Log the message at a detailed logging level.
*
* @param message the message
*/
public void logDetailed( String message ) {
log.logDetailed( message );
}
/**
* Log the message with arguments at a detailed logging level.
*
* @param message the message
* @param arguments the arguments
*/
public void logDetailed( String message, Object... arguments ) {
log.logDetailed( message, arguments );
}
/**
* Log the message at a debug logging level.
*
* @param message the message
*/
public void logDebug( String message ) {
log.logDebug( message );
}
/**
* Log the message with arguments at a debug logging level.
*
* @param message the message
* @param arguments the arguments
*/
public void logDebug( String message, Object... arguments ) {
log.logDebug( message, arguments );
}
/**
* Log the message at a rowlevel logging level.
*
* @param message the message
*/
public void logRowlevel( String message ) {
log.logRowlevel( message );
}
/**
* Log the message with arguments at a rowlevel logging level.
*
* @param message the message
* @param arguments the arguments
*/
public void logRowlevel( String message, Object... arguments ) {
log.logRowlevel( message, arguments );
}
/**
* Log the message at a error logging level.
*
* @param message the message
*/
public void logError( String message ) {
log.logError( message );
}
/**
* Log the message with the associated Throwable object at a error logging level.
*
* @param message the message
* @param e the e
*/
public void logError( String message, Throwable e ) {
log.logError( message, e );
}
/**
* Log the message with arguments at a error logging level.
*
* @param message the message
* @param arguments the arguments
*/
public void logError( String message, Object... arguments ) {
log.logError( message, arguments );
}
protected Button createHelpButton( final Shell shell, final StepMeta stepMeta, final PluginInterface plugin ) {
return HelpUtils.createHelpButton( shell, HelpUtils.getHelpDialogTitle( plugin ), plugin );
}
private void setShellImage( Shell shell ) {
if ( stepMeta != null ) {
PluginInterface plugin =
PluginRegistry.getInstance().getPlugin( StepPluginType.class, stepMeta.getStepMetaInterface() );
createHelpButton( shell, stepMeta, plugin );
String id = plugin.getIds()[ 0 ];
if ( id != null ) {
shell.setImage( GUIResource.getInstance().getImagesSteps().get( id ).getAsBitmapForSize(
shell.getDisplay(), ConstUI.ICON_SIZE, ConstUI.ICON_SIZE ) );
}
}
}
public IMetaStore getMetaStore() {
return metaStore;
}
public void setMetaStore( IMetaStore metaStore ) {
this.metaStore = metaStore;
}
protected String getPathOf( RepositoryElementMetaInterface object ) {
return DialogUtils.getPathOf( object );
}
@VisibleForTesting
class AddConnectionListener extends SelectionAdapter {
private final CCombo wConnection;
public AddConnectionListener( CCombo wConnection ) {
this.wConnection = wConnection;
}
@Override
public void widgetSelected( SelectionEvent e ) {
DatabaseMeta databaseMeta = new DatabaseMeta();
String connectionName = showDbDialogUnlessCancelledOrValid( databaseMeta, null );
if ( connectionName != null ) {
transMeta.addDatabase( databaseMeta );
reinitConnectionDropDown( wConnection, databaseMeta.getName() );
}
}
}
@VisibleForTesting
class EditConnectionListener extends SelectionAdapter {
private final CCombo wConnection;
public EditConnectionListener( CCombo wConnection ) {
this.wConnection = wConnection;
}
public void widgetSelected( SelectionEvent e ) {
DatabaseMeta databaseMeta = transMeta.findDatabase( wConnection.getText() );
if ( databaseMeta != null ) {
// cloning to avoid spoiling data on cancel or incorrect input
DatabaseMeta clone = (DatabaseMeta) databaseMeta.clone();
// setting old Id, so a repository (if it used) could find and replace the existing connection
clone.setObjectId( databaseMeta.getObjectId() );
String connectionName = showDbDialogUnlessCancelledOrValid( clone, databaseMeta );
if ( connectionName != null ) {
// need to replace the old connection with a new one
if ( databaseMeta.isShared() ) {
if ( !replaceSharedConnection( databaseMeta, clone ) ) {
return;
}
}
transMeta.removeDatabase( transMeta.indexOfDatabase( databaseMeta ) );
transMeta.addDatabase( clone );
reinitConnectionDropDown( wConnection, connectionName );
}
}
}
boolean replaceSharedConnection( DatabaseMeta dbConnection, DatabaseMeta newDbConnection ) {
try {
SharedObjects sharedObjects = transMeta.getSharedObjects();
sharedObjects.removeObject( dbConnection );
sharedObjects.storeObject( newDbConnection );
sharedObjects.saveToFile();
return true;
} catch ( Exception e ) {
showErrorDialog( e );
return false;
}
}
void showErrorDialog( Exception e ) {
new ErrorDialog( wConnection.getShell(), BaseMessages.getString( PKG,
"BaseStep.Exception.UnexpectedErrorEditingConnection.DialogTitle" ), BaseMessages.getString( PKG,
"BaseStep.Exception.UnexpectedErrorEditingConnection.DialogMessage" ), e );
}
}
}