Java tutorial
//CHECKSTYLE:FileLength:OFF /*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2019 by Hitachi Vantara : 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.spoon.trans; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.MessageDialogWithToggle; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.window.DefaultToolTip; import org.eclipse.jface.window.ToolTip; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; import org.pentaho.di.core.CheckResultInterface; import org.pentaho.di.core.Const; import org.pentaho.di.core.EngineMetaInterface; import org.pentaho.di.core.NotePadMeta; import org.pentaho.di.core.Props; import org.pentaho.di.core.SwtUniversalImage; import org.pentaho.di.core.dnd.DragAndDropContainer; import org.pentaho.di.core.dnd.XMLTransfer; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.exception.KettleStepException; import org.pentaho.di.core.exception.KettleValueException; import org.pentaho.di.core.extension.ExtensionPointHandler; import org.pentaho.di.core.extension.KettleExtensionPoint; import org.pentaho.di.core.gui.AreaOwner; import org.pentaho.di.core.gui.AreaOwner.AreaType; import org.pentaho.di.core.gui.BasePainter; import org.pentaho.di.core.gui.GCInterface; import org.pentaho.di.core.gui.Point; import org.pentaho.di.core.gui.Redrawable; import org.pentaho.di.core.gui.SnapAllignDistribute; import org.pentaho.di.core.logging.DefaultLogLevel; import org.pentaho.di.core.logging.HasLogChannelInterface; import org.pentaho.di.core.logging.KettleLogStore; import org.pentaho.di.core.logging.KettleLoggingEvent; import org.pentaho.di.core.logging.LogChannel; import org.pentaho.di.core.logging.LogChannelInterface; import org.pentaho.di.core.logging.LogMessage; import org.pentaho.di.core.logging.LogParentProvidedInterface; import org.pentaho.di.core.logging.LoggingObjectInterface; import org.pentaho.di.core.logging.LoggingObjectType; import org.pentaho.di.core.logging.LoggingRegistry; 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.util.Utils; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.job.Job; import org.pentaho.di.job.JobMeta; import org.pentaho.di.lineage.TransDataLineage; import org.pentaho.di.repository.KettleRepositoryLostException; import org.pentaho.di.repository.Repository; import org.pentaho.di.repository.RepositoryObjectType; import org.pentaho.di.repository.RepositoryOperation; import org.pentaho.di.shared.SharedObjects; import org.pentaho.di.trans.DatabaseImpact; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransExecutionConfiguration; import org.pentaho.di.trans.TransHopMeta; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.TransPainter; import org.pentaho.di.trans.TransSupplier; import org.pentaho.di.trans.debug.BreakPointListener; import org.pentaho.di.trans.debug.StepDebugMeta; import org.pentaho.di.trans.debug.TransDebugMeta; import org.pentaho.di.trans.debug.TransDebugMetaWrapper; import org.pentaho.di.trans.step.RemoteStep; import org.pentaho.di.trans.step.RowDistributionInterface; import org.pentaho.di.trans.step.RowDistributionPluginType; import org.pentaho.di.trans.step.RowListener; import org.pentaho.di.trans.step.StepErrorMeta; import org.pentaho.di.trans.step.StepIOMetaInterface; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaDataCombi; import org.pentaho.di.trans.step.StepMetaInterface; import org.pentaho.di.trans.step.errorhandling.Stream; import org.pentaho.di.trans.step.errorhandling.StreamIcon; import org.pentaho.di.trans.step.errorhandling.StreamInterface; import org.pentaho.di.trans.step.errorhandling.StreamInterface.StreamType; import org.pentaho.di.trans.steps.tableinput.TableInputMeta; import org.pentaho.di.ui.core.ConstUI; import org.pentaho.di.ui.core.PropsUI; import org.pentaho.di.ui.core.dialog.DialogClosedListener; import org.pentaho.di.ui.core.dialog.EnterSelectionDialog; import org.pentaho.di.ui.core.dialog.EnterStringDialog; import org.pentaho.di.ui.core.dialog.EnterTextDialog; import org.pentaho.di.ui.core.dialog.ErrorDialog; import org.pentaho.di.ui.core.dialog.PreviewRowsDialog; import org.pentaho.di.ui.core.dialog.StepFieldsDialog; import org.pentaho.di.ui.core.gui.GUIResource; import org.pentaho.di.ui.core.widget.CheckBoxToolTip; import org.pentaho.di.ui.core.widget.CheckBoxToolTipListener; import org.pentaho.di.ui.repository.RepositorySecurityUI; import org.pentaho.di.ui.repository.dialog.RepositoryExplorerDialog; import org.pentaho.di.ui.repository.dialog.RepositoryRevisionBrowserDialogInterface; import org.pentaho.di.ui.spoon.AbstractGraph; import org.pentaho.di.ui.spoon.SWTGC; import org.pentaho.di.ui.spoon.Spoon; import org.pentaho.di.ui.spoon.SpoonPluginManager; import org.pentaho.di.ui.spoon.SpoonUiExtenderPluginInterface; import org.pentaho.di.ui.spoon.SpoonUiExtenderPluginType; import org.pentaho.di.ui.spoon.SwtScrollBar; import org.pentaho.di.ui.spoon.TabItemInterface; import org.pentaho.di.ui.spoon.XulSpoonResourceBundle; import org.pentaho.di.ui.spoon.XulSpoonSettingsManager; import org.pentaho.di.ui.spoon.dialog.EnterPreviewRowsDialog; import org.pentaho.di.ui.spoon.dialog.NotePadDialog; import org.pentaho.di.ui.spoon.dialog.SearchFieldsProgressDialog; import org.pentaho.di.ui.spoon.job.JobGraph; import org.pentaho.di.ui.trans.dialog.TransDialog; import org.pentaho.di.ui.xul.KettleXulLoader; import org.pentaho.ui.xul.XulDomContainer; import org.pentaho.ui.xul.XulException; import org.pentaho.ui.xul.components.XulMenuitem; import org.pentaho.ui.xul.components.XulToolbarbutton; import org.pentaho.ui.xul.containers.XulMenu; import org.pentaho.ui.xul.containers.XulMenupopup; import org.pentaho.ui.xul.containers.XulToolbar; import org.pentaho.ui.xul.dom.Document; import org.pentaho.ui.xul.impl.XulEventHandler; import org.pentaho.ui.xul.jface.tags.JfaceMenuitem; import org.pentaho.ui.xul.jface.tags.JfaceMenupopup; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.concurrent.Callable; /** * This class handles the display of the transformations in a graphical way using icons, arrows, etc. One transformation * is handled per TransGraph * * @author Matt * @since 17-mei-2003 */ public class TransGraph extends AbstractGraph implements XulEventHandler, Redrawable, TabItemInterface, LogParentProvidedInterface, MouseListener, MouseMoveListener, MouseTrackListener, MouseWheelListener, KeyListener { private static Class<?> PKG = Spoon.class; // for i18n purposes, needed by Translator2!! private LogChannelInterface log; private static final int HOP_SEL_MARGIN = 9; private static final String XUL_FILE_TRANS_TOOLBAR = "ui/trans-toolbar.xul"; public static final String LOAD_TAB = "loadTab"; public static final String PREVIEW_TRANS = "previewTrans"; public static final String START_TEXT = BaseMessages.getString(PKG, "TransLog.Button.StartTransformation"); public static final String PAUSE_TEXT = BaseMessages.getString(PKG, "TransLog.Button.PauseTransformation"); public static final String RESUME_TEXT = BaseMessages.getString(PKG, "TransLog.Button.ResumeTransformation"); public static final String STOP_TEXT = BaseMessages.getString(PKG, "TransLog.Button.StopTransformation"); public static final String TRANS_GRAPH_ENTRY_SNIFF = "trans-graph-entry-sniff"; public static final String TRANS_GRAPH_ENTRY_AGAIN = "trans-graph-entry-align"; private static final int TOOLTIP_HIDE_DELAY_SHORT = 5000; private static final int TOOLTIP_HIDE_DELAY_LONG = 10000; private TransMeta transMeta; public Trans trans; private Shell shell; private Composite mainComposite; private DefaultToolTip toolTip; private CheckBoxToolTip helpTip; private XulToolbar toolbar; private int iconsize; private Point lastclick; private Point lastMove; private Point[] previous_step_locations; private Point[] previous_note_locations; private List<StepMeta> selectedSteps; private StepMeta selectedStep; private List<StepMeta> mouseOverSteps; private List<NotePadMeta> selectedNotes; private NotePadMeta selectedNote; private TransHopMeta candidate; private Point drop_candidate; private Spoon spoon; // public boolean shift, control; private boolean split_hop; private int lastButton; private TransHopMeta last_hop_split; private org.pentaho.di.core.gui.Rectangle selectionRegion; /** * A list of remarks on the current Transformation... */ private List<CheckResultInterface> remarks; /** * A list of impacts of the current transformation on the used databases. */ private List<DatabaseImpact> impact; /** * Indicates whether or not an impact analysis has already run. */ private boolean impactFinished; private TransDebugMeta lastTransDebugMeta; private Map<String, XulMenupopup> menuMap = new HashMap<>(); protected int currentMouseX = 0; protected int currentMouseY = 0; protected NotePadMeta ni = null; protected TransHopMeta currentHop; protected StepMeta currentStep; private List<AreaOwner> areaOwners; // private Text filenameLabel; private SashForm sashForm; public Composite extraViewComposite; public CTabFolder extraViewTabFolder; private boolean initialized; private boolean running; private boolean halted; private boolean halting; private boolean safeStopping; private boolean debug; private boolean pausing; public TransLogDelegate transLogDelegate; public TransGridDelegate transGridDelegate; public TransHistoryDelegate transHistoryDelegate; public TransPerfDelegate transPerfDelegate; public TransMetricsDelegate transMetricsDelegate; public TransPreviewDelegate transPreviewDelegate; public List<SelectedStepListener> stepListeners; public List<StepSelectionListener> currentStepListeners = new ArrayList<>(); /** * A map that keeps track of which log line was written by which step */ private Map<StepMeta, String> stepLogMap; private StepMeta startHopStep; private Point endHopLocation; private boolean startErrorHopStep; private StepMeta noInputStep; private StepMeta endHopStep; private StreamType candidateHopType; private Map<StepMeta, DelayTimer> delayTimers; private StepMeta showTargetStreamsStep; Timer redrawTimer; private ToolItem stopItem; public void setCurrentNote(NotePadMeta ni) { this.ni = ni; } public NotePadMeta getCurrentNote() { return ni; } public TransHopMeta getCurrentHop() { return currentHop; } public void setCurrentHop(TransHopMeta currentHop) { this.currentHop = currentHop; } public StepMeta getCurrentStep() { return currentStep; } public void setCurrentStep(StepMeta currentStep) { this.currentStep = currentStep; } public void addSelectedStepListener(SelectedStepListener selectedStepListener) { stepListeners.add(selectedStepListener); } public void addCurrentStepListener(StepSelectionListener stepSelectionListener) { currentStepListeners.add(stepSelectionListener); } public TransGraph(Composite parent, final Spoon spoon, final TransMeta transMeta) { super(parent, SWT.NONE); this.shell = parent.getShell(); this.spoon = spoon; this.transMeta = transMeta; this.areaOwners = new ArrayList<>(); this.log = spoon.getLog(); spoon.clearSearchFilter(); this.mouseOverSteps = new ArrayList<>(); this.delayTimers = new HashMap<>(); transLogDelegate = new TransLogDelegate(spoon, this); transGridDelegate = new TransGridDelegate(spoon, this); transHistoryDelegate = new TransHistoryDelegate(spoon, this); transPerfDelegate = new TransPerfDelegate(spoon, this); transMetricsDelegate = new TransMetricsDelegate(spoon, this); transPreviewDelegate = new TransPreviewDelegate(spoon, this); stepListeners = new ArrayList<>(); try { KettleXulLoader loader = new KettleXulLoader(); loader.setIconsSize(16, 16); loader.setSettingsManager(XulSpoonSettingsManager.getInstance()); ResourceBundle bundle = new XulSpoonResourceBundle(Spoon.class); XulDomContainer container = loader.loadXul(XUL_FILE_TRANS_TOOLBAR, bundle); container.addEventHandler(this); SpoonPluginManager.getInstance().applyPluginsForContainer("trans-graph", xulDomContainer); setXulDomContainer(container); } catch (XulException e1) { log.logError("Error loading XUL resource bundle for Spoon", e1); } setLayout(new FormLayout()); setLayoutData(new GridData(GridData.FILL_BOTH)); // Add a tool-bar at the top of the tab // The form-data is set on the native widget automatically // addToolBar(); setControlStates(); // enable / disable the icons in the toolbar too. // The main composite contains the graph view, but if needed also // a view with an extra tab containing log, etc. // mainComposite = new Composite(this, SWT.NONE); mainComposite.setLayout(new FillLayout()); // Nick's fix below ------- Control toolbarControl = (Control) toolbar.getManagedObject(); FormData toolbarFd = new FormData(); toolbarFd.left = new FormAttachment(0, 0); toolbarFd.right = new FormAttachment(100, 0); toolbarControl.setLayoutData(toolbarFd); toolbarControl.setParent(this); // ------------------------ FormData fdMainComposite = new FormData(); fdMainComposite.left = new FormAttachment(0, 0); fdMainComposite.top = new FormAttachment((Control) toolbar.getManagedObject(), 0); fdMainComposite.right = new FormAttachment(100, 0); fdMainComposite.bottom = new FormAttachment(100, 0); mainComposite.setLayoutData(fdMainComposite); // To allow for a splitter later on, we will add the splitter here... // sashForm = new SashForm(mainComposite, SWT.VERTICAL); // Add a canvas below it, use up all space initially // canvas = new Canvas(sashForm, SWT.V_SCROLL | SWT.H_SCROLL | SWT.NO_BACKGROUND | SWT.BORDER); sashForm.setWeights(new int[] { 100, }); try { // first get the XML document menuMap.put("trans-graph-hop", (XulMenupopup) getXulDomContainer().getDocumentRoot().getElementById("trans-graph-hop")); menuMap.put("trans-graph-entry", (XulMenupopup) getXulDomContainer().getDocumentRoot().getElementById("trans-graph-entry")); menuMap.put("trans-graph-background", (XulMenupopup) getXulDomContainer().getDocumentRoot().getElementById("trans-graph-background")); menuMap.put("trans-graph-note", (XulMenupopup) getXulDomContainer().getDocumentRoot().getElementById("trans-graph-note")); } catch (Throwable t) { log.logError("Error parsing XUL XML", t); } toolTip = new DefaultToolTip(canvas, ToolTip.NO_RECREATE, true); toolTip.setRespectMonitorBounds(true); toolTip.setRespectDisplayBounds(true); toolTip.setPopupDelay(350); toolTip.setHideDelay(TOOLTIP_HIDE_DELAY_SHORT); toolTip.setShift(new org.eclipse.swt.graphics.Point(ConstUI.TOOLTIP_OFFSET, ConstUI.TOOLTIP_OFFSET)); helpTip = new CheckBoxToolTip(canvas); helpTip.addCheckBoxToolTipListener(new CheckBoxToolTipListener() { @Override public void checkBoxSelected(boolean enabled) { spoon.props.setShowingHelpToolTips(enabled); } }); iconsize = spoon.props.getIconSize(); clearSettings(); remarks = new ArrayList<>(); impact = new ArrayList<>(); impactFinished = false; hori = canvas.getHorizontalBar(); vert = canvas.getVerticalBar(); hori.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { redraw(); } }); vert.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { redraw(); } }); hori.setThumb(100); vert.setThumb(100); hori.setVisible(true); vert.setVisible(true); setVisible(true); newProps(); canvas.setBackground(GUIResource.getInstance().getColorBackground()); canvas.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { if (!spoon.isStopped()) { TransGraph.this.paintControl(e); } } }); selectedSteps = null; lastclick = null; /* * Handle the mouse... */ canvas.addMouseListener(this); canvas.addMouseMoveListener(this); canvas.addMouseTrackListener(this); canvas.addMouseWheelListener(this); canvas.addKeyListener(this); // Drag & Drop for steps Transfer[] ttypes = new Transfer[] { XMLTransfer.getInstance() }; DropTarget ddTarget = new DropTarget(canvas, DND.DROP_MOVE); ddTarget.setTransfer(ttypes); ddTarget.addDropListener(new DropTargetListener() { @Override public void dragEnter(DropTargetEvent event) { clearSettings(); drop_candidate = PropsUI.calculateGridPosition(getRealPosition(canvas, event.x, event.y)); redraw(); } @Override public void dragLeave(DropTargetEvent event) { drop_candidate = null; redraw(); } @Override public void dragOperationChanged(DropTargetEvent event) { } @Override public void dragOver(DropTargetEvent event) { drop_candidate = PropsUI.calculateGridPosition(getRealPosition(canvas, event.x, event.y)); redraw(); } @Override public void drop(DropTargetEvent event) { // no data to copy, indicate failure in event.detail if (event.data == null) { event.detail = DND.DROP_NONE; return; } // System.out.println("Dropping a step!!"); // What's the real drop position? Point p = getRealPosition(canvas, event.x, event.y); // // We expect a Drag and Drop container... (encased in XML) try { DragAndDropContainer container = (DragAndDropContainer) event.data; StepMeta stepMeta = null; boolean newstep = false; switch (container.getType()) { // Put an existing one on the canvas. case DragAndDropContainer.TYPE_STEP: // Drop hidden step onto canvas.... stepMeta = transMeta.findStep(container.getData()); if (stepMeta != null) { if (stepMeta.isDrawn() || transMeta.isStepUsedInTransHops(stepMeta)) { modalMessageDialog(getString("TransGraph.Dialog.StepIsAlreadyOnCanvas.Title"), getString("TransGraph.Dialog.StepIsAlreadyOnCanvas.Message"), SWT.OK); return; } // This step gets the drawn attribute and position set below. } else { // Unknown step dropped: ignore this to be safe! return; } break; // Create a new step case DragAndDropContainer.TYPE_BASE_STEP_TYPE: // Not an existing step: data refers to the type of step to create String id = container.getId(); String name = container.getData(); stepMeta = spoon.newStep(transMeta, id, name, name, false, true); if (stepMeta != null) { newstep = true; } else { return; // Cancelled pressed in dialog or unable to create step. } break; // Create a new TableInput step using the selected connection... case DragAndDropContainer.TYPE_DATABASE_CONNECTION: newstep = true; String connectionName = container.getData(); TableInputMeta tii = new TableInputMeta(); tii.setDatabaseMeta(transMeta.findDatabase(connectionName)); PluginRegistry registry = PluginRegistry.getInstance(); String stepID = registry.getPluginId(StepPluginType.class, tii); PluginInterface stepPlugin = registry.findPluginWithId(StepPluginType.class, stepID); String stepName = transMeta.getAlternativeStepname(stepPlugin.getName()); stepMeta = new StepMeta(stepID, stepName, tii); if (spoon.editStep(transMeta, stepMeta) != null) { transMeta.addStep(stepMeta); spoon.refreshTree(); spoon.refreshGraph(); } else { return; } break; // Drag hop on the canvas: create a new Hop... case DragAndDropContainer.TYPE_TRANS_HOP: newHop(); return; default: // Nothing we can use: give an error! modalMessageDialog(getString("TransGraph.Dialog.ItemCanNotBePlacedOnCanvas.Title"), getString("TransGraph.Dialog.ItemCanNotBePlacedOnCanvas.Message"), SWT.OK); return; } transMeta.unselectAll(); StepMeta before = null; if (!newstep) { before = (StepMeta) stepMeta.clone(); } stepMeta.drawStep(); stepMeta.setSelected(true); PropsUI.setLocation(stepMeta, p.x, p.y); if (newstep) { spoon.addUndoNew(transMeta, new StepMeta[] { stepMeta }, new int[] { transMeta.indexOfStep(stepMeta) }); } else { spoon.addUndoChange(transMeta, new StepMeta[] { before }, new StepMeta[] { (StepMeta) stepMeta.clone() }, new int[] { transMeta.indexOfStep(stepMeta) }); } canvas.forceFocus(); redraw(); // See if we want to draw a tool tip explaining how to create new hops... // if (newstep && transMeta.nrSteps() > 1 && transMeta.nrSteps() < 5 && spoon.props.isShowingHelpToolTips()) { showHelpTip(p.x, p.y, BaseMessages.getString(PKG, "TransGraph.HelpToolTip.CreatingHops.Title"), BaseMessages.getString(PKG, "TransGraph.HelpToolTip.CreatingHops.Message")); } } catch (Exception e) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransGraph.Dialog.ErrorDroppingObject.Message"), BaseMessages.getString(PKG, "TransGraph.Dialog.ErrorDroppingObject.Title"), e); } } @Override public void dropAccept(DropTargetEvent event) { } }); setBackground(GUIResource.getInstance().getColorBackground()); // Add a timer to set correct the state of the run/stop buttons every 2 seconds... // final Timer timer = new Timer("TransGraph.setControlStates Timer: " + getMeta().getName()); TimerTask timerTask = new TimerTask() { @Override public void run() { try { setControlStates(); } catch (KettleRepositoryLostException krle) { if (log.isBasic()) { log.logBasic(krle.getLocalizedMessage()); } } } }; timer.schedule(timerTask, 2000, 1000); // Make sure the timer stops when we close the tab... // addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent arg0) { timer.cancel(); } }); } @Override public void mouseDoubleClick(MouseEvent e) { clearSettings(); Point real = screen2real(e.x, e.y); // Hide the tooltip! hideToolTips(); try { ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransGraphMouseDoubleClick.id, new TransGraphExtension(this, e, real)); } catch (Exception ex) { LogChannel.GENERAL.logError("Error calling TransGraphMouseDoubleClick extension point", ex); } StepMeta stepMeta = transMeta.getStep(real.x, real.y, iconsize); if (stepMeta != null) { if (e.button == 1) { editStep(stepMeta); } else { editDescription(stepMeta); } } else { // Check if point lies on one of the many hop-lines... TransHopMeta online = findHop(real.x, real.y); if (online != null) { editHop(online); } else { NotePadMeta ni = transMeta.getNote(real.x, real.y); if (ni != null) { selectedNote = null; editNote(ni); } else { // See if the double click was in one of the area's... // boolean hit = false; for (AreaOwner areaOwner : areaOwners) { if (areaOwner.contains(real.x, real.y)) { if (areaOwner.getParent() instanceof StepMeta && areaOwner.getOwner().equals(TransPainter.STRING_PARTITIONING_CURRENT_STEP)) { StepMeta step = (StepMeta) areaOwner.getParent(); spoon.editPartitioning(transMeta, step); hit = true; break; } } } if (!hit) { settings(); } } } } } @Override public void mouseDown(MouseEvent e) { boolean alt = (e.stateMask & SWT.ALT) != 0; boolean control = (e.stateMask & SWT.MOD1) != 0; boolean shift = (e.stateMask & SWT.SHIFT) != 0; lastButton = e.button; Point real = screen2real(e.x, e.y); lastclick = new Point(real.x, real.y); // Hide the tooltip! hideToolTips(); // Set the pop-up menu if (e.button == 3) { setMenu(real.x, real.y); return; } try { ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransGraphMouseDown.id, new TransGraphExtension(this, e, real)); } catch (Exception ex) { LogChannel.GENERAL.logError("Error calling TransGraphMouseDown extension point", ex); } // A single left or middle click on one of the area owners... // if (e.button == 1 || e.button == 2) { AreaOwner areaOwner = getVisibleAreaOwner(real.x, real.y); if (areaOwner != null && areaOwner.getAreaType() != null) { switch (areaOwner.getAreaType()) { case STEP_OUTPUT_HOP_ICON: // Click on the output icon means: start of drag // Action: We show the input icons on the other steps... // selectedStep = null; startHopStep = (StepMeta) areaOwner.getParent(); candidateHopType = null; startErrorHopStep = false; // stopStepMouseOverDelayTimer(startHopStep); break; case STEP_INPUT_HOP_ICON: // Click on the input icon means: start to a new hop // In this case, we set the end hop step... // selectedStep = null; startHopStep = null; endHopStep = (StepMeta) areaOwner.getParent(); candidateHopType = null; startErrorHopStep = false; // stopStepMouseOverDelayTimer(endHopStep); break; case HOP_ERROR_ICON: // Click on the error icon means: Edit error handling // StepMeta stepMeta = (StepMeta) areaOwner.getParent(); spoon.editStepErrorHandling(transMeta, stepMeta); break; case STEP_TARGET_HOP_ICON_OPTION: // Below, see showStepTargetOptions() break; case STEP_EDIT_ICON: clearSettings(); currentStep = (StepMeta) areaOwner.getParent(); stopStepMouseOverDelayTimer(currentStep); editStep(); break; case STEP_INJECT_ICON: modalMessageDialog(getString("TransGraph.StepInjectionSupported.Title"), getString("TransGraph.StepInjectionSupported.Tooltip"), SWT.OK | SWT.ICON_INFORMATION); break; case STEP_MENU_ICON: clearSettings(); stepMeta = (StepMeta) areaOwner.getParent(); setMenu(stepMeta.getLocation().x, stepMeta.getLocation().y); break; case STEP_ICON: stepMeta = (StepMeta) areaOwner.getOwner(); currentStep = stepMeta; for (StepSelectionListener listener : currentStepListeners) { listener.onUpdateSelection(currentStep); } if (candidate != null) { addCandidateAsHop(e.x, e.y); } else { if (transPreviewDelegate.isActive()) { transPreviewDelegate.setSelectedStep(currentStep); for (SelectedStepListener stepListener : stepListeners) { if (this.extraViewComposite != null && !this.extraViewComposite.isDisposed()) { stepListener.onSelect(currentStep); } } transPreviewDelegate.refreshView(); } } // ALT-Click: edit error handling // if (e.button == 1 && alt && stepMeta.supportsErrorHandling()) { spoon.editStepErrorHandling(transMeta, stepMeta); return; } else if (e.button == 2 || (e.button == 1 && shift)) { // SHIFT CLICK is start of drag to create a new hop // startHopStep = stepMeta; } else { selectedSteps = transMeta.getSelectedSteps(); selectedStep = stepMeta; // // When an icon is moved that is not selected, it gets // selected too late. // It is not captured here, but in the mouseMoveListener... // previous_step_locations = transMeta.getSelectedStepLocations(); Point p = stepMeta.getLocation(); iconoffset = new Point(real.x - p.x, real.y - p.y); } redraw(); break; case NOTE: ni = (NotePadMeta) areaOwner.getOwner(); selectedNotes = transMeta.getSelectedNotes(); selectedNote = ni; Point loc = ni.getLocation(); previous_note_locations = transMeta.getSelectedNoteLocations(); noteoffset = new Point(real.x - loc.x, real.y - loc.y); redraw(); break; case STEP_COPIES_TEXT: copies((StepMeta) areaOwner.getOwner()); break; case STEP_DATA_SERVICE: editProperties(transMeta, spoon, spoon.getRepository(), true, TransDialog.Tabs.EXTRA_TAB); break; default: break; } } else { // A hop? --> enable/disable // TransHopMeta hop = findHop(real.x, real.y); if (hop != null) { TransHopMeta before = (TransHopMeta) hop.clone(); setHopEnabled(hop, !hop.isEnabled()); if (hop.isEnabled() && transMeta.hasLoop(hop.getToStep())) { setHopEnabled(hop, false); modalMessageDialog(getString("TransGraph.Dialog.HopCausesLoop.Title"), getString("TransGraph.Dialog.HopCausesLoop.Message"), SWT.OK | SWT.ICON_ERROR); } TransHopMeta after = (TransHopMeta) hop.clone(); spoon.addUndoChange(transMeta, new TransHopMeta[] { before }, new TransHopMeta[] { after }, new int[] { transMeta.indexOfTransHop(hop) }); redraw(); spoon.setShellText(); } else { // No area-owner & no hop means : background click: // startHopStep = null; if (!control) { selectionRegion = new org.pentaho.di.core.gui.Rectangle(real.x, real.y, 0, 0); } redraw(); } } } } @Override public void mouseUp(MouseEvent e) { try { TransGraphExtension ext = new TransGraphExtension(null, e, getArea()); ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransGraphMouseUp.id, ext); if (ext.isPreventDefault()) { redraw(); clearSettings(); return; } } catch (Exception ex) { LogChannel.GENERAL.logError("Error calling TransGraphMouseUp extension point", ex); } boolean control = (e.stateMask & SWT.MOD1) != 0; TransHopMeta selectedHop = findHop(e.x, e.y); updateErrorMetaForHop(selectedHop); if (iconoffset == null) { iconoffset = new Point(0, 0); } Point real = screen2real(e.x, e.y); Point icon = new Point(real.x - iconoffset.x, real.y - iconoffset.y); AreaOwner areaOwner = getVisibleAreaOwner(real.x, real.y); try { TransGraphExtension ext = new TransGraphExtension(this, e, real); ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransGraphMouseUp.id, ext); if (ext.isPreventDefault()) { redraw(); clearSettings(); return; } } catch (Exception ex) { LogChannel.GENERAL.logError("Error calling TransGraphMouseUp extension point", ex); } // Quick new hop option? (drag from one step to another) // if (candidate != null && areaOwner != null && areaOwner.getAreaType() != null) { switch (areaOwner.getAreaType()) { case STEP_ICON: currentStep = (StepMeta) areaOwner.getOwner(); break; case STEP_INPUT_HOP_ICON: currentStep = (StepMeta) areaOwner.getParent(); break; default: break; } addCandidateAsHop(e.x, e.y); redraw(); } else { // Did we select a region on the screen? Mark steps in region as // selected // if (selectionRegion != null) { selectionRegion.width = real.x - selectionRegion.x; selectionRegion.height = real.y - selectionRegion.y; transMeta.unselectAll(); selectInRect(transMeta, selectionRegion); selectionRegion = null; stopStepMouseOverDelayTimers(); redraw(); } else { // Clicked on an icon? // if (selectedStep != null && startHopStep == null) { if (e.button == 1) { Point realclick = screen2real(e.x, e.y); if (lastclick.x == realclick.x && lastclick.y == realclick.y) { // Flip selection when control is pressed! if (control) { selectedStep.flipSelected(); } else { // Otherwise, select only the icon clicked on! transMeta.unselectAll(); selectedStep.setSelected(true); } } else { // Find out which Steps & Notes are selected selectedSteps = transMeta.getSelectedSteps(); selectedNotes = transMeta.getSelectedNotes(); // We moved around some items: store undo info... // boolean also = false; if (selectedNotes != null && selectedNotes.size() > 0 && previous_note_locations != null) { int[] indexes = transMeta.getNoteIndexes(selectedNotes); addUndoPosition(selectedNotes.toArray(new NotePadMeta[selectedNotes.size()]), indexes, previous_note_locations, transMeta.getSelectedNoteLocations(), also); also = selectedSteps != null && selectedSteps.size() > 0; } if (selectedSteps != null && previous_step_locations != null) { int[] indexes = transMeta.getStepIndexes(selectedSteps); addUndoPosition(selectedSteps.toArray(new StepMeta[selectedSteps.size()]), indexes, previous_step_locations, transMeta.getSelectedStepLocations(), also); } } } // OK, we moved the step, did we move it across a hop? // If so, ask to split the hop! if (split_hop) { TransHopMeta hi = findHop(icon.x + iconsize / 2, icon.y + iconsize / 2, selectedStep); if (hi != null) { splitHop(hi); } split_hop = false; } selectedSteps = null; selectedNotes = null; selectedStep = null; selectedNote = null; startHopStep = null; endHopLocation = null; redraw(); spoon.setShellText(); } else { // Notes? // if (selectedNote != null) { if (e.button == 1) { if (lastclick.x == e.x && lastclick.y == e.y) { // Flip selection when control is pressed! if (control) { selectedNote.flipSelected(); } else { // Otherwise, select only the note clicked on! transMeta.unselectAll(); selectedNote.setSelected(true); } } else { // Find out which Steps & Notes are selected selectedSteps = transMeta.getSelectedSteps(); selectedNotes = transMeta.getSelectedNotes(); // We moved around some items: store undo info... boolean also = false; if (selectedNotes != null && selectedNotes.size() > 0 && previous_note_locations != null) { int[] indexes = transMeta.getNoteIndexes(selectedNotes); addUndoPosition(selectedNotes.toArray(new NotePadMeta[selectedNotes.size()]), indexes, previous_note_locations, transMeta.getSelectedNoteLocations(), also); also = selectedSteps != null && selectedSteps.size() > 0; } if (selectedSteps != null && selectedSteps.size() > 0 && previous_step_locations != null) { int[] indexes = transMeta.getStepIndexes(selectedSteps); addUndoPosition(selectedSteps.toArray(new StepMeta[selectedSteps.size()]), indexes, previous_step_locations, transMeta.getSelectedStepLocations(), also); } } } selectedNotes = null; selectedSteps = null; selectedStep = null; selectedNote = null; startHopStep = null; endHopLocation = null; } else { if (areaOwner == null && selectionRegion == null) { // Hit absolutely nothing: clear the settings // clearSettings(); } } } } } lastButton = 0; } private void splitHop(TransHopMeta hi) { int id = 0; if (!spoon.props.getAutoSplit()) { MessageDialogWithToggle md = new MessageDialogWithToggle(shell, BaseMessages.getString(PKG, "TransGraph.Dialog.SplitHop.Title"), null, BaseMessages.getString(PKG, "TransGraph.Dialog.SplitHop.Message") + Const.CR + hi.toString(), MessageDialog.QUESTION, new String[] { BaseMessages.getString(PKG, "System.Button.Yes"), BaseMessages.getString(PKG, "System.Button.No") }, 0, BaseMessages.getString(PKG, "TransGraph.Dialog.Option.SplitHop.DoNotAskAgain"), spoon.props.getAutoSplit()); MessageDialogWithToggle.setDefaultImage(GUIResource.getInstance().getImageSpoon()); id = md.open(); spoon.props.setAutoSplit(md.getToggleState()); } if ((id & 0xFF) == 0) { // Means: "Yes" button clicked! // Only split A-->--B by putting C in between IF... // C-->--A or B-->--C don't exists... // A ==> hi.getFromStep() // B ==> hi.getToStep(); // C ==> selected_step // boolean caExists = transMeta.findTransHop(selectedStep, hi.getFromStep()) != null; boolean bcExists = transMeta.findTransHop(hi.getToStep(), selectedStep) != null; if (!caExists && !bcExists) { StepMeta fromStep = hi.getFromStep(); StepMeta toStep = hi.getToStep(); // In case step A targets B then we now need to target C // StepIOMetaInterface fromIo = fromStep.getStepMetaInterface().getStepIOMeta(); for (StreamInterface stream : fromIo.getTargetStreams()) { if (stream.getStepMeta() != null && stream.getStepMeta().equals(toStep)) { // This target stream was directed to B, now we need to direct it to C stream.setStepMeta(selectedStep); fromStep.getStepMetaInterface().handleStreamSelection(stream); } } // In case step B sources from A then we now need to source from C // StepIOMetaInterface toIo = toStep.getStepMetaInterface().getStepIOMeta(); for (StreamInterface stream : toIo.getInfoStreams()) { if (stream.getStepMeta() != null && stream.getStepMeta().equals(fromStep)) { // This info stream was reading from B, now we need to direct it to C stream.setStepMeta(selectedStep); toStep.getStepMetaInterface().handleStreamSelection(stream); } } // In case there is error handling on A, we want to make it point to C now // StepErrorMeta errorMeta = fromStep.getStepErrorMeta(); if (fromStep.isDoingErrorHandling() && toStep.equals(errorMeta.getTargetStep())) { errorMeta.setTargetStep(selectedStep); } TransHopMeta newhop1 = new TransHopMeta(hi.getFromStep(), selectedStep); if (transMeta.findTransHop(newhop1) == null) { transMeta.addTransHop(newhop1); spoon.addUndoNew(transMeta, new TransHopMeta[] { newhop1, }, new int[] { transMeta.indexOfTransHop(newhop1), }, true); } TransHopMeta newhop2 = new TransHopMeta(selectedStep, hi.getToStep()); if (transMeta.findTransHop(newhop2) == null) { transMeta.addTransHop(newhop2); spoon.addUndoNew(transMeta, new TransHopMeta[] { newhop2 }, new int[] { transMeta.indexOfTransHop(newhop2) }, true); } int idx = transMeta.indexOfTransHop(hi); spoon.addUndoDelete(transMeta, new TransHopMeta[] { hi }, new int[] { idx }, true); transMeta.removeTransHop(idx); spoon.refreshTree(); } // else: Silently discard this hop-split attempt. } } @Override public void mouseMove(MouseEvent e) { boolean shift = (e.stateMask & SWT.SHIFT) != 0; noInputStep = null; // disable the tooltip // toolTip.hide(); toolTip.setHideDelay(TOOLTIP_HIDE_DELAY_SHORT); Point real = screen2real(e.x, e.y); // Remember the last position of the mouse for paste with keyboard // lastMove = real; if (iconoffset == null) { iconoffset = new Point(0, 0); } Point icon = new Point(real.x - iconoffset.x, real.y - iconoffset.y); if (noteoffset == null) { noteoffset = new Point(0, 0); } Point note = new Point(real.x - noteoffset.x, real.y - noteoffset.y); // Moved over an area? // AreaOwner areaOwner = getVisibleAreaOwner(real.x, real.y); if (areaOwner != null && areaOwner.getAreaType() != null) { switch (areaOwner.getAreaType()) { case STEP_ICON: StepMeta stepMeta = (StepMeta) areaOwner.getOwner(); resetDelayTimer(stepMeta); break; case MINI_ICONS_BALLOON: // Give the timer a bit more time stepMeta = (StepMeta) areaOwner.getParent(); resetDelayTimer(stepMeta); break; default: break; } } try { TransGraphExtension ext = new TransGraphExtension(this, e, real); ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransGraphMouseMoved.id, ext); } catch (Exception ex) { LogChannel.GENERAL.logError("Error calling TransGraphMouseMoved extension point", ex); } // // First see if the icon we clicked on was selected. // If the icon was not selected, we should un-select all other // icons, selected and move only the one icon // if (selectedStep != null && !selectedStep.isSelected()) { transMeta.unselectAll(); selectedStep.setSelected(true); selectedSteps = new ArrayList<>(); selectedSteps.add(selectedStep); previous_step_locations = new Point[] { selectedStep.getLocation() }; redraw(); } else if (selectedNote != null && !selectedNote.isSelected()) { transMeta.unselectAll(); selectedNote.setSelected(true); selectedNotes = new ArrayList<>(); selectedNotes.add(selectedNote); previous_note_locations = new Point[] { selectedNote.getLocation() }; redraw(); } else if (selectionRegion != null && startHopStep == null) { // Did we select a region...? // selectionRegion.width = real.x - selectionRegion.x; selectionRegion.height = real.y - selectionRegion.y; redraw(); } else if (selectedStep != null && lastButton == 1 && !shift && startHopStep == null) { // // One or more icons are selected and moved around... // // new : new position of the ICON (not the mouse pointer) dx : difference with previous position // int dx = icon.x - selectedStep.getLocation().x; int dy = icon.y - selectedStep.getLocation().y; // See if we have a hop-split candidate // TransHopMeta hi = findHop(icon.x + iconsize / 2, icon.y + iconsize / 2, selectedStep); if (hi != null) { // OK, we want to split the hop in 2 // if (!hi.getFromStep().equals(selectedStep) && !hi.getToStep().equals(selectedStep)) { split_hop = true; last_hop_split = hi; hi.split = true; } } else { if (last_hop_split != null) { last_hop_split.split = false; last_hop_split = null; split_hop = false; } } selectedNotes = transMeta.getSelectedNotes(); selectedSteps = transMeta.getSelectedSteps(); // Adjust location of selected steps... if (selectedSteps != null) { for (int i = 0; i < selectedSteps.size(); i++) { StepMeta stepMeta = selectedSteps.get(i); PropsUI.setLocation(stepMeta, stepMeta.getLocation().x + dx, stepMeta.getLocation().y + dy); stopStepMouseOverDelayTimer(stepMeta); } } // Adjust location of selected hops... if (selectedNotes != null) { for (int i = 0; i < selectedNotes.size(); i++) { NotePadMeta ni = selectedNotes.get(i); PropsUI.setLocation(ni, ni.getLocation().x + dx, ni.getLocation().y + dy); } } redraw(); } else if ((startHopStep != null && endHopStep == null) || (endHopStep != null && startHopStep == null)) { // Are we creating a new hop with the middle button or pressing SHIFT? // StepMeta stepMeta = transMeta.getStep(real.x, real.y, iconsize); endHopLocation = new Point(real.x, real.y); if (stepMeta != null && ((startHopStep != null && !startHopStep.equals(stepMeta)) || (endHopStep != null && !endHopStep.equals(stepMeta)))) { StepIOMetaInterface ioMeta = stepMeta.getStepMetaInterface().getStepIOMeta(); if (candidate == null) { // See if the step accepts input. If not, we can't create a new hop... // if (startHopStep != null) { if (ioMeta.isInputAcceptor()) { candidate = new TransHopMeta(startHopStep, stepMeta); endHopLocation = null; } else { noInputStep = stepMeta; toolTip.setImage(null); toolTip.setText("This step does not accept any input from other steps"); toolTip.show(new org.eclipse.swt.graphics.Point(real.x, real.y)); } } else if (endHopStep != null) { if (ioMeta.isOutputProducer()) { candidate = new TransHopMeta(stepMeta, endHopStep); endHopLocation = null; } else { noInputStep = stepMeta; toolTip.setImage(null); toolTip.setText( "This step doesn't pass any output to other steps. (except perhaps for targetted output)"); toolTip.show(new org.eclipse.swt.graphics.Point(real.x, real.y)); } } } } else { if (candidate != null) { candidate = null; redraw(); } } redraw(); } // Move around notes & steps // if (selectedNote != null) { if (lastButton == 1 && !shift) { /* * One or more notes are selected and moved around... * * new : new position of the note (not the mouse pointer) dx : difference with previous position */ int dx = note.x - selectedNote.getLocation().x; int dy = note.y - selectedNote.getLocation().y; selectedNotes = transMeta.getSelectedNotes(); selectedSteps = transMeta.getSelectedSteps(); // Adjust location of selected steps... if (selectedSteps != null) { for (int i = 0; i < selectedSteps.size(); i++) { StepMeta stepMeta = selectedSteps.get(i); PropsUI.setLocation(stepMeta, stepMeta.getLocation().x + dx, stepMeta.getLocation().y + dy); } } // Adjust location of selected hops... if (selectedNotes != null) { for (int i = 0; i < selectedNotes.size(); i++) { NotePadMeta ni = selectedNotes.get(i); PropsUI.setLocation(ni, ni.getLocation().x + dx, ni.getLocation().y + dy); } } redraw(); } } } @Override public void mouseHover(MouseEvent e) { boolean tip = true; boolean isDeprecated = false; toolTip.hide(); toolTip.setHideDelay(TOOLTIP_HIDE_DELAY_SHORT); Point real = screen2real(e.x, e.y); AreaOwner areaOwner = getVisibleAreaOwner(real.x, real.y); if (areaOwner != null && areaOwner.getAreaType() != null) { switch (areaOwner.getAreaType()) { case STEP_ICON: StepMeta stepMeta = (StepMeta) areaOwner.getOwner(); isDeprecated = stepMeta.isDeprecated(); if (!stepMeta.isMissing() && !mouseOverSteps.contains(stepMeta)) { addStepMouseOverDelayTimer(stepMeta); redraw(); tip = false; } break; default: break; } } // Show a tool tip upon mouse-over of an object on the canvas if ((tip && !helpTip.isVisible()) || isDeprecated) { setToolTip(real.x, real.y, e.x, e.y); } } @Override public void mouseScrolled(MouseEvent e) { /* * if (e.count == 3) { // scroll up zoomIn(); } else if (e.count == -3) { // scroll down zoomOut(); } } */ } private void addCandidateAsHop(int mouseX, int mouseY) { boolean forward = startHopStep != null; StepMeta fromStep = candidate.getFromStep(); StepMeta toStep = candidate.getToStep(); // See what the options are. // - Does the source step has multiple stream options? // - Does the target step have multiple input stream options? // List<StreamInterface> streams = new ArrayList<>(); StepIOMetaInterface fromIoMeta = fromStep.getStepMetaInterface().getStepIOMeta(); List<StreamInterface> targetStreams = fromIoMeta.getTargetStreams(); if (forward) { streams.addAll(targetStreams); } StepIOMetaInterface toIoMeta = toStep.getStepMetaInterface().getStepIOMeta(); List<StreamInterface> infoStreams = toIoMeta.getInfoStreams(); if (!forward) { streams.addAll(infoStreams); } if (forward) { if (fromIoMeta.isOutputProducer() && toStep.equals(currentStep)) { streams.add(new Stream(StreamType.OUTPUT, fromStep, BaseMessages.getString(PKG, "Spoon.Hop.MainOutputOfStep"), StreamIcon.OUTPUT, null)); } if (fromStep.supportsErrorHandling() && toStep.equals(currentStep)) { streams.add(new Stream(StreamType.ERROR, fromStep, BaseMessages.getString(PKG, "Spoon.Hop.ErrorHandlingOfStep"), StreamIcon.ERROR, null)); } } else { if (toIoMeta.isInputAcceptor() && fromStep.equals(currentStep)) { streams.add(new Stream(StreamType.INPUT, toStep, BaseMessages.getString(PKG, "Spoon.Hop.MainInputOfStep"), StreamIcon.INPUT, null)); } if (fromStep.supportsErrorHandling() && fromStep.equals(currentStep)) { streams.add(new Stream(StreamType.ERROR, fromStep, BaseMessages.getString(PKG, "Spoon.Hop.ErrorHandlingOfStep"), StreamIcon.ERROR, null)); } } // Targets can be dynamically added to this step... // if (forward) { streams.addAll(fromStep.getStepMetaInterface().getOptionalStreams()); } else { streams.addAll(toStep.getStepMetaInterface().getOptionalStreams()); } // Show a list of options on the canvas... // if (streams.size() > 1) { // Show a pop-up menu with all the possible options... // Menu menu = new Menu(canvas); for (final StreamInterface stream : streams) { MenuItem item = new MenuItem(menu, SWT.NONE); item.setText(Const.NVL(stream.getDescription(), "")); item.setImage(getImageFor(stream)); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { addHop(stream); } }); } menu.setLocation(canvas.toDisplay(mouseX, mouseY)); menu.setVisible(true); return; } if (streams.size() == 1) { addHop(streams.get(0)); } else { return; } /* * * if (transMeta.findTransHop(candidate) == null) { spoon.newHop(transMeta, candidate); } if (startErrorHopStep) { * addErrorHop(); } if (startTargetHopStream != null) { // Auto-configure the target in the source step... // * startTargetHopStream.setStepMeta(candidate.getToStep()); * startTargetHopStream.setStepname(candidate.getToStep().getName()); startTargetHopStream = null; } */ candidate = null; selectedSteps = null; startHopStep = null; endHopLocation = null; startErrorHopStep = false; // redraw(); } private Image getImageFor(StreamInterface stream) { Display disp = shell.getDisplay(); SwtUniversalImage swtImage = SWTGC.getNativeImage(BasePainter.getStreamIconImage(stream.getStreamIcon())); return swtImage.getAsBitmapForSize(disp, ConstUI.SMALL_ICON_SIZE, ConstUI.SMALL_ICON_SIZE); } protected void addHop(StreamInterface stream) { switch (stream.getStreamType()) { case ERROR: addErrorHop(); candidate.setErrorHop(true); spoon.newHop(transMeta, candidate); break; case INPUT: spoon.newHop(transMeta, candidate); break; case OUTPUT: StepErrorMeta stepErrorMeta = candidate.getFromStep().getStepErrorMeta(); if (stepErrorMeta != null && stepErrorMeta.getTargetStep() != null) { if (stepErrorMeta.getTargetStep().equals(candidate.getToStep())) { candidate.getFromStep().setStepErrorMeta(null); } } spoon.newHop(transMeta, candidate); break; case INFO: stream.setStepMeta(candidate.getFromStep()); candidate.getToStep().getStepMetaInterface().handleStreamSelection(stream); spoon.newHop(transMeta, candidate); break; case TARGET: // We connect a target of the source step to an output step... // stream.setStepMeta(candidate.getToStep()); candidate.getFromStep().getStepMetaInterface().handleStreamSelection(stream); spoon.newHop(transMeta, candidate); break; default: break; } clearSettings(); } private void addErrorHop() { // Automatically configure the step error handling too! // if (candidate == null || candidate.getFromStep() == null) { return; } StepErrorMeta errorMeta = candidate.getFromStep().getStepErrorMeta(); if (errorMeta == null) { errorMeta = new StepErrorMeta(transMeta, candidate.getFromStep()); } errorMeta.setEnabled(true); errorMeta.setTargetStep(candidate.getToStep()); candidate.getFromStep().setStepErrorMeta(errorMeta); } private void resetDelayTimer(StepMeta stepMeta) { DelayTimer delayTimer = delayTimers.get(stepMeta); if (delayTimer != null) { delayTimer.reset(); } } /* * private void showStepTargetOptions(final StepMeta stepMeta, StepIOMetaInterface ioMeta, int x, int y) { * * if (!Utils.isEmpty(ioMeta.getTargetStepnames())) { final Menu menu = new Menu(canvas); for (final StreamInterface * stream : ioMeta.getTargetStreams()) { MenuItem menuItem = new MenuItem(menu, SWT.NONE); * menuItem.setText(stream.getDescription()); menuItem.addSelectionListener(new SelectionAdapter() { * * @Override public void widgetSelected(SelectionEvent arg0) { // Click on the target icon means: create a new target * hop // if (startHopStep==null) { startHopStep = stepMeta; } menu.setVisible(false); menu.dispose(); redraw(); } }); * } menu.setLocation(x, y); menu.setVisible(true); resetDelayTimer(stepMeta); * * //showTargetStreamsStep = stepMeta; } } */ @Override public void mouseEnter(MouseEvent arg0) { } @Override public void mouseExit(MouseEvent arg0) { } private synchronized void addStepMouseOverDelayTimer(final StepMeta stepMeta) { // Don't add the same mouse over delay timer twice... // if (mouseOverSteps.contains(stepMeta)) { return; } mouseOverSteps.add(stepMeta); DelayTimer delayTimer = new DelayTimer(500, new DelayListener() { @Override public void expired() { mouseOverSteps.remove(stepMeta); delayTimers.remove(stepMeta); showTargetStreamsStep = null; asyncRedraw(); } }, new Callable<Boolean>() { @Override public Boolean call() throws Exception { Point cursor = getLastMove(); if (cursor != null) { AreaOwner areaOwner = getVisibleAreaOwner(cursor.x, cursor.y); if (areaOwner != null) { AreaType areaType = areaOwner.getAreaType(); if (areaType == AreaType.STEP_ICON) { StepMeta selectedStepMeta = (StepMeta) areaOwner.getOwner(); return selectedStepMeta == stepMeta; } else if (areaType != null && areaType.belongsToTransContextMenu()) { StepMeta selectedStepMeta = (StepMeta) areaOwner.getParent(); return selectedStepMeta == stepMeta; } else if (areaOwner.getExtensionAreaType() != null) { return true; } } } return false; } }); new Thread(delayTimer).start(); delayTimers.put(stepMeta, delayTimer); } private void stopStepMouseOverDelayTimer(final StepMeta stepMeta) { DelayTimer delayTimer = delayTimers.get(stepMeta); if (delayTimer != null) { delayTimer.stop(); } } private void stopStepMouseOverDelayTimers() { for (DelayTimer timer : delayTimers.values()) { timer.stop(); } } protected void asyncRedraw() { spoon.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!TransGraph.this.isDisposed()) { TransGraph.this.redraw(); } } }); } private void addToolBar() { try { toolbar = (XulToolbar) getXulDomContainer().getDocumentRoot().getElementById("nav-toolbar"); ToolBar swtToolbar = (ToolBar) toolbar.getManagedObject(); swtToolbar.setBackground(GUIResource.getInstance().getColorDemoGray()); swtToolbar.pack(); // Added 1/11/2016 to implement dropdown option for "Run" ToolItem runItem = new ToolItem(swtToolbar, SWT.DROP_DOWN, 0); runItem.setImage(GUIResource.getInstance().getImage("ui/images/run.svg")); runItem.setToolTipText(BaseMessages.getString(PKG, "Spoon.Tooltip.RunTranformation")); runItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (e.detail == SWT.DROP_DOWN) { Menu menu = new Menu(shell, SWT.POP_UP); MenuItem item1 = new MenuItem(menu, SWT.PUSH); item1.setText(BaseMessages.getString(PKG, "Spoon.Run.Run")); item1.setAccelerator(SWT.F9); item1.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e1) { runTransformation(); } }); MenuItem item2 = new MenuItem(menu, SWT.PUSH); item2.setText(BaseMessages.getString(PKG, "Spoon.Run.RunOptions")); item2.setAccelerator(SWT.F8); item2.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e2) { runOptionsTransformation(); } }); menu.setLocation(shell.getDisplay().map(mainComposite.getParent(), null, mainComposite.getLocation())); menu.setVisible(true); } else { runTransformation(); } } }); stopItem = new ToolItem(swtToolbar, SWT.DROP_DOWN, 2); stopItem.setImage(GUIResource.getInstance().getImage("ui/images/stop.svg")); stopItem.setToolTipText(BaseMessages.getString(PKG, "Spoon.Tooltip.StopTranformation")); stopItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (e.detail == SWT.DROP_DOWN) { Menu menu = new Menu(shell, SWT.POP_UP); MenuItem item1 = new MenuItem(menu, SWT.PUSH); item1.setText(BaseMessages.getString(PKG, "Spoon.Menu.StopTranformation")); item1.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e1) { stopTransformation(); } }); MenuItem item2 = new MenuItem(menu, SWT.PUSH); item2.setText(BaseMessages.getString(PKG, "Spoon.Menu.SafeStopTranformation")); item2.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e2) { safeStop(); } }); menu.setLocation(shell.getDisplay().map(mainComposite.getParent(), null, mainComposite.getLocation())); menu.setVisible(true); } else { stopTransformation(); } } }); // Hack alert : more XUL limitations... // TODO: no longer a limitation use toolbaritem // ToolItem sep = new ToolItem(swtToolbar, SWT.SEPARATOR); zoomLabel = new Combo(swtToolbar, SWT.DROP_DOWN); zoomLabel.setItems(TransPainter.magnificationDescriptions); zoomLabel.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { readMagnification(); } }); zoomLabel.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent event) { if (event.character == SWT.CR) { readMagnification(); } } }); setZoomLabel(); zoomLabel.pack(); sep.setWidth(80); sep.setControl(zoomLabel); swtToolbar.pack(); } catch (Throwable t) { log.logError("Error loading the navigation toolbar for Spoon", t); new ErrorDialog( shell, BaseMessages.getString(PKG, "Spoon.Exception.ErrorReadingXULFile.Title"), BaseMessages .getString(PKG, "Spoon.Exception.ErrorReadingXULFile.Message", XUL_FILE_TRANS_TOOLBAR), new Exception(t)); } } /** * Allows for magnifying to any percentage entered by the user... */ private void readMagnification() { String possibleText = zoomLabel.getText(); possibleText = possibleText.replace("%", ""); float possibleFloatMagnification; try { possibleFloatMagnification = Float.parseFloat(possibleText) / 100; magnification = possibleFloatMagnification; if (zoomLabel.getText().indexOf('%') < 0) { zoomLabel.setText(zoomLabel.getText().concat("%")); } } catch (Exception e) { modalMessageDialog(getString("TransGraph.Dialog.InvalidZoomMeasurement.Title"), getString("TransGraph.Dialog.InvalidZoomMeasurement.Message", zoomLabel.getText()), SWT.YES | SWT.ICON_ERROR); } redraw(); } protected void hideToolTips() { toolTip.hide(); helpTip.hide(); toolTip.setHideDelay(TOOLTIP_HIDE_DELAY_SHORT); } private void showHelpTip(int x, int y, String tipTitle, String tipMessage) { helpTip.setTitle(tipTitle); helpTip.setMessage(tipMessage.replaceAll("\n", Const.CR)); helpTip.setCheckBoxMessage( BaseMessages.getString(PKG, "TransGraph.HelpToolTip.DoNotShowAnyMoreCheckBox.Message")); // helpTip.hide(); // int iconSize = spoon.props.getIconSize(); org.eclipse.swt.graphics.Point location = new org.eclipse.swt.graphics.Point(x - 5, y - 5); helpTip.show(location); } /** * Select all the steps in a certain (screen) rectangle * * @param rect The selection area as a rectangle */ public void selectInRect(TransMeta transMeta, org.pentaho.di.core.gui.Rectangle rect) { if (rect.height < 0 || rect.width < 0) { org.pentaho.di.core.gui.Rectangle rectified = new org.pentaho.di.core.gui.Rectangle(rect.x, rect.y, rect.width, rect.height); // Only for people not dragging from left top to right bottom if (rectified.height < 0) { rectified.y = rectified.y + rectified.height; rectified.height = -rectified.height; } if (rectified.width < 0) { rectified.x = rectified.x + rectified.width; rectified.width = -rectified.width; } rect = rectified; } for (int i = 0; i < transMeta.nrSteps(); i++) { StepMeta stepMeta = transMeta.getStep(i); Point a = stepMeta.getLocation(); if (rect.contains(a.x, a.y)) { stepMeta.setSelected(true); } } for (int i = 0; i < transMeta.nrNotes(); i++) { NotePadMeta ni = transMeta.getNote(i); Point a = ni.getLocation(); Point b = new Point(a.x + ni.width, a.y + ni.height); if (rect.contains(a.x, a.y) && rect.contains(b.x, b.y)) { ni.setSelected(true); } } } @Override public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.ESC) { clearSettings(); redraw(); } if (e.keyCode == SWT.DEL) { List<StepMeta> stepMeta = transMeta.getSelectedSteps(); if (stepMeta != null && stepMeta.size() > 0) { delSelected(null); } } if (e.keyCode == SWT.F1) { spoon.browseVersionHistory(); } if (e.keyCode == SWT.F2) { spoon.editKettlePropertiesFile(); } // CTRL-UP : allignTop(); if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.MOD1) != 0) { alligntop(); } // CTRL-DOWN : allignBottom(); if (e.keyCode == SWT.ARROW_DOWN && (e.stateMask & SWT.MOD1) != 0) { allignbottom(); } // CTRL-LEFT : allignleft(); if (e.keyCode == SWT.ARROW_LEFT && (e.stateMask & SWT.MOD1) != 0) { allignleft(); } // CTRL-RIGHT : allignRight(); if (e.keyCode == SWT.ARROW_RIGHT && (e.stateMask & SWT.MOD1) != 0) { allignright(); } // ALT-RIGHT : distributeHorizontal(); if (e.keyCode == SWT.ARROW_RIGHT && (e.stateMask & SWT.ALT) != 0) { distributehorizontal(); } // ALT-UP : distributeVertical(); if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.ALT) != 0) { distributevertical(); } // ALT-HOME : snap to grid if (e.keyCode == SWT.HOME && (e.stateMask & SWT.ALT) != 0) { snaptogrid(ConstUI.GRID_SIZE); } if (e.character == 'E' && (e.stateMask & SWT.CTRL) != 0) { checkErrorVisuals(); } // CTRL-W or CTRL-F4 : close tab if ((e.keyCode == 'w' && (e.stateMask & SWT.MOD1) != 0) || (e.keyCode == SWT.F4 && (e.stateMask & SWT.MOD1) != 0)) { spoon.tabCloseSelected(); } // Auto-layout if (e.character == 'A') { autoLayout(); } // SPACE : over a step: show output fields... if (e.character == ' ' && lastMove != null) { Point real = lastMove; // Hide the tooltip! hideToolTips(); // Set the pop-up menu StepMeta stepMeta = transMeta.getStep(real.x, real.y, iconsize); if (stepMeta != null) { // OK, we found a step, show the output fields... inputOutputFields(stepMeta, false); } } } @Override public void keyReleased(KeyEvent e) { } public void renameStep(StepMeta stepMeta, String stepname) { String newname = stepname; StepMeta smeta = transMeta.findStep(newname, stepMeta); int nr = 2; while (smeta != null) { newname = stepname + " " + nr; smeta = transMeta.findStep(newname); nr++; } if (nr > 2) { stepname = newname; modalMessageDialog(getString("Spoon.Dialog.StepnameExists.Title"), getString("Spoon.Dialog.StepnameExists.Message", stepname), SWT.OK | SWT.ICON_INFORMATION); } stepMeta.setName(stepname); stepMeta.setChanged(); spoon.refreshTree(); // to reflect the new name spoon.refreshGraph(); } public void clearSettings() { selectedStep = null; noInputStep = null; selectedNote = null; selectedSteps = null; selectionRegion = null; candidate = null; last_hop_split = null; lastButton = 0; iconoffset = null; startHopStep = null; endHopStep = null; endHopLocation = null; mouseOverSteps.clear(); for (int i = 0; i < transMeta.nrTransHops(); i++) { // CHECKSTYLE:Indentation:OFF transMeta.getTransHop(i).split = false; } stopStepMouseOverDelayTimers(); } public String[] getDropStrings(String str, String sep) { StringTokenizer strtok = new StringTokenizer(str, sep); String[] retval = new String[strtok.countTokens()]; int i = 0; while (strtok.hasMoreElements()) { retval[i] = strtok.nextToken(); i++; } return retval; } public Point getRealPosition(Composite canvas, int x, int y) { Point p = new Point(0, 0); Composite follow = canvas; while (follow != null) { org.eclipse.swt.graphics.Point loc = follow.getLocation(); Point xy = new Point(loc.x, loc.y); p.x += xy.x; p.y += xy.y; follow = follow.getParent(); } int offsetX = -16; int offsetY = -64; if (Const.isOSX()) { offsetX = -2; offsetY = -24; } p.x = x - p.x + offsetX; p.y = y - p.y + offsetY; return screen2real(p.x, p.y); } /** * See if location (x,y) is on a line between two steps: the hop! * * @param x * @param y * @return the transformation hop on the specified location, otherwise: null */ protected TransHopMeta findHop(int x, int y) { return findHop(x, y, null); } /** * See if location (x,y) is on a line between two steps: the hop! * * @param x * @param y * @param exclude the step to exclude from the hops (from or to location). Specify null if no step is to be excluded. * @return the transformation hop on the specified location, otherwise: null */ private TransHopMeta findHop(int x, int y, StepMeta exclude) { int i; TransHopMeta online = null; for (i = 0; i < transMeta.nrTransHops(); i++) { TransHopMeta hi = transMeta.getTransHop(i); StepMeta fs = hi.getFromStep(); StepMeta ts = hi.getToStep(); if (fs == null || ts == null) { return null; } // If either the "from" or "to" step is excluded, skip this hop. // if (exclude != null && (exclude.equals(fs) || exclude.equals(ts))) { continue; } int[] line = getLine(fs, ts); if (pointOnLine(x, y, line)) { online = hi; } } return online; } private int[] getLine(StepMeta fs, StepMeta ts) { Point from = fs.getLocation(); Point to = ts.getLocation(); offset = getOffset(); int x1 = from.x + iconsize / 2; int y1 = from.y + iconsize / 2; int x2 = to.x + iconsize / 2; int y2 = to.y + iconsize / 2; return new int[] { x1, y1, x2, y2 }; } public void hideStep() { for (int i = 0; i < transMeta.nrSteps(); i++) { StepMeta sti = transMeta.getStep(i); if (sti.isDrawn() && sti.isSelected()) { sti.hideStep(); spoon.refreshTree(); } } getCurrentStep().hideStep(); spoon.refreshTree(); redraw(); } public void checkSelectedSteps() { spoon.checkTrans(transMeta, true); } public void detachStep() { detach(getCurrentStep()); selectedSteps = null; } public void generateMappingToThisStep() { spoon.generateFieldMapping(transMeta, getCurrentStep()); } public void partitioning() { spoon.editPartitioning(transMeta, getCurrentStep()); } public void clustering() { List<StepMeta> selected = transMeta.getSelectedSteps(); if (selected != null && selected.size() > 0) { spoon.editClustering(transMeta, transMeta.getSelectedSteps()); } else { spoon.editClustering(transMeta, getCurrentStep()); } } public void errorHandling() { spoon.editStepErrorHandling(transMeta, getCurrentStep()); } public void newHopChoice() { selectedSteps = null; newHop(); } public void editStep() { selectedSteps = null; editStep(getCurrentStep()); } public void editDescription() { editDescription(getCurrentStep()); } public void setDistributes() { getCurrentStep().setDistributes(true); getCurrentStep().setRowDistribution(null); spoon.refreshGraph(); spoon.refreshTree(); } public void setCustomRowDistribution() { // TODO: ask user which row distribution is needed... // RowDistributionInterface rowDistribution = askUserForCustomDistributionMethod(); getCurrentStep().setDistributes(true); getCurrentStep().setRowDistribution(rowDistribution); spoon.refreshGraph(); spoon.refreshTree(); } public RowDistributionInterface askUserForCustomDistributionMethod() { List<PluginInterface> plugins = PluginRegistry.getInstance().getPlugins(RowDistributionPluginType.class); if (Utils.isEmpty(plugins)) { return null; } List<String> choices = new ArrayList<>(); for (PluginInterface plugin : plugins) { choices.add(plugin.getName() + " : " + plugin.getDescription()); } EnterSelectionDialog dialog = new EnterSelectionDialog(shell, choices.toArray(new String[choices.size()]), "Select distribution method", "Please select the row distribution method:"); if (dialog.open() != null) { PluginInterface plugin = plugins.get(dialog.getSelectionNr()); try { return (RowDistributionInterface) PluginRegistry.getInstance().loadClass(plugin); } catch (Exception e) { new ErrorDialog(shell, "Error", "Error loading row distribution plugin class", e); return null; } } else { return null; } } public void setCopies() { getCurrentStep().setDistributes(false); spoon.refreshGraph(); spoon.refreshTree(); } public void copies() { copies(getCurrentStep()); } public void copies(StepMeta stepMeta) { final boolean multipleOK = checkNumberOfCopies(transMeta, stepMeta); selectedSteps = null; String tt = BaseMessages.getString(PKG, "TransGraph.Dialog.NrOfCopiesOfStep.Title"); String mt = BaseMessages.getString(PKG, "TransGraph.Dialog.NrOfCopiesOfStep.Message"); EnterStringDialog nd = new EnterStringDialog(shell, stepMeta.getCopiesString(), tt, mt, true, transMeta); String cop = nd.open(); if (!Utils.isEmpty(cop)) { int copies = Const.toInt(transMeta.environmentSubstitute(cop), -1); if (copies > 1 && !multipleOK) { cop = "1"; modalMessageDialog(getString("TransGraph.Dialog.MultipleCopiesAreNotAllowedHere.Title"), getString("TransGraph.Dialog.MultipleCopiesAreNotAllowedHere.Message"), SWT.YES | SWT.ICON_WARNING); } String cps = stepMeta.getCopiesString(); if ((cps != null && !cps.equals(cop)) || (cps == null && cop != null)) { stepMeta.setChanged(); } stepMeta.setCopiesString(cop); spoon.refreshGraph(); } } public void dupeStep() { try { List<StepMeta> steps = transMeta.getSelectedSteps(); if (steps.size() <= 1) { spoon.dupeStep(transMeta, getCurrentStep()); } else { for (StepMeta stepMeta : steps) { spoon.dupeStep(transMeta, stepMeta); } } } catch (Exception ex) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransGraph.Dialog.ErrorDuplicatingStep.Title"), BaseMessages.getString(PKG, "TransGraph.Dialog.ErrorDuplicatingStep.Message"), ex); } } public void copyStep() { spoon.copySelected(transMeta, transMeta.getSelectedSteps(), transMeta.getSelectedNotes()); } public void delSelected() { delSelected(getCurrentStep()); } public void fieldsBefore() { selectedSteps = null; inputOutputFields(getCurrentStep(), true); } public void fieldsAfter() { selectedSteps = null; inputOutputFields(getCurrentStep(), false); } public void fieldsLineage() { TransDataLineage tdl = new TransDataLineage(transMeta); try { tdl.calculateLineage(); } catch (Exception e) { new ErrorDialog(shell, "Lineage error", "Unexpected lineage calculation error", e); } } public void editHop() { selectionRegion = null; editHop(getCurrentHop()); } public void flipHopDirection() { selectionRegion = null; TransHopMeta hi = getCurrentHop(); hi.flip(); if (transMeta.hasLoop(hi.getToStep())) { spoon.refreshGraph(); modalMessageDialog(getString("TransGraph.Dialog.LoopsAreNotAllowed.Title"), getString("TransGraph.Dialog.LoopsAreNotAllowed.Message"), SWT.OK | SWT.ICON_ERROR); hi.flip(); spoon.refreshGraph(); } else { hi.setChanged(); spoon.refreshGraph(); spoon.refreshTree(); spoon.setShellText(); } } public void enableHop() { selectionRegion = null; TransHopMeta hi = getCurrentHop(); TransHopMeta before = (TransHopMeta) hi.clone(); setHopEnabled(hi, !hi.isEnabled()); if (hi.isEnabled() && transMeta.hasLoop(hi.getToStep())) { setHopEnabled(hi, false); modalMessageDialog(getString("TransGraph.Dialog.LoopAfterHopEnabled.Title"), getString("TransGraph.Dialog.LoopAfterHopEnabled.Message"), SWT.OK | SWT.ICON_ERROR); } else { TransHopMeta after = (TransHopMeta) hi.clone(); spoon.addUndoChange(transMeta, new TransHopMeta[] { before }, new TransHopMeta[] { after }, new int[] { transMeta.indexOfTransHop(hi) }); spoon.refreshGraph(); spoon.refreshTree(); } updateErrorMetaForHop(hi); } public void deleteHop() { selectionRegion = null; TransHopMeta hi = getCurrentHop(); spoon.delHop(transMeta, hi); } private void updateErrorMetaForHop(TransHopMeta hop) { if (hop != null && hop.isErrorHop()) { StepErrorMeta errorMeta = hop.getFromStep().getStepErrorMeta(); if (errorMeta != null) { errorMeta.setEnabled(hop.isEnabled()); } } } public void enableHopsBetweenSelectedSteps() { enableHopsBetweenSelectedSteps(true); } public void disableHopsBetweenSelectedSteps() { enableHopsBetweenSelectedSteps(false); } /** * This method enables or disables all the hops between the selected steps. **/ public void enableHopsBetweenSelectedSteps(boolean enabled) { List<StepMeta> list = transMeta.getSelectedSteps(); boolean hasLoop = false; for (int i = 0; i < transMeta.nrTransHops(); i++) { TransHopMeta hop = transMeta.getTransHop(i); if (list.contains(hop.getFromStep()) && list.contains(hop.getToStep())) { TransHopMeta before = (TransHopMeta) hop.clone(); setHopEnabled(hop, enabled); TransHopMeta after = (TransHopMeta) hop.clone(); spoon.addUndoChange(transMeta, new TransHopMeta[] { before }, new TransHopMeta[] { after }, new int[] { transMeta.indexOfTransHop(hop) }); if (transMeta.hasLoop(hop.getToStep())) { hasLoop = true; setHopEnabled(hop, false); } } } if (enabled && hasLoop) { modalMessageDialog(getString("TransGraph.Dialog.HopCausesLoop.Title"), getString("TransGraph.Dialog.HopCausesLoop.Message"), SWT.OK | SWT.ICON_ERROR); } spoon.refreshGraph(); } public void enableHopsDownstream() { enableDisableHopsDownstream(true); } public void disableHopsDownstream() { enableDisableHopsDownstream(false); } public void enableDisableHopsDownstream(boolean enabled) { if (currentHop == null) { return; } TransHopMeta before = (TransHopMeta) currentHop.clone(); setHopEnabled(currentHop, enabled); TransHopMeta after = (TransHopMeta) currentHop.clone(); spoon.addUndoChange(transMeta, new TransHopMeta[] { before }, new TransHopMeta[] { after }, new int[] { transMeta.indexOfTransHop(currentHop) }); Set<StepMeta> checkedEntries = enableDisableNextHops(currentHop.getToStep(), enabled, new HashSet<>()); if (checkedEntries.stream().anyMatch(entry -> transMeta.hasLoop(entry))) { modalMessageDialog(getString("TransGraph.Dialog.HopCausesLoop.Title"), getString("TransGraph.Dialog.HopCausesLoop.Message"), SWT.OK | SWT.ICON_ERROR); } spoon.refreshGraph(); } private Set<StepMeta> enableDisableNextHops(StepMeta from, boolean enabled, Set<StepMeta> checkedEntries) { checkedEntries.add(from); transMeta.getTransHops().stream().filter(hop -> from.equals(hop.getFromStep())).forEach(hop -> { if (hop.isEnabled() != enabled) { TransHopMeta before = (TransHopMeta) hop.clone(); setHopEnabled(hop, enabled); TransHopMeta after = (TransHopMeta) hop.clone(); spoon.addUndoChange(transMeta, new TransHopMeta[] { before }, new TransHopMeta[] { after }, new int[] { transMeta.indexOfTransHop(hop) }); } if (!checkedEntries.contains(hop.getToStep())) { enableDisableNextHops(hop.getToStep(), enabled, checkedEntries); } }); return checkedEntries; } public void editNote() { selectionRegion = null; editNote(getCurrentNote()); } public void deleteNote() { selectionRegion = null; int idx = transMeta.indexOfNote(ni); if (idx >= 0) { transMeta.removeNote(idx); spoon.addUndoDelete(transMeta, new NotePadMeta[] { (NotePadMeta) ni.clone() }, new int[] { idx }); redraw(); } } public void raiseNote() { selectionRegion = null; int idx = transMeta.indexOfNote(getCurrentNote()); if (idx >= 0) { transMeta.raiseNote(idx); // TBD: spoon.addUndoRaise(transMeta, new NotePadMeta[] {getCurrentNote()}, new int[] {idx} ); } redraw(); } public void lowerNote() { selectionRegion = null; int idx = transMeta.indexOfNote(getCurrentNote()); if (idx >= 0) { transMeta.lowerNote(idx); // TBD: spoon.addUndoLower(transMeta, new NotePadMeta[] {getCurrentNote()}, new int[] {idx} ); } redraw(); } public void newNote() { selectionRegion = null; String title = BaseMessages.getString(PKG, "TransGraph.Dialog.NoteEditor.Title"); NotePadDialog dd = new NotePadDialog(transMeta, shell, title); NotePadMeta n = dd.open(); if (n != null) { NotePadMeta npi = new NotePadMeta(n.getNote(), lastclick.x, lastclick.y, ConstUI.NOTE_MIN_SIZE, ConstUI.NOTE_MIN_SIZE, n.getFontName(), n.getFontSize(), n.isFontBold(), n.isFontItalic(), n.getFontColorRed(), n.getFontColorGreen(), n.getFontColorBlue(), n.getBackGroundColorRed(), n.getBackGroundColorGreen(), n.getBackGroundColorBlue(), n.getBorderColorRed(), n.getBorderColorGreen(), n.getBorderColorBlue(), n.isDrawShadow()); transMeta.addNote(npi); spoon.addUndoNew(transMeta, new NotePadMeta[] { npi }, new int[] { transMeta.indexOfNote(npi) }); redraw(); } } public void paste() { final String clipcontent = spoon.fromClipboard(); Point loc = new Point(currentMouseX, currentMouseY); spoon.pasteXML(transMeta, clipcontent, loc); } public void settings() { editProperties(transMeta, spoon, spoon.getRepository(), true); } public void newStep(String description) { StepMeta stepMeta = spoon.newStep(transMeta, description, description, false, true); PropsUI.setLocation(stepMeta, currentMouseX, currentMouseY); stepMeta.setDraw(true); redraw(); } /** * This sets the popup-menu on the background of the canvas based on the xy coordinate of the mouse. This method is * called after a mouse-click. * * @param x X-coordinate on screen * @param y Y-coordinate on screen */ private synchronized void setMenu(int x, int y) { try { currentMouseX = x; currentMouseY = y; final StepMeta stepMeta = transMeta.getStep(x, y, iconsize); if (stepMeta != null) { // We clicked on a Step! setCurrentStep(stepMeta); XulMenupopup menu = menuMap.get("trans-graph-entry"); try { ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransStepRightClick.id, new StepMenuExtension(this, menu)); } catch (Exception ex) { LogChannel.GENERAL.logError("Error calling TransStepRightClick extension point", ex); } if (menu != null) { List<StepMeta> selection = transMeta.getSelectedSteps(); doRightClickSelection(stepMeta, selection); int sels = selection.size(); Document doc = getXulDomContainer().getDocumentRoot(); // TODO: cache the next line (seems fast enough)? // List<PluginInterface> rowDistributionPlugins = PluginRegistry.getInstance() .getPlugins(RowDistributionPluginType.class); JfaceMenupopup customRowDistMenu = (JfaceMenupopup) doc .getElementById("trans-graph-entry-data-movement-popup"); customRowDistMenu.setDisabled(false); customRowDistMenu.removeChildren(); // Add the default round robin plugin... // Action action = new Action("RoundRobinRowDistribution", Action.AS_CHECK_BOX) { @Override public void run() { stepMeta.setRowDistribution(null); // default stepMeta.setDistributes(true); } }; boolean selected = stepMeta.isDistributes() && stepMeta.getRowDistribution() == null; action.setChecked(selected); JfaceMenuitem child = new JfaceMenuitem(null, customRowDistMenu, xulDomContainer, "Round Robin row distribution", 0, action); child.setLabel(BaseMessages.getString(PKG, "TransGraph.PopupMenu.RoundRobin")); child.setDisabled(false); child.setSelected(selected); for (int p = 0; p < rowDistributionPlugins.size(); p++) { final PluginInterface rowDistributionPlugin = rowDistributionPlugins.get(p); selected = stepMeta.isDistributes() && stepMeta.getRowDistribution() != null && stepMeta .getRowDistribution().getCode().equals(rowDistributionPlugin.getIds()[0]); action = new Action(rowDistributionPlugin.getIds()[0], Action.AS_CHECK_BOX) { @Override public void run() { try { stepMeta.setRowDistribution((RowDistributionInterface) PluginRegistry .getInstance().loadClass(rowDistributionPlugin)); } catch (Exception e) { LogChannel.GENERAL.logError("Error loading row distribution plugin class: ", e); } } }; action.setChecked(selected); child = new JfaceMenuitem(null, customRowDistMenu, xulDomContainer, rowDistributionPlugin.getName(), p + 1, action); child.setLabel(rowDistributionPlugin.getName()); child.setDisabled(false); child.setSelected(selected); } // Add the default copy rows plugin... // action = new Action("CopyRowsDistribution", Action.AS_CHECK_BOX) { @Override public void run() { stepMeta.setDistributes(false); } }; selected = !stepMeta.isDistributes(); action.setChecked(selected); child = new JfaceMenuitem(null, customRowDistMenu, xulDomContainer, "Copy rows distribution", 0, action); child.setLabel(BaseMessages.getString(PKG, "TransGraph.PopupMenu.CopyData")); child.setDisabled(false); child.setSelected(selected); JfaceMenupopup launchMenu = (JfaceMenupopup) doc .getElementById("trans-graph-entry-launch-popup"); String[] referencedObjects = stepMeta.getStepMetaInterface().getReferencedObjectDescriptions(); boolean[] enabledObjects = stepMeta.getStepMetaInterface().isReferencedObjectEnabled(); launchMenu.setDisabled(Utils.isEmpty(referencedObjects)); launchMenu.removeChildren(); int childIndex = 0; // First see if we need to add a special "active" entry (running transformation) // StepMetaInterface stepMetaInterface = stepMeta.getStepMetaInterface(); String activeReferencedObjectDescription = stepMetaInterface .getActiveReferencedObjectDescription(); if (getActiveSubtransformation(this, stepMeta) != null && activeReferencedObjectDescription != null) { action = new Action(activeReferencedObjectDescription, Action.AS_DROP_DOWN_MENU) { @Override public void run() { openMapping(stepMeta, -1); // negative by convention } }; child = new JfaceMenuitem(null, launchMenu, xulDomContainer, activeReferencedObjectDescription, childIndex++, action); child.setLabel(activeReferencedObjectDescription); child.setDisabled(false); } if (!Utils.isEmpty(referencedObjects)) { for (int i = 0; i < referencedObjects.length; i++) { final int index = i; String referencedObject = referencedObjects[i]; action = new Action(referencedObject, Action.AS_DROP_DOWN_MENU) { @Override public void run() { openMapping(stepMeta, index); } }; child = new JfaceMenuitem(null, launchMenu, xulDomContainer, referencedObject, childIndex++, action); child.setLabel(referencedObject); child.setDisabled(!enabledObjects[i]); } } initializeXulMenu(doc, selection, stepMeta); ConstUI.displayMenu(menu, canvas); } } else { final TransHopMeta hi = findHop(x, y); if (hi != null) { // We clicked on a HOP! XulMenupopup menu = menuMap.get("trans-graph-hop"); if (menu != null) { setCurrentHop(hi); XulMenuitem item = (XulMenuitem) getXulDomContainer().getDocumentRoot() .getElementById("trans-graph-hop-enabled"); if (item != null) { if (hi.isEnabled()) { item.setLabel(BaseMessages.getString(PKG, "TransGraph.PopupMenu.DisableHop")); } else { item.setLabel(BaseMessages.getString(PKG, "TransGraph.PopupMenu.EnableHop")); } } ConstUI.displayMenu(menu, canvas); } } else { // Clicked on the background: maybe we hit a note? final NotePadMeta ni = transMeta.getNote(x, y); setCurrentNote(ni); if (ni != null) { XulMenupopup menu = menuMap.get("trans-graph-note"); if (menu != null) { ConstUI.displayMenu(menu, canvas); } } else { XulMenupopup menu = menuMap.get("trans-graph-background"); if (menu != null) { final String clipcontent = spoon.fromClipboard(); XulMenuitem item = (XulMenuitem) getXulDomContainer().getDocumentRoot() .getElementById("trans-graph-background-paste"); if (item != null) { item.setDisabled(clipcontent == null); } ConstUI.displayMenu(menu, canvas); } } } } } catch (Throwable t) { // TODO: fix this: log somehow, is IGNORED for now. t.printStackTrace(); } } public void selectAll() { spoon.editSelectAll(); } public void clearSelection() { spoon.editUnselectAll(); } protected void initializeXulMenu(Document doc, List<StepMeta> selection, StepMeta stepMeta) throws KettleException { XulMenuitem item = (XulMenuitem) doc.getElementById("trans-graph-entry-newhop"); int sels = selection.size(); item.setDisabled(sels != 2); item = (XulMenuitem) doc.getElementById("trans-graph-entry-align-snap"); item.setAcceltext("ALT-HOME"); item.setLabel(BaseMessages.getString(PKG, "TransGraph.PopupMenu.SnapToGrid")); item.setAccesskey("alt-home"); item = (XulMenuitem) doc.getElementById("trans-graph-entry-open-mapping"); XulMenu men = (XulMenu) doc.getElementById(TRANS_GRAPH_ENTRY_SNIFF); men.setDisabled(trans == null || trans.isRunning() == false); item = (XulMenuitem) doc.getElementById("trans-graph-entry-sniff-input"); item.setDisabled(trans == null || trans.isRunning() == false); item = (XulMenuitem) doc.getElementById("trans-graph-entry-sniff-output"); item.setDisabled(trans == null || trans.isRunning() == false); item = (XulMenuitem) doc.getElementById("trans-graph-entry-sniff-error"); item.setDisabled(!(stepMeta.supportsErrorHandling() && stepMeta.getStepErrorMeta() != null && stepMeta.getStepErrorMeta().getTargetStep() != null && trans != null && trans.isRunning())); XulMenu aMenu = (XulMenu) doc.getElementById(TRANS_GRAPH_ENTRY_AGAIN); if (aMenu != null) { aMenu.setDisabled(sels < 2); } // item = (XulMenuitem) doc.getElementById("trans-graph-entry-data-movement-distribute"); // item.setSelected(stepMeta.isDistributes()); item = (XulMenuitem) doc.getElementById("trans-graph-entry-partitioning"); item.setDisabled(spoon.getPartitionSchemasNames(transMeta).isEmpty()); item = (XulMenuitem) doc.getElementById("trans-graph-entry-data-movement-copy"); item.setSelected(!stepMeta.isDistributes()); item = (XulMenuitem) doc.getElementById("trans-graph-entry-hide"); item.setDisabled(!(stepMeta.isDrawn() && !transMeta.isAnySelectedStepUsedInTransHops())); item = (XulMenuitem) doc.getElementById("trans-graph-entry-detach"); item.setDisabled(!transMeta.isStepUsedInTransHops(stepMeta)); item = (XulMenuitem) doc.getElementById("trans-graph-entry-errors"); item.setDisabled(!stepMeta.supportsErrorHandling()); } private boolean checkNumberOfCopies(TransMeta transMeta, StepMeta stepMeta) { boolean enabled = true; List<StepMeta> prevSteps = transMeta.findPreviousSteps(stepMeta); for (StepMeta prevStep : prevSteps) { // See what the target steps are. // If one of the target steps is our original step, we can't start multiple copies // String[] targetSteps = prevStep.getStepMetaInterface().getStepIOMeta().getTargetStepnames(); if (targetSteps != null) { for (int t = 0; t < targetSteps.length && enabled; t++) { if (!Utils.isEmpty(targetSteps[t]) && targetSteps[t].equalsIgnoreCase(stepMeta.getName())) { enabled = false; } } } } return enabled; } private AreaOwner setToolTip(int x, int y, int screenX, int screenY) { AreaOwner subject = null; if (!spoon.getProperties().showToolTips()) { return subject; } canvas.setToolTipText(null); String newTip = null; Image tipImage = null; final TransHopMeta hi = findHop(x, y); // check the area owner list... // StringBuilder tip = new StringBuilder(); AreaOwner areaOwner = getVisibleAreaOwner(x, y); AreaType areaType = null; if (areaOwner != null && areaOwner.getAreaType() != null) { areaType = areaOwner.getAreaType(); switch (areaType) { case REMOTE_INPUT_STEP: StepMeta step = (StepMeta) areaOwner.getParent(); tip.append("Remote input steps:").append(Const.CR).append("-----------------------") .append(Const.CR); for (RemoteStep remoteStep : step.getRemoteInputSteps()) { tip.append(remoteStep.toString()).append(Const.CR); } break; case REMOTE_OUTPUT_STEP: step = (StepMeta) areaOwner.getParent(); tip.append("Remote output steps:").append(Const.CR).append("-----------------------") .append(Const.CR); for (RemoteStep remoteStep : step.getRemoteOutputSteps()) { tip.append(remoteStep.toString()).append(Const.CR); } break; case STEP_PARTITIONING: step = (StepMeta) areaOwner.getParent(); tip.append("Step partitioning:").append(Const.CR).append("-----------------------") .append(Const.CR); tip.append(step.getStepPartitioningMeta().toString()).append(Const.CR); if (step.getTargetStepPartitioningMeta() != null) { tip.append(Const.CR).append(Const.CR) .append("TARGET: " + step.getTargetStepPartitioningMeta().toString()).append(Const.CR); } break; case STEP_ERROR_ICON: String log = (String) areaOwner.getParent(); tip.append(log); tipImage = GUIResource.getInstance().getImageStepError(); break; case STEP_ERROR_RED_ICON: String redLog = (String) areaOwner.getParent(); tip.append(redLog); tipImage = GUIResource.getInstance().getImageRedStepError(); break; case HOP_COPY_ICON: step = (StepMeta) areaOwner.getParent(); tip.append(BaseMessages.getString(PKG, "TransGraph.Hop.Tooltip.HopTypeCopy", step.getName(), Const.CR)); tipImage = GUIResource.getInstance().getImageCopyHop(); break; case ROW_DISTRIBUTION_ICON: step = (StepMeta) areaOwner.getParent(); tip.append(BaseMessages.getString(PKG, "TransGraph.Hop.Tooltip.RowDistribution", step.getName(), step.getRowDistribution() == null ? "" : step.getRowDistribution().getDescription())); tip.append(Const.CR); tipImage = GUIResource.getInstance().getImageBalance(); break; case HOP_INFO_ICON: StepMeta from = (StepMeta) areaOwner.getParent(); StepMeta to = (StepMeta) areaOwner.getOwner(); tip.append(BaseMessages.getString(PKG, "TransGraph.Hop.Tooltip.HopTypeInfo", to.getName(), from.getName(), Const.CR)); tipImage = GUIResource.getInstance().getImageInfoHop(); break; case HOP_ERROR_ICON: from = (StepMeta) areaOwner.getParent(); to = (StepMeta) areaOwner.getOwner(); areaOwner.getOwner(); tip.append(BaseMessages.getString(PKG, "TransGraph.Hop.Tooltip.HopTypeError", from.getName(), to.getName(), Const.CR)); tipImage = GUIResource.getInstance().getImageErrorHop(); break; case HOP_INFO_STEP_COPIES_ERROR: from = (StepMeta) areaOwner.getParent(); to = (StepMeta) areaOwner.getOwner(); tip.append(BaseMessages.getString(PKG, "TransGraph.Hop.Tooltip.InfoStepCopies", from.getName(), to.getName(), Const.CR)); tipImage = GUIResource.getInstance().getImageStepError(); break; case STEP_INPUT_HOP_ICON: // StepMeta subjectStep = (StepMeta) (areaOwner.getParent()); tip.append(BaseMessages.getString(PKG, "TransGraph.StepInputConnector.Tooltip")); tipImage = GUIResource.getInstance().getImageHopInput(); break; case STEP_OUTPUT_HOP_ICON: // subjectStep = (StepMeta) (areaOwner.getParent()); tip.append(BaseMessages.getString(PKG, "TransGraph.StepOutputConnector.Tooltip")); tipImage = GUIResource.getInstance().getImageHopOutput(); break; case STEP_INFO_HOP_ICON: // subjectStep = (StepMeta) (areaOwner.getParent()); // StreamInterface stream = (StreamInterface) areaOwner.getOwner(); StepIOMetaInterface ioMeta = (StepIOMetaInterface) areaOwner.getOwner(); tip.append(BaseMessages.getString(PKG, "TransGraph.StepInfoConnector.Tooltip") + Const.CR + ioMeta.toString()); tipImage = GUIResource.getInstance().getImageHopOutput(); break; case STEP_TARGET_HOP_ICON: StreamInterface stream = (StreamInterface) areaOwner.getOwner(); tip.append(stream.getDescription()); tipImage = GUIResource.getInstance().getImageHopOutput(); break; case STEP_ERROR_HOP_ICON: StepMeta stepMeta = (StepMeta) areaOwner.getParent(); if (stepMeta.supportsErrorHandling()) { tip.append(BaseMessages.getString(PKG, "TransGraph.StepSupportsErrorHandling.Tooltip")); } else { tip.append(BaseMessages.getString(PKG, "TransGraph.StepDoesNotSupportsErrorHandling.Tooltip")); } tipImage = GUIResource.getInstance().getImageHopOutput(); break; case STEP_EDIT_ICON: tip.append(BaseMessages.getString(PKG, "TransGraph.EditStep.Tooltip")); tipImage = GUIResource.getInstance().getImageEdit(); break; case STEP_INJECT_ICON: Object injection = areaOwner.getOwner(); if (injection != null) { tip.append(BaseMessages.getString(PKG, "TransGraph.StepInjectionSupported.Tooltip")); } else { tip.append(BaseMessages.getString(PKG, "TransGraph.StepInjectionNotSupported.Tooltip")); } tipImage = GUIResource.getInstance().getImageInject(); break; case STEP_MENU_ICON: tip.append(BaseMessages.getString(PKG, "TransGraph.ShowMenu.Tooltip")); tipImage = GUIResource.getInstance().getImageContextMenu(); break; case STEP_ICON: StepMeta iconStepMeta = (StepMeta) areaOwner.getOwner(); if (iconStepMeta.isDeprecated()) { // only need tooltip if step is deprecated tip.append(BaseMessages.getString(PKG, "TransGraph.DeprecatedStep.Tooltip.Title")) .append(Const.CR); String tipNext = BaseMessages.getString(PKG, "TransGraph.DeprecatedStep.Tooltip.Message1", iconStepMeta.getName()); int length = tipNext.length() + 5; for (int i = 0; i < length; i++) { tip.append("-"); } tip.append(Const.CR).append(tipNext).append(Const.CR); tip.append(BaseMessages.getString(PKG, "TransGraph.DeprecatedStep.Tooltip.Message2")); if (!Utils.isEmpty(iconStepMeta.getSuggestion()) && !(iconStepMeta.getSuggestion().startsWith("!") && iconStepMeta.getSuggestion().endsWith("!"))) { tip.append(" "); tip.append(BaseMessages.getString(PKG, "TransGraph.DeprecatedStep.Tooltip.Message3", iconStepMeta.getSuggestion())); } tipImage = GUIResource.getInstance().getImageDeprecated(); toolTip.setHideDelay(TOOLTIP_HIDE_DELAY_LONG); } break; default: break; } } if (hi != null && tip.length() == 0) { // We clicked on a HOP! // Set the tooltip for the hop: tip.append(Const.CR).append(BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo")) .append(newTip = hi.toString()).append(Const.CR); } if (tip.length() == 0) { newTip = null; } else { newTip = tip.toString(); } if (newTip == null) { toolTip.hide(); if (hi != null) { // We clicked on a HOP! // Set the tooltip for the hop: newTip = BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo") + Const.CR + BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo.SourceStep") + " " + hi.getFromStep().getName() + Const.CR + BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo.TargetStep") + " " + hi.getToStep().getName() + Const.CR + BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo.Status") + " " + (hi.isEnabled() ? BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo.Enable") : BaseMessages.getString(PKG, "TransGraph.Dialog.HopInfo.Disable")); toolTip.setText(newTip); if (hi.isEnabled()) { toolTip.setImage(GUIResource.getInstance().getImageHop()); } else { toolTip.setImage(GUIResource.getInstance().getImageDisabledHop()); } toolTip.show(new org.eclipse.swt.graphics.Point(screenX, screenY)); } else { newTip = null; } } else if (!newTip.equalsIgnoreCase(getToolTipText())) { Image tooltipImage = null; if (tipImage != null) { tooltipImage = tipImage; } else { tooltipImage = GUIResource.getInstance().getImageSpoonLow(); } showTooltip(newTip, tooltipImage, screenX, screenY); } if (areaOwner != null && areaOwner.getExtensionAreaType() != null) { try { TransPainterFlyoutTooltipExtension extension = new TransPainterFlyoutTooltipExtension(areaOwner, this, new Point(screenX, screenY)); ExtensionPointHandler.callExtensionPoint(LogChannel.GENERAL, KettleExtensionPoint.TransPainterFlyoutTooltip.id, extension); } catch (Exception e) { LogChannel.GENERAL.logError("Error calling extension point(s) for the transformation painter step", e); } } return subject; } public void showTooltip(String label, Image image, int screenX, int screenY) { toolTip.setImage(image); toolTip.setText(label); toolTip.hide(); toolTip.show(new org.eclipse.swt.graphics.Point(screenX, screenY)); } public AreaOwner getVisibleAreaOwner(int x, int y) { for (int i = areaOwners.size() - 1; i >= 0; i--) { AreaOwner areaOwner = areaOwners.get(i); if (areaOwner.contains(x, y)) { return areaOwner; } } return null; } public void delSelected(StepMeta stMeta) { List<StepMeta> selection = transMeta.getSelectedSteps(); if (selection.size() == 0) { spoon.delStep(transMeta, stMeta); return; } if (currentStep != null && selection.contains(currentStep)) { currentStep = null; transPreviewDelegate.setSelectedStep(currentStep); transPreviewDelegate.refreshView(); for (StepSelectionListener listener : currentStepListeners) { listener.onUpdateSelection(currentStep); } } StepMeta[] steps = selection.toArray(new StepMeta[selection.size()]); spoon.delSteps(transMeta, steps); } public void editDescription(StepMeta stepMeta) { String title = BaseMessages.getString(PKG, "TransGraph.Dialog.StepDescription.Title"); String message = BaseMessages.getString(PKG, "TransGraph.Dialog.StepDescription.Message"); EnterTextDialog dd = new EnterTextDialog(shell, title, message, stepMeta.getDescription()); String d = dd.open(); if (d != null) { stepMeta.setDescription(d); stepMeta.setChanged(); spoon.setShellText(); } } /** * Display the input- or outputfields for a step. * * @param stepMeta The step (it's metadata) to query * @param before set to true if you want to have the fields going INTO the step, false if you want to see all the * fields that exit the step. */ private void inputOutputFields(StepMeta stepMeta, boolean before) { spoon.refreshGraph(); transMeta.setRepository(spoon.rep); SearchFieldsProgressDialog op = new SearchFieldsProgressDialog(transMeta, stepMeta, before); boolean alreadyThrownError = false; try { final ProgressMonitorDialog pmd = new ProgressMonitorDialog(shell); // Run something in the background to cancel active database queries, forecably if needed! Runnable run = new Runnable() { @Override public void run() { IProgressMonitor monitor = pmd.getProgressMonitor(); while (pmd.getShell() == null || (!pmd.getShell().isDisposed() && !monitor.isCanceled())) { try { Thread.sleep(250); } catch (InterruptedException e) { // Ignore } } if (monitor.isCanceled()) { // Disconnect and see what happens! try { transMeta.cancelQueries(); } catch (Exception e) { // Ignore } } } }; // Dump the cancel looker in the background! new Thread(run).start(); pmd.run(true, true, op); } catch (InvocationTargetException e) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransGraph.Dialog.GettingFields.Title"), BaseMessages.getString(PKG, "TransGraph.Dialog.GettingFields.Message"), e); alreadyThrownError = true; } catch (InterruptedException e) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransGraph.Dialog.GettingFields.Title"), BaseMessages.getString(PKG, "TransGraph.Dialog.GettingFields.Message"), e); alreadyThrownError = true; } RowMetaInterface fields = op.getFields(); if (fields != null && fields.size() > 0) { StepFieldsDialog sfd = new StepFieldsDialog(shell, transMeta, SWT.NONE, stepMeta.getName(), fields); String sn = (String) sfd.open(); if (sn != null) { StepMeta esi = transMeta.findStep(sn); if (esi != null) { editStep(esi); } } } else { if (!alreadyThrownError) { modalMessageDialog(getString("TransGraph.Dialog.CouldntFindFields.Title"), getString("TransGraph.Dialog.CouldntFindFields.Message"), SWT.OK | SWT.ICON_INFORMATION); } } } public void paintControl(PaintEvent e) { Point area = getArea(); if (area.x == 0 || area.y == 0) { return; // nothing to do! } Display disp = shell.getDisplay(); Image img = getTransformationImage(disp, area.x, area.y, magnification); e.gc.drawImage(img, 0, 0); if (transMeta.nrSteps() == 0) { e.gc.setForeground(GUIResource.getInstance().getColorCrystalTextPentaho()); e.gc.setFont(GUIResource.getInstance().getFontMedium()); Image pentahoImage = GUIResource.getInstance().getImageTransCanvas(); int leftPosition = (area.x - pentahoImage.getBounds().width) / 2; int topPosition = (area.y - pentahoImage.getBounds().height) / 2; e.gc.drawImage(pentahoImage, leftPosition, topPosition); } img.dispose(); // spoon.setShellText(); } public Image getTransformationImage(Device device, int x, int y, float magnificationFactor) { GCInterface gc = new SWTGC(device, new Point(x, y), iconsize); int gridSize = PropsUI.getInstance().isShowCanvasGridEnabled() ? PropsUI.getInstance().getCanvasGridSize() : 1; TransPainter transPainter = new TransPainter(gc, transMeta, new Point(x, y), new SwtScrollBar(hori), new SwtScrollBar(vert), candidate, drop_candidate, selectionRegion, areaOwners, mouseOverSteps, PropsUI.getInstance().getIconSize(), PropsUI.getInstance().getLineWidth(), gridSize, PropsUI.getInstance().getShadowSize(), PropsUI.getInstance().isAntiAliasingEnabled(), PropsUI.getInstance().getNoteFont().getName(), PropsUI.getInstance().getNoteFont().getHeight(), trans, PropsUI.getInstance().isIndicateSlowTransStepsEnabled()); transPainter.setMagnification(magnificationFactor); transPainter.setStepLogMap(stepLogMap); transPainter.setStartHopStep(startHopStep); transPainter.setEndHopLocation(endHopLocation); transPainter.setNoInputStep(noInputStep); transPainter.setEndHopStep(endHopStep); transPainter.setCandidateHopType(candidateHopType); transPainter.setStartErrorHopStep(startErrorHopStep); transPainter.setShowTargetStreamsStep(showTargetStreamsStep); transPainter.buildTransformationImage(); Image img = (Image) gc.getImage(); gc.dispose(); return img; } @Override protected Point getOffset() { Point area = getArea(); Point max = transMeta.getMaximum(); Point thumb = getThumb(area, max); return getOffset(thumb, area); } private void editStep(StepMeta stepMeta) { spoon.editStep(transMeta, stepMeta); } private void editNote(NotePadMeta ni) { NotePadMeta before = (NotePadMeta) ni.clone(); String title = BaseMessages.getString(PKG, "TransGraph.Dialog.EditNote.Title"); NotePadDialog dd = new NotePadDialog(transMeta, shell, title, ni); NotePadMeta n = dd.open(); if (n != null) { ni.setChanged(); ni.setNote(n.getNote()); ni.setFontName(n.getFontName()); ni.setFontSize(n.getFontSize()); ni.setFontBold(n.isFontBold()); ni.setFontItalic(n.isFontItalic()); // font color ni.setFontColorRed(n.getFontColorRed()); ni.setFontColorGreen(n.getFontColorGreen()); ni.setFontColorBlue(n.getFontColorBlue()); // background color ni.setBackGroundColorRed(n.getBackGroundColorRed()); ni.setBackGroundColorGreen(n.getBackGroundColorGreen()); ni.setBackGroundColorBlue(n.getBackGroundColorBlue()); // border color ni.setBorderColorRed(n.getBorderColorRed()); ni.setBorderColorGreen(n.getBorderColorGreen()); ni.setBorderColorBlue(n.getBorderColorBlue()); ni.setDrawShadow(n.isDrawShadow()); ni.width = ConstUI.NOTE_MIN_SIZE; ni.height = ConstUI.NOTE_MIN_SIZE; NotePadMeta after = (NotePadMeta) ni.clone(); spoon.addUndoChange(transMeta, new NotePadMeta[] { before }, new NotePadMeta[] { after }, new int[] { transMeta.indexOfNote(ni) }); spoon.refreshGraph(); } } private void editHop(TransHopMeta transHopMeta) { String name = transHopMeta.toString(); if (log.isDebug()) { log.logDebug(BaseMessages.getString(PKG, "TransGraph.Logging.EditingHop") + name); } spoon.editHop(transMeta, transHopMeta); } private void newHop() { List<StepMeta> selection = transMeta.getSelectedSteps(); if (selection.size() == 2) { StepMeta fr = selection.get(0); StepMeta to = selection.get(1); spoon.newHop(transMeta, fr, to); } } private boolean pointOnLine(int x, int y, int[] line) { int dx, dy; int pm = HOP_SEL_MARGIN / 2; boolean retval = false; for (dx = -pm; dx <= pm && !retval; dx++) { for (dy = -pm; dy <= pm && !retval; dy++) { retval = pointOnThinLine(x + dx, y + dy, line); } } return retval; } private boolean pointOnThinLine(int x, int y, int[] line) { int x1 = line[0]; int y1 = line[1]; int x2 = line[2]; int y2 = line[3]; // Not in the square formed by these 2 points: ignore! // CHECKSTYLE:LineLength:OFF if (!(((x >= x1 && x <= x2) || (x >= x2 && x <= x1)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1)))) { return false; } double angle_line = Math.atan2(y2 - y1, x2 - x1) + Math.PI; double angle_point = Math.atan2(y - y1, x - x1) + Math.PI; // Same angle, or close enough? if (angle_point >= angle_line - 0.01 && angle_point <= angle_line + 0.01) { return true; } return false; } private SnapAllignDistribute createSnapAllignDistribute() { List<StepMeta> selection = transMeta.getSelectedSteps(); int[] indices = transMeta.getStepIndexes(selection); return new SnapAllignDistribute(transMeta, selection, indices, spoon, this); } public void snaptogrid() { snaptogrid(ConstUI.GRID_SIZE); } private void snaptogrid(int size) { createSnapAllignDistribute().snaptogrid(size); } public void allignleft() { createSnapAllignDistribute().allignleft(); } public void allignright() { createSnapAllignDistribute().allignright(); } public void alligntop() { createSnapAllignDistribute().alligntop(); } public void allignbottom() { createSnapAllignDistribute().allignbottom(); } public void distributehorizontal() { createSnapAllignDistribute().distributehorizontal(); } public void distributevertical() { createSnapAllignDistribute().distributevertical(); } private void detach(StepMeta stepMeta) { for (int i = transMeta.nrTransHops() - 1; i >= 0; i--) { TransHopMeta hop = transMeta.getTransHop(i); if (stepMeta.equals(hop.getFromStep()) || stepMeta.equals(hop.getToStep())) { // Step is connected with a hop, remove this hop. // spoon.addUndoNew(transMeta, new TransHopMeta[] { hop }, new int[] { i }); transMeta.removeTransHop(i); } } /* * TransHopMeta hfrom = transMeta.findTransHopTo(stepMeta); TransHopMeta hto = transMeta.findTransHopFrom(stepMeta); * * if (hfrom != null && hto != null) { if (transMeta.findTransHop(hfrom.getFromStep(), hto.getToStep()) == null) { * TransHopMeta hnew = new TransHopMeta(hfrom.getFromStep(), hto.getToStep()); transMeta.addTransHop(hnew); * spoon.addUndoNew(transMeta, new TransHopMeta[] { hnew }, new int[] { transMeta.indexOfTransHop(hnew) }); * spoon.refreshTree(); } } if (hfrom != null) { int fromidx = transMeta.indexOfTransHop(hfrom); if (fromidx >= 0) { * transMeta.removeTransHop(fromidx); spoon.refreshTree(); } } if (hto != null) { int toidx = * transMeta.indexOfTransHop(hto); if (toidx >= 0) { transMeta.removeTransHop(toidx); spoon.refreshTree(); } } */ spoon.refreshTree(); redraw(); } // Preview the selected steps... public void preview() { spoon.previewTransformation(); } public void newProps() { iconsize = spoon.props.getIconSize(); } @Override public EngineMetaInterface getMeta() { return transMeta; } /** * @param transMeta the transMeta to set * @return the transMeta / public TransMeta getTransMeta() { return transMeta; } * <p/> * /** */ public void setTransMeta(TransMeta transMeta) { this.transMeta = transMeta; } // Change of step, connection, hop or note... public void addUndoPosition(Object[] obj, int[] pos, Point[] prev, Point[] curr) { addUndoPosition(obj, pos, prev, curr, false); } // Change of step, connection, hop or note... public void addUndoPosition(Object[] obj, int[] pos, Point[] prev, Point[] curr, boolean nextAlso) { // It's better to store the indexes of the objects, not the objects itself! transMeta.addUndo(obj, null, pos, prev, curr, TransMeta.TYPE_UNDO_POSITION, nextAlso); spoon.setUndoMenu(transMeta); } @Override public boolean applyChanges() throws KettleException { return spoon.saveToFile(transMeta); } @Override public boolean canBeClosed() { return !transMeta.hasChanged(); } @Override public TransMeta getManagedObject() { return transMeta; } @Override public boolean hasContentChanged() { return transMeta.hasChanged(); } public List<CheckResultInterface> getRemarks() { return remarks; } public void setRemarks(List<CheckResultInterface> remarks) { this.remarks = remarks; } public List<DatabaseImpact> getImpact() { return impact; } public void setImpact(List<DatabaseImpact> impact) { this.impact = impact; } public boolean isImpactFinished() { return impactFinished; } public void setImpactFinished(boolean impactHasRun) { this.impactFinished = impactHasRun; } /** * @return the lastMove */ public Point getLastMove() { return lastMove; } public static boolean editProperties(TransMeta transMeta, Spoon spoon, Repository rep, boolean allowDirectoryChange) { return editProperties(transMeta, spoon, rep, allowDirectoryChange, null); } public static boolean editProperties(TransMeta transMeta, Spoon spoon, Repository rep, boolean allowDirectoryChange, TransDialog.Tabs currentTab) { if (transMeta == null) { return false; } TransDialog tid = new TransDialog(spoon.getShell(), SWT.NONE, transMeta, rep, currentTab); tid.setDirectoryChangeAllowed(allowDirectoryChange); TransMeta ti = tid.open(); // Load shared objects // if (tid.isSharedObjectsFileChanged()) { try { SharedObjects sharedObjects = rep != null ? rep.readTransSharedObjects(transMeta) : transMeta.readSharedObjects(); spoon.sharedObjectsFileMap.put(sharedObjects.getFilename(), sharedObjects); } catch (KettleException e) { // CHECKSTYLE:LineLength:OFF new ErrorDialog(spoon.getShell(), BaseMessages.getString(PKG, "Spoon.Dialog.ErrorReadingSharedObjects.Title"), BaseMessages.getString(PKG, "Spoon.Dialog.ErrorReadingSharedObjects.Message", spoon.makeTabName(transMeta, true)), e); } // If we added properties, add them to the variables too, so that they appear in the CTRL-SPACE variable // completion. // spoon.setParametersAsVariablesInUI(transMeta, transMeta); spoon.refreshTree(); spoon.delegates.tabs.renameTabs(); // cheap operation, might as will do it anyway } spoon.setShellText(); return ti != null; } public void openFile() { spoon.openFile(); } public void saveFile() throws KettleException { spoon.saveFile(); } public void saveFileAs() throws KettleException { spoon.saveFileAs(); } public void saveXMLFileToVfs() { spoon.saveXMLFileToVfs(); } public void printFile() { spoon.printFile(); } public void runTransformation() { spoon.runFile(); } public void runOptionsTransformation() { spoon.runOptionsFile(); } public void pauseTransformation() { pauseResume(); } public void stopTransformation() { stop(); } public void previewFile() { spoon.previewFile(); } public void debugFile() { spoon.debugFile(); } public void transReplay() { spoon.replayTransformation(); } public void checkTrans() { spoon.checkTrans(); } public void analyseImpact() { spoon.analyseImpact(); } public void getSQL() { spoon.getSQL(); } public void exploreDatabase() { spoon.exploreDatabase(); } public boolean isExecutionResultsPaneVisible() { return extraViewComposite != null && !extraViewComposite.isDisposed(); } public void showExecutionResults() { if (isExecutionResultsPaneVisible()) { disposeExtraView(); } else { addAllTabs(); } } public void browseVersionHistory() { try { if (spoon.rep.exists(transMeta.getName(), transMeta.getRepositoryDirectory(), RepositoryObjectType.TRANSFORMATION)) { RepositoryRevisionBrowserDialogInterface dialog = RepositoryExplorerDialog .getVersionBrowserDialog(shell, spoon.rep, transMeta); String versionLabel = dialog.open(); if (versionLabel != null) { spoon.loadObjectFromRepository(transMeta.getName(), transMeta.getRepositoryElementType(), transMeta.getRepositoryDirectory(), versionLabel); } } else { modalMessageDialog(getString("TransGraph.Sorry"), getString("TransGraph.VersionBrowser.CantFindInRepo"), SWT.CLOSE | SWT.ICON_ERROR); } } catch (Exception e) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransGraph.VersionBrowserException.Title"), BaseMessages.getString(PKG, "TransGraph.VersionBrowserException.Message"), e); } } /** * If the extra tab view at the bottom is empty, we close it. */ public void checkEmptyExtraView() { if (extraViewTabFolder.getItemCount() == 0) { disposeExtraView(); } } private void disposeExtraView() { extraViewComposite.dispose(); sashForm.layout(); sashForm.setWeights(new int[] { 100, }); XulToolbarbutton button = (XulToolbarbutton) toolbar.getElementById("trans-show-results"); button.setTooltiptext(BaseMessages.getString(PKG, "Spoon.Tooltip.ShowExecutionResults")); ToolItem toolItem = (ToolItem) button.getManagedObject(); toolItem.setImage(GUIResource.getInstance().getImageShowResults()); } private void minMaxExtraView() { // What is the state? // boolean maximized = sashForm.getMaximizedControl() != null; if (maximized) { // Minimize // sashForm.setMaximizedControl(null); minMaxButton.setImage(GUIResource.getInstance().getImageMaximizePanel()); minMaxButton.setToolTipText( BaseMessages.getString(PKG, "TransGraph.ExecutionResultsPanel.MaxButton.Tooltip")); } else { // Maximize // sashForm.setMaximizedControl(extraViewComposite); minMaxButton.setImage(GUIResource.getInstance().getImageMinimizePanel()); minMaxButton.setToolTipText( BaseMessages.getString(PKG, "TransGraph.ExecutionResultsPanel.MinButton.Tooltip")); } } /** * @return the toolbar */ public XulToolbar getToolbar() { return toolbar; } /** * @param toolbar the toolbar to set */ public void setToolbar(XulToolbar toolbar) { this.toolbar = toolbar; } private Label closeButton; private Label minMaxButton; /** * Add an extra view to the main composite SashForm */ public void addExtraView() { extraViewComposite = new Composite(sashForm, SWT.NONE); FormLayout extraCompositeFormLayout = new FormLayout(); extraCompositeFormLayout.marginWidth = 2; extraCompositeFormLayout.marginHeight = 2; extraViewComposite.setLayout(extraCompositeFormLayout); // Put a close and max button to the upper right corner... // closeButton = new Label(extraViewComposite, SWT.NONE); closeButton.setImage(GUIResource.getInstance().getImageClosePanel()); closeButton.setToolTipText( BaseMessages.getString(PKG, "TransGraph.ExecutionResultsPanel.CloseButton.Tooltip")); FormData fdClose = new FormData(); fdClose.right = new FormAttachment(100, 0); fdClose.top = new FormAttachment(0, 0); closeButton.setLayoutData(fdClose); closeButton.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { disposeExtraView(); } }); minMaxButton = new Label(extraViewComposite, SWT.NONE); minMaxButton.setImage(GUIResource.getInstance().getImageMaximizePanel()); minMaxButton .setToolTipText(BaseMessages.getString(PKG, "TransGraph.ExecutionResultsPanel.MaxButton.Tooltip")); FormData fdMinMax = new FormData(); fdMinMax.right = new FormAttachment(closeButton, -Const.MARGIN); fdMinMax.top = new FormAttachment(0, 0); minMaxButton.setLayoutData(fdMinMax); minMaxButton.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { minMaxExtraView(); } }); // Add a label at the top: Results // Label wResultsLabel = new Label(extraViewComposite, SWT.LEFT); wResultsLabel.setFont(GUIResource.getInstance().getFontMediumBold()); wResultsLabel.setBackground(GUIResource.getInstance().getColorWhite()); wResultsLabel.setText(BaseMessages.getString(PKG, "TransLog.ResultsPanel.NameLabel")); FormData fdResultsLabel = new FormData(); fdResultsLabel.left = new FormAttachment(0, 0); fdResultsLabel.right = new FormAttachment(minMaxButton, -Const.MARGIN); fdResultsLabel.top = new FormAttachment(0, 0); wResultsLabel.setLayoutData(fdResultsLabel); // Add a tab folder ... // extraViewTabFolder = new CTabFolder(extraViewComposite, SWT.MULTI); spoon.props.setLook(extraViewTabFolder, Props.WIDGET_STYLE_TAB); extraViewTabFolder.addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(MouseEvent arg0) { if (sashForm.getMaximizedControl() == null) { sashForm.setMaximizedControl(extraViewComposite); } else { sashForm.setMaximizedControl(null); } } }); FormData fdTabFolder = new FormData(); fdTabFolder.left = new FormAttachment(0, 0); fdTabFolder.right = new FormAttachment(100, 0); fdTabFolder.top = new FormAttachment(wResultsLabel, Const.MARGIN); fdTabFolder.bottom = new FormAttachment(100, 0); extraViewTabFolder.setLayoutData(fdTabFolder); sashForm.setWeights(new int[] { 60, 40, }); } /** * @deprecated Deprecated as of 8.0. Seems unused; will be to remove in 8.1 (ccaspanello) */ @Deprecated public void checkErrors() { if (trans != null) { if (!trans.isFinished()) { if (trans.getErrors() != 0) { trans.killAll(); } } } } public synchronized void start(TransExecutionConfiguration executionConfiguration) throws KettleException { // Auto save feature... handleTransMetaChanges(transMeta); if (((transMeta.getName() != null && transMeta.getObjectId() != null && spoon.rep != null) || // Repository // available & // name / id set (transMeta.getFilename() != null && spoon.rep == null) // No repository & filename set ) && !transMeta.hasChanged() // Didn't change ) { if (trans == null || !running) { try { // Set the requested logging level.. // DefaultLogLevel.setLogLevel(executionConfiguration.getLogLevel()); transMeta.injectVariables(executionConfiguration.getVariables()); // Set the named parameters Map<String, String> paramMap = executionConfiguration.getParams(); Set<String> keys = paramMap.keySet(); for (String key : keys) { transMeta.setParameterValue(key, Const.NVL(paramMap.get(key), "")); } transMeta.activateParameters(); // Do we need to clear the log before running? // if (executionConfiguration.isClearingLog()) { transLogDelegate.clearLog(); } // Also make sure to clear the log entries in the central log store & registry // if (trans != null) { KettleLogStore.discardLines(trans.getLogChannelId(), true); } // Important: even though transMeta is passed to the Trans constructor, it is not the same object as is in // memory // To be able to completely test this, we need to run it as we would normally do in pan // trans = new TransSupplier(transMeta, log, this::createLegacyTrans).get(); trans.setRepository(spoon.getRepository()); trans.setMetaStore(spoon.getMetaStore()); String spoonLogObjectId = UUID.randomUUID().toString(); SimpleLoggingObject spoonLoggingObject = new SimpleLoggingObject("SPOON", LoggingObjectType.SPOON, null); spoonLoggingObject.setContainerObjectId(spoonLogObjectId); spoonLoggingObject.setLogLevel(executionConfiguration.getLogLevel()); trans.setParent(spoonLoggingObject); trans.setLogLevel(executionConfiguration.getLogLevel()); trans.setReplayDate(executionConfiguration.getReplayDate()); trans.setRepository(executionConfiguration.getRepository()); trans.setMonitored(true); log.logBasic(BaseMessages.getString(PKG, "TransLog.Log.TransformationOpened")); } catch (KettleException e) { trans = null; new ErrorDialog(shell, BaseMessages.getString(PKG, "TransLog.Dialog.ErrorOpeningTransformation.Title"), BaseMessages.getString(PKG, "TransLog.Dialog.ErrorOpeningTransformation.Message"), e); } if (trans != null) { Map<String, String> arguments = executionConfiguration.getArguments(); final String[] args; if (arguments != null) { args = convertArguments(arguments); } else { args = null; } log.logMinimal(BaseMessages.getString(PKG, "TransLog.Log.LaunchingTransformation") + trans.getTransMeta().getName() + "]..."); trans.setSafeModeEnabled(executionConfiguration.isSafeModeEnabled()); trans.setGatheringMetrics(executionConfiguration.isGatheringMetrics()); // Launch the step preparation in a different thread. // That way Spoon doesn't block anymore and that way we can follow the progress of the initialization // final Thread parentThread = Thread.currentThread(); shell.getDisplay().asyncExec(new Runnable() { @Override public void run() { addAllTabs(); prepareTrans(parentThread, args); } }); log.logMinimal(BaseMessages.getString(PKG, "TransLog.Log.StartedExecutionOfTransformation")); setControlStates(); } } else { modalMessageDialog(getString("TransLog.Dialog.DoNoStartTransformationTwice.Title"), getString("TransLog.Dialog.DoNoStartTransformationTwice.Message"), SWT.OK | SWT.ICON_WARNING); } } else { if (transMeta.hasChanged()) { showSaveFileMessage(); } else if (spoon.rep != null && transMeta.getName() == null) { modalMessageDialog(getString("TransLog.Dialog.GiveTransformationANameBeforeRunning.Title"), getString("TransLog.Dialog.GiveTransformationANameBeforeRunning.Message"), SWT.OK | SWT.ICON_WARNING); } else { modalMessageDialog(getString("TransLog.Dialog.SaveTransformationBeforeRunning2.Title"), getString("TransLog.Dialog.SaveTransformationBeforeRunning2.Message"), SWT.OK | SWT.ICON_WARNING); } } } public void showSaveFileMessage() { modalMessageDialog(getString("TransLog.Dialog.SaveTransformationBeforeRunning.Title"), getString("TransLog.Dialog.SaveTransformationBeforeRunning.Message"), SWT.OK | SWT.ICON_WARNING); } public void addAllTabs() { CTabItem tabItemSelection = null; if (extraViewTabFolder != null && !extraViewTabFolder.isDisposed()) { tabItemSelection = extraViewTabFolder.getSelection(); } transHistoryDelegate.addTransHistory(); transLogDelegate.addTransLog(); transGridDelegate.addTransGrid(); transPerfDelegate.addTransPerf(); transMetricsDelegate.addTransMetrics(); transPreviewDelegate.addTransPreview(); List<SpoonUiExtenderPluginInterface> relevantExtenders = SpoonUiExtenderPluginType.getInstance() .getRelevantExtenders(TransGraph.class, LOAD_TAB); for (SpoonUiExtenderPluginInterface relevantExtender : relevantExtenders) { relevantExtender.uiEvent(this, LOAD_TAB); } if (tabItemSelection != null) { extraViewTabFolder.setSelection(tabItemSelection); } else { extraViewTabFolder.setSelection(transGridDelegate.getTransGridTab()); } XulToolbarbutton button = (XulToolbarbutton) toolbar.getElementById("trans-show-results"); button.setTooltiptext(BaseMessages.getString(PKG, "Spoon.Tooltip.HideExecutionResults")); ToolItem toolItem = (ToolItem) button.getManagedObject(); toolItem.setImage(GUIResource.getInstance().getImageHideResults()); } public synchronized void debug(TransExecutionConfiguration executionConfiguration, TransDebugMeta transDebugMeta) { if (!running) { try { this.lastTransDebugMeta = transDebugMeta; log.setLogLevel(executionConfiguration.getLogLevel()); if (log.isDetailed()) { log.logDetailed(BaseMessages.getString(PKG, "TransLog.Log.DoPreview")); } String[] args = null; Map<String, String> arguments = executionConfiguration.getArguments(); if (arguments != null) { args = convertArguments(arguments); } transMeta.injectVariables(executionConfiguration.getVariables()); // Set the named parameters Map<String, String> paramMap = executionConfiguration.getParams(); Set<String> keys = paramMap.keySet(); for (String key : keys) { transMeta.setParameterValue(key, Const.NVL(paramMap.get(key), "")); } transMeta.activateParameters(); // Do we need to clear the log before running? // if (executionConfiguration.isClearingLog()) { transLogDelegate.clearLog(); } // Do we have a previous execution to clean up in the logging registry? // if (trans != null) { KettleLogStore.discardLines(trans.getLogChannelId(), false); LoggingRegistry.getInstance().removeIncludingChildren(trans.getLogChannelId()); } // Create a new transformation to execution // trans = new Trans(transMeta); trans.setSafeModeEnabled(executionConfiguration.isSafeModeEnabled()); trans.setPreview(true); trans.setGatheringMetrics(executionConfiguration.isGatheringMetrics()); trans.setMetaStore(spoon.getMetaStore()); trans.prepareExecution(args); trans.setRepository(spoon.rep); List<SpoonUiExtenderPluginInterface> relevantExtenders = SpoonUiExtenderPluginType.getInstance() .getRelevantExtenders(TransDebugMetaWrapper.class, PREVIEW_TRANS); TransDebugMetaWrapper transDebugMetaWrapper = new TransDebugMetaWrapper(trans, transDebugMeta); for (SpoonUiExtenderPluginInterface relevantExtender : relevantExtenders) { relevantExtender.uiEvent(transDebugMetaWrapper, PREVIEW_TRANS); } // Add the row listeners to the allocated threads // transDebugMeta.addRowListenersToTransformation(trans); // What method should we call back when a break-point is hit? transDebugMeta.addBreakPointListers(new BreakPointListener() { @Override public void breakPointHit(TransDebugMeta transDebugMeta, StepDebugMeta stepDebugMeta, RowMetaInterface rowBufferMeta, List<Object[]> rowBuffer) { showPreview(transDebugMeta, stepDebugMeta, rowBufferMeta, rowBuffer); } }); // Do we capture data? // if (transPreviewDelegate.isActive()) { transPreviewDelegate.capturePreviewData(trans, transMeta.getSteps()); } // Start the threads for the steps... // startThreads(); debug = true; // Show the execution results view... // shell.getDisplay().asyncExec(new Runnable() { @Override public void run() { addAllTabs(); } }); } catch (Exception e) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransLog.Dialog.UnexpectedErrorDuringPreview.Title"), BaseMessages.getString(PKG, "TransLog.Dialog.UnexpectedErrorDuringPreview.Message"), e); } } else { modalMessageDialog(getString("TransLog.Dialog.DoNoPreviewWhileRunning.Title"), getString("TransLog.Dialog.DoNoPreviewWhileRunning.Message"), SWT.OK | SWT.ICON_WARNING); } checkErrorVisuals(); } public synchronized void showPreview(final TransDebugMeta transDebugMeta, final StepDebugMeta stepDebugMeta, final RowMetaInterface rowBufferMeta, final List<Object[]> rowBuffer) { shell.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (isDisposed()) { return; } spoon.enableMenus(); // The transformation is now paused, indicate this in the log dialog... // pausing = true; setControlStates(); checkErrorVisuals(); PreviewRowsDialog previewRowsDialog = new PreviewRowsDialog(shell, transMeta, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | SWT.APPLICATION_MODAL | SWT.SHEET, stepDebugMeta.getStepMeta().getName(), rowBufferMeta, rowBuffer); previewRowsDialog.setProposingToGetMoreRows(true); previewRowsDialog.setProposingToStop(true); previewRowsDialog.open(); if (previewRowsDialog.isAskingForMoreRows()) { // clear the row buffer. // That way if you click resume, you get the next N rows for the step :-) // rowBuffer.clear(); // Resume running: find more rows... // pauseResume(); } if (previewRowsDialog.isAskingToStop()) { // Stop running // stop(); } } }); } private String[] convertArguments(Map<String, String> arguments) { String[] argumentNames = arguments.keySet().toArray(new String[arguments.size()]); Arrays.sort(argumentNames); String[] args = new String[argumentNames.length]; for (int i = 0; i < args.length; i++) { String argumentName = argumentNames[i]; args[i] = arguments.get(argumentName); } return args; } public void stop() { if (safeStopping) { modalMessageDialog(getString("TransLog.Log.SafeStopAlreadyStarted.Title"), getString("TransLog.Log.SafeStopAlreadyStarted"), SWT.ICON_ERROR | SWT.OK); return; } if ((running && !halting)) { halting = true; trans.stopAll(); log.logMinimal(BaseMessages.getString(PKG, "TransLog.Log.ProcessingOfTransformationStopped")); running = false; initialized = false; halted = false; halting = false; setControlStates(); transMeta.setInternalKettleVariables(); // set the original vars back as they may be changed by a mapping } } public void safeStop() { if (running && !halting) { halting = true; safeStopping = true; trans.safeStop(); log.logMinimal(BaseMessages.getString(PKG, "TransLog.Log.TransformationSafeStopped")); initialized = false; halted = false; setControlStates(); transMeta.setInternalKettleVariables(); // set the original vars back as they may be changed by a mapping } } public synchronized void pauseResume() { if (running) { // Get the pause toolbar item // if (!pausing) { pausing = true; trans.pauseRunning(); setControlStates(); } else { pausing = false; trans.resumeRunning(); setControlStates(); } } } private boolean controlDisposed(XulToolbarbutton button) { if (button.getManagedObject() instanceof Widget) { Widget widget = (Widget) button.getManagedObject(); return widget.isDisposed(); } return false; } @Override public synchronized void setControlStates() { if (isDisposed() || getDisplay().isDisposed()) { return; } if (((Control) toolbar.getManagedObject()).isDisposed()) { return; } getDisplay().asyncExec(new Runnable() { @Override public void run() { boolean operationsNotAllowed = false; try { operationsNotAllowed = RepositorySecurityUI.verifyOperations(shell, spoon.rep, false, RepositoryOperation.EXECUTE_TRANSFORMATION); } catch (KettleRepositoryLostException krle) { log.logError(krle.getLocalizedMessage()); spoon.handleRepositoryLost(krle); } // Start/Run button... // XulToolbarbutton runButton = (XulToolbarbutton) toolbar.getElementById("trans-run"); if (runButton != null && !controlDisposed(runButton) && !operationsNotAllowed) { if (runButton.isDisabled() ^ running) { runButton.setDisabled(running); } } // Pause button... // XulToolbarbutton pauseButton = (XulToolbarbutton) toolbar.getElementById("trans-pause"); if (pauseButton != null && !controlDisposed(pauseButton)) { if (pauseButton.isDisabled() ^ !running) { pauseButton.setDisabled(!running); pauseButton.setLabel(pausing ? RESUME_TEXT : PAUSE_TEXT); pauseButton.setTooltiptext( pausing ? BaseMessages.getString(PKG, "Spoon.Tooltip.ResumeTranformation") : BaseMessages.getString(PKG, "Spoon.Tooltip.PauseTranformation")); } } // Stop button... // if (!stopItem.isDisposed() && !stopItem.isEnabled() ^ !running) { stopItem.setEnabled(running); } // Debug button... // XulToolbarbutton debugButton = (XulToolbarbutton) toolbar.getElementById("trans-debug"); if (debugButton != null && !controlDisposed(debugButton) && !operationsNotAllowed) { if (debugButton.isDisabled() ^ running) { debugButton.setDisabled(running); } } // Preview button... // XulToolbarbutton previewButton = (XulToolbarbutton) toolbar.getElementById("trans-preview"); if (previewButton != null && !controlDisposed(previewButton) && !operationsNotAllowed) { if (previewButton.isDisabled() ^ running) { previewButton.setDisabled(running); } } } }); } private synchronized void prepareTrans(final Thread parentThread, final String[] args) { Runnable runnable = new Runnable() { @Override public void run() { try { trans.prepareExecution(args); // Do we capture data? // if (transPreviewDelegate.isActive()) { transPreviewDelegate.capturePreviewData(trans, transMeta.getSteps()); } initialized = true; } catch (KettleException e) { log.logError(trans.getName() + ": preparing transformation execution failed", e); checkErrorVisuals(); } halted = trans.hasHaltedSteps(); if (trans.isReadyToStart()) { checkStartThreads(); // After init, launch the threads. } else { initialized = false; running = false; checkErrorVisuals(); } } }; Thread thread = new Thread(runnable); thread.start(); } private void checkStartThreads() { if (initialized && !running && trans != null) { startThreads(); } } private synchronized void startThreads() { running = true; try { // Add a listener to the transformation. // If the transformation is done, we want to do the end processing, etc. // trans.addTransListener(new org.pentaho.di.trans.TransAdapter() { @Override public void transFinished(Trans trans) { checkTransEnded(); checkErrorVisuals(); stopRedrawTimer(); transMetricsDelegate.resetLastRefreshTime(); transMetricsDelegate.updateGraph(); } }); trans.startThreads(); startRedrawTimer(); setControlStates(); } catch (KettleException e) { log.logError("Error starting step threads", e); checkErrorVisuals(); stopRedrawTimer(); } // See if we have to fire off the performance graph updater etc. // getDisplay().asyncExec(new Runnable() { @Override public void run() { if (transPerfDelegate.getTransPerfTab() != null) { // If there is a tab open, try to the correct content on there now // transPerfDelegate.setupContent(); transPerfDelegate.layoutPerfComposite(); } } }); } private void startRedrawTimer() { redrawTimer = new Timer("TransGraph: redraw timer"); TimerTask timtask = new TimerTask() { @Override public void run() { if (!spoon.getDisplay().isDisposed()) { spoon.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!TransGraph.this.canvas.isDisposed()) { TransGraph.this.canvas.redraw(); } } }); } } }; redrawTimer.schedule(timtask, 0L, ConstUI.INTERVAL_MS_TRANS_CANVAS_REFRESH); } protected void stopRedrawTimer() { if (redrawTimer != null) { redrawTimer.cancel(); redrawTimer.purge(); redrawTimer = null; } } private void checkTransEnded() { if (trans != null) { if (trans.isFinished() && (running || halted)) { log.logMinimal(BaseMessages.getString(PKG, "TransLog.Log.TransformationHasFinished")); running = false; initialized = false; halted = false; halting = false; safeStopping = false; setControlStates(); // OK, also see if we had a debugging session going on. // If so and we didn't hit a breakpoint yet, display the show // preview dialog... // if (debug && lastTransDebugMeta != null && lastTransDebugMeta.getTotalNumberOfHits() == 0) { debug = false; showLastPreviewResults(); } debug = false; checkErrorVisuals(); shell.getDisplay().asyncExec(new Runnable() { @Override public void run() { spoon.fireMenuControlers(); redraw(); } }); } } } private void checkErrorVisuals() { if (trans.getErrors() > 0) { // Get the logging text and filter it out. Store it in the stepLogMap... // stepLogMap = new HashMap<>(); shell.getDisplay().syncExec(new Runnable() { @Override public void run() { for (StepMetaDataCombi combi : trans.getSteps()) { if (combi.step.getErrors() > 0) { String channelId = combi.step.getLogChannel().getLogChannelId(); List<KettleLoggingEvent> eventList = KettleLogStore.getLogBufferFromTo(channelId, false, 0, KettleLogStore.getLastBufferLineNr()); StringBuilder logText = new StringBuilder(); for (KettleLoggingEvent event : eventList) { Object message = event.getMessage(); if (message instanceof LogMessage) { LogMessage logMessage = (LogMessage) message; if (logMessage.isError()) { logText.append(logMessage.getMessage()).append(Const.CR); } } } stepLogMap.put(combi.stepMeta, logText.toString()); } } } }); } else { stepLogMap = null; } // Redraw the canvas to show the error icons etc. // shell.getDisplay().asyncExec(new Runnable() { @Override public void run() { redraw(); } }); } public synchronized void showLastPreviewResults() { if (lastTransDebugMeta == null || lastTransDebugMeta.getStepDebugMetaMap().isEmpty()) { return; } final List<String> stepnames = new ArrayList<>(); final List<RowMetaInterface> rowMetas = new ArrayList<>(); final List<List<Object[]>> rowBuffers = new ArrayList<>(); // Assemble the buffers etc in the old style... // for (StepMeta stepMeta : lastTransDebugMeta.getStepDebugMetaMap().keySet()) { StepDebugMeta stepDebugMeta = lastTransDebugMeta.getStepDebugMetaMap().get(stepMeta); stepnames.add(stepMeta.getName()); rowMetas.add(stepDebugMeta.getRowBufferMeta()); rowBuffers.add(stepDebugMeta.getRowBuffer()); } getDisplay().asyncExec(new Runnable() { @Override public void run() { EnterPreviewRowsDialog dialog = new EnterPreviewRowsDialog(shell, SWT.NONE, stepnames, rowMetas, rowBuffers); dialog.open(); } }); } /** * XUL dummy menu entry */ public void openMapping() { } /** * Open the transformation mentioned in the mapping... */ public void openMapping(StepMeta stepMeta, int index) { try { Object referencedMeta = null; Trans subTrans = getActiveSubtransformation(this, stepMeta); if (subTrans != null && (stepMeta.getStepMetaInterface().getActiveReferencedObjectDescription() == null || index < 0)) { TransMeta subTransMeta = subTrans.getTransMeta(); referencedMeta = subTransMeta; Object[] objectArray = new Object[4]; objectArray[0] = stepMeta; objectArray[1] = subTransMeta; ExtensionPointHandler.callExtensionPoint(log, KettleExtensionPoint.OpenMapping.id, objectArray); } else { StepMetaInterface meta = stepMeta.getStepMetaInterface(); if (!Utils.isEmpty(meta.getReferencedObjectDescriptions())) { referencedMeta = meta.loadReferencedObject(index, spoon.rep, spoon.metaStore, transMeta); } } if (referencedMeta == null) { return; } if (referencedMeta instanceof TransMeta) { TransMeta mappingMeta = (TransMeta) referencedMeta; mappingMeta.clearChanged(); spoon.addTransGraph(mappingMeta); TransGraph subTransGraph = spoon.getActiveTransGraph(); attachActiveTrans(subTransGraph, this.currentStep); } if (referencedMeta instanceof JobMeta) { JobMeta jobMeta = (JobMeta) referencedMeta; jobMeta.clearChanged(); spoon.addJobGraph(jobMeta); JobGraph jobGraph = spoon.getActiveJobGraph(); attachActiveJob(jobGraph, this.currentStep); } } catch (Exception e) { new ErrorDialog(shell, BaseMessages.getString(PKG, "TransGraph.Exception.UnableToLoadMapping.Title"), BaseMessages.getString(PKG, "TransGraph.Exception.UnableToLoadMapping.Message"), e); } } /** * Finds the last active transformation in the running job to the opened transMeta * * @param transGraph * @param stepMeta */ private void attachActiveTrans(TransGraph transGraph, StepMeta stepMeta) { if (trans != null && transGraph != null) { Trans subTransformation = trans.getActiveSubTransformation(stepMeta.getName()); transGraph.setTrans(subTransformation); if (!transGraph.isExecutionResultsPaneVisible()) { transGraph.showExecutionResults(); } transGraph.setControlStates(); } } /** * Finds the last active transformation in the running job to the opened transMeta * * @param transGraph * @param stepMeta */ private Trans getActiveSubtransformation(TransGraph transGraph, StepMeta stepMeta) { if (trans != null && transGraph != null) { return trans.getActiveSubTransformation(stepMeta.getName()); } return null; } /** * Finds the last active job in the running transformation to the opened jobMeta * * @param jobGraph * @param stepMeta */ private void attachActiveJob(JobGraph jobGraph, StepMeta stepMeta) { if (trans != null && jobGraph != null) { Job subJob = trans.getActiveSubjobs().get(stepMeta.getName()); jobGraph.setJob(subJob); if (!jobGraph.isExecutionResultsPaneVisible()) { jobGraph.showExecutionResults(); } jobGraph.setControlStates(); } } /** * @return the running */ public boolean isRunning() { return running; } /** * @param running the running to set */ public void setRunning(boolean running) { this.running = running; } /** * @return the lastTransDebugMeta */ public TransDebugMeta getLastTransDebugMeta() { return lastTransDebugMeta; } /** * @return the halting */ public boolean isHalting() { return halting; } /** * @param halting the halting to set */ public void setHalting(boolean halting) { this.halting = halting; } /** * @return the stepLogMap */ public Map<StepMeta, String> getStepLogMap() { return stepLogMap; } /** * @param stepLogMap the stepLogMap to set */ public void setStepLogMap(Map<StepMeta, String> stepLogMap) { this.stepLogMap = stepLogMap; } public void dumpLoggingRegistry() { LoggingRegistry registry = LoggingRegistry.getInstance(); Map<String, LoggingObjectInterface> loggingMap = registry.getMap(); for (LoggingObjectInterface loggingObject : loggingMap.values()) { System.out.println(loggingObject.getLogChannelId() + " - " + loggingObject.getObjectName() + " - " + loggingObject.getObjectType()); } } @Override public HasLogChannelInterface getLogChannelProvider() { return new HasLogChannelInterface() { @Override public LogChannelInterface getLogChannel() { return getTrans() != null ? getTrans().getLogChannel() : getTransMeta().getLogChannel(); } }; } public synchronized void setTrans(Trans trans) { this.trans = trans; if (trans != null) { pausing = trans.isPaused(); initialized = trans.isInitializing(); running = trans.isRunning(); halted = trans.isStopped(); if (running) { trans.addTransListener(new org.pentaho.di.trans.TransAdapter() { @Override public void transFinished(Trans trans) { checkTransEnded(); checkErrorVisuals(); } }); } } } public void sniffInput() { sniff(true, false, false); } public void sniffOutput() { sniff(false, true, false); } public void sniffError() { sniff(false, false, true); } public void sniff(final boolean input, final boolean output, final boolean error) { StepMeta stepMeta = getCurrentStep(); if (stepMeta == null || trans == null) { return; } final StepInterface runThread = trans.findRunThread(stepMeta.getName()); if (runThread != null) { List<Object[]> rows = new ArrayList<>(); final PreviewRowsDialog dialog = new PreviewRowsDialog(shell, trans, SWT.NONE, stepMeta.getName(), null, rows); dialog.setDynamic(true); // Add a row listener that sends the rows over to the dialog... // final RowListener rowListener = new RowListener() { @Override public void rowReadEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException { if (input) { try { dialog.addDataRow(rowMeta, rowMeta.cloneRow(row)); } catch (KettleValueException e) { throw new KettleStepException(e); } } } @Override public void rowWrittenEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException { if (output) { try { dialog.addDataRow(rowMeta, rowMeta.cloneRow(row)); } catch (KettleValueException e) { throw new KettleStepException(e); } } } @Override public void errorRowWrittenEvent(RowMetaInterface rowMeta, Object[] row) throws KettleStepException { if (error) { try { dialog.addDataRow(rowMeta, rowMeta.cloneRow(row)); } catch (KettleValueException e) { throw new KettleStepException(e); } } } }; // When the dialog is closed, make sure to remove the listener! // dialog.addDialogClosedListener(new DialogClosedListener() { @Override public void dialogClosed() { runThread.removeRowListener(rowListener); } }); // Open the dialog in a separate thread to make sure it doesn't block // getDisplay().asyncExec(new Runnable() { @Override public void run() { dialog.open(); } }); runThread.addRowListener(rowListener); } } @Override public String getName() { return "transgraph"; } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#getXulDomContainer() */ @Override public XulDomContainer getXulDomContainer() { return xulDomContainer; } @Override public void setName(String arg0) { } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#setXulDomContainer(org.pentaho.ui.xul.XulDomContainer) */ @Override public void setXulDomContainer(XulDomContainer xulDomContainer) { this.xulDomContainer = xulDomContainer; } @Override public boolean canHandleSave() { return true; } @Override public int showChangedWarning() throws KettleException { return showChangedWarning(transMeta.getName()); } private class StepVelocity { public StepVelocity(double dx, double dy) { this.dx = dx; this.dy = dy; } public double dx, dy; } private class StepLocation { public StepLocation(double x, double y) { this.x = x; this.y = y; } public double x, y; public void add(StepLocation loc) { x += loc.x; y += loc.y; } } private class Force { public Force(double fx, double fy) { this.fx = fx; this.fy = fy; } public double fx, fy; public void add(Force force) { fx += force.fx; fy += force.fy; } } private static double dampningConstant = 0.5; // private static double springConstant = 1.0; private static double timeStep = 1.0; private static double nodeMass = 1.0; /** * Perform an automatic layout of a transformation based on "Force-based algorithms". Source: * http://en.wikipedia.org/wiki/Force-based_algorithms_(graph_drawing) * <p/> * set up initial node velocities to (0,0) set up initial node positions randomly // make sure no 2 nodes are in * exactly the same position loop total_kinetic_energy := 0 // running sum of total kinetic energy over all particles * for each node net-force := (0, 0) // running sum of total force on this particular node * <p/> * for each other node net-force := net-force + Coulomb_repulsion( this_node, other_node ) next node * <p/> * for each spring connected to this node net-force := net-force + Hooke_attraction( this_node, spring ) next spring * <p/> * // without damping, it moves forever this_node.velocity := (this_node.velocity + timestep * net-force) * damping * this_node.position := this_node.position + timestep * this_node.velocity total_kinetic_energy := * total_kinetic_energy + this_node.mass * (this_node.velocity)^2 next node until total_kinetic_energy is less than * some small number // the simulation has stopped moving */ public void autoLayout() { // Initialize... // Map<StepMeta, StepVelocity> speeds = new HashMap<>(); Map<StepMeta, StepLocation> locations = new HashMap<>(); for (StepMeta stepMeta : transMeta.getSteps()) { speeds.put(stepMeta, new StepVelocity(0, 0)); StepLocation location = new StepLocation(stepMeta.getLocation().x, stepMeta.getLocation().y); locations.put(stepMeta, location); } StepLocation center = calculateCenter(locations); // Layout loop! // double totalKineticEngergy = 0; do { totalKineticEngergy = 0; for (StepMeta stepMeta : transMeta.getSteps()) { Force netForce = new Force(0, 0); StepVelocity velocity = speeds.get(stepMeta); StepLocation location = locations.get(stepMeta); for (StepMeta otherStep : transMeta.getSteps()) { if (!stepMeta.equals(otherStep)) { netForce.add(getCoulombRepulsion(stepMeta, otherStep, locations)); } } for (int i = 0; i < transMeta.nrTransHops(); i++) { TransHopMeta hopMeta = transMeta.getTransHop(i); if (hopMeta.getFromStep().equals(stepMeta) || hopMeta.getToStep().equals(stepMeta)) { netForce.add(getHookeAttraction(hopMeta, locations)); } } adjustVelocity(velocity, netForce); adjustLocation(location, velocity); totalKineticEngergy += nodeMass * (velocity.dx * velocity.dx + velocity.dy * velocity.dy); } StepLocation newCenter = calculateCenter(locations); StepLocation diff = new StepLocation(center.x - newCenter.x, center.y - newCenter.y); for (StepMeta stepMeta : transMeta.getSteps()) { StepLocation location = locations.get(stepMeta); location.x += diff.x; location.y += diff.y; stepMeta.setLocation((int) Math.round(location.x), (int) Math.round(location.y)); } // redraw... // redraw(); } while (totalKineticEngergy < 0.01); } private StepLocation calculateCenter(Map<StepMeta, StepLocation> locations) { StepLocation center = new StepLocation(0, 0); for (StepLocation location : locations.values()) { center.add(location); } center.x /= locations.size(); center.y /= locations.size(); return center; } /** * http://en.wikipedia.org/wiki/Coulomb's_law * * @param step1 * @param step2 * @param locations * @return */ private Force getCoulombRepulsion(StepMeta step1, StepMeta step2, Map<StepMeta, StepLocation> locations) { double q1 = 4.0; double q2 = 4.0; double Ke = -3.0; StepLocation loc1 = locations.get(step1); StepLocation loc2 = locations.get(step2); double fx = Ke * q1 * q2 / Math.abs(loc1.x - loc2.x); double fy = Ke * q1 * q2 / Math.abs(loc1.y - loc2.y); return new Force(fx, fy); } /** * The longer the hop, the higher the force * * @param hopMeta * @param locations * @return */ private Force getHookeAttraction(TransHopMeta hopMeta, Map<StepMeta, StepLocation> locations) { StepLocation loc1 = locations.get(hopMeta.getFromStep()); StepLocation loc2 = locations.get(hopMeta.getToStep()); double springConstant = 0.01; double fx = springConstant * Math.abs(loc1.x - loc2.x); double fy = springConstant * Math.abs(loc1.y - loc2.y); return new Force(fx * fx, fy * fy); } private void adjustVelocity(StepVelocity velocity, Force netForce) { velocity.dx = (velocity.dx + timeStep * netForce.fx) * dampningConstant; velocity.dy = (velocity.dy + timeStep * netForce.fy) * dampningConstant; } private void adjustLocation(StepLocation location, StepVelocity velocity) { location.x = location.x + nodeMass * velocity.dx * velocity.dx; location.y = location.y + nodeMass * velocity.dy * velocity.dy; } public void handleTransMetaChanges(TransMeta transMeta) throws KettleException { if (transMeta.hasChanged()) { if (spoon.props.getAutoSave()) { spoon.saveToFile(transMeta); } else { MessageDialogWithToggle md = new MessageDialogWithToggle(shell, BaseMessages.getString(PKG, "TransLog.Dialog.FileHasChanged.Title"), null, BaseMessages.getString(PKG, "TransLog.Dialog.FileHasChanged1.Message") + Const.CR + BaseMessages.getString(PKG, "TransLog.Dialog.FileHasChanged2.Message") + Const.CR, MessageDialog.QUESTION, new String[] { BaseMessages.getString(PKG, "System.Button.Yes"), BaseMessages.getString(PKG, "System.Button.No") }, 0, BaseMessages.getString(PKG, "TransLog.Dialog.Option.AutoSaveTransformation"), spoon.props.getAutoSave()); MessageDialogWithToggle.setDefaultImage(GUIResource.getInstance().getImageSpoon()); int answer = md.open(); if ((answer & 0xFF) == 0) { spoon.saveToFile(transMeta); } spoon.props.setAutoSave(md.getToggleState()); } } } private StepMeta lastChained = null; public void addStepToChain(PluginInterface stepPlugin, boolean shift) { TransMeta transMeta = spoon.getActiveTransformation(); if (transMeta == null) { return; } // Is the lastChained entry still valid? // if (lastChained != null && transMeta.findStep(lastChained.getName()) == null) { lastChained = null; } // If there is exactly one selected step, pick that one as last chained. // List<StepMeta> sel = transMeta.getSelectedSteps(); if (sel.size() == 1) { lastChained = sel.get(0); } // Where do we add this? Point p = null; if (lastChained == null) { p = transMeta.getMaximum(); p.x -= 100; } else { p = new Point(lastChained.getLocation().x, lastChained.getLocation().y); } p.x += 200; // Which is the new step? StepMeta newStep = spoon.newStep(transMeta, stepPlugin.getIds()[0], stepPlugin.getName(), stepPlugin.getName(), false, true); if (newStep == null) { return; } newStep.setLocation(p.x, p.y); newStep.setDraw(true); if (lastChained != null) { TransHopMeta hop = new TransHopMeta(lastChained, newStep); spoon.newHop(transMeta, hop); } lastChained = newStep; spoon.refreshGraph(); spoon.refreshTree(); if (shift) { editStep(newStep); } transMeta.unselectAll(); newStep.setSelected(true); } public Spoon getSpoon() { return spoon; } public void setSpoon(Spoon spoon) { this.spoon = spoon; } public TransMeta getTransMeta() { return transMeta; } public Trans getTrans() { return trans; } private Trans createLegacyTrans() { try { return new Trans(transMeta, spoon.rep, transMeta.getName(), transMeta.getRepositoryDirectory().getPath(), transMeta.getFilename()); } catch (KettleException e) { throw new RuntimeException(e); } } private void setHopEnabled(TransHopMeta hop, boolean enabled) { hop.setEnabled(enabled); transMeta.clearCaches(); } private void modalMessageDialog(String title, String message, int swtFlags) { MessageBox messageBox = new MessageBox(shell, swtFlags); messageBox.setMessage(message); messageBox.setText(title); messageBox.open(); } private String getString(String key) { return BaseMessages.getString(PKG, key); } private String getString(String key, String... params) { return BaseMessages.getString(PKG, key, params); } }