/******************************************************************************* * Copyright (c) 2012 Google, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.codegen.ui.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import com.windowtester.controller.ControllerManager; import com.windowtester.controller.IControllerAction; import com.windowtester.recorder.event.meta.RecorderAssertionHookAddedEvent; import com.windowtester.recorder.event.meta.RecorderMetaEvent; import com.windowtester.swt.event.recorder.EventRecorderPlugin; /** * Copied from org.eclipse.tptp.test.auto.gui.internal.editor.AutoGUITestControllerDialog * and modified. * */ public class ControllerDialog implements SelectionListener, MouseListener, MouseMoveListener, KeyListener, DisposeListener{ // icon pointers private final List _usedHookNames = new ArrayList(); //a list to keep track of used assertion names private Shell _sShell; private Composite _toolbarComposite; private Group _verificationGrp; private Group _controlGrp; private ToolBar _toolBar; private Text _verificationName; private Button _insertButton; private Label _nameLbl; private ToolItem _start; private ToolItem _terminate; private ToolItem _restart; private ToolItem _assert; // private ToolItem _write; private ToolItem _pause; // private ToolItem _generate; private ToolItem _positionBasedRecording; private ToolItem _recordWaitTime; private Composite _statusComposite; private Label _statuslbl; /* The display listener that will be listening in for new active shells (set only if isRoot is true) */ private DisplayListener _displayListener; /* The control delegator (maybe set only if isRoot is true) */ private ControllerDialog _delegator; /* The dialog is moved relative to this position */ private Point _originalPosition; /* Indicates that the mouse button is pushed on the shell */ private boolean _isMouseDown; /* The first control center dialog will have this set and the successive ones will not */ private boolean _isRoot; /* The last status of the control center dialog */ private String _lastStatus; /* The last location of the control dialog box */ private Point _lastLocation; /* Indicates whether the control center shell is disposed or not */ private boolean _isShellDisposed; /* Indicates whether position based should be on (should be set before shell is created)*/ private boolean _positionBasedOn; /* Indicates whether wait times should be recorded */ private boolean _waitTimeOn; /* Event types */ protected final static byte TERMINATE = 0x01; protected final static byte VERIFICATION_HOOK_INSERT = 0x02; protected final static byte RESTART = 0x03; protected final static byte POSITION_BASED = 0x04; protected final static byte WAIT_TIME = 0x05; protected final static byte START = 0x06; //pq protected final static byte WRITE = 0x07; //pq protected final static byte GENERATE = 0x08; //pq protected final static byte ASSERT = 0x09; //pq private final static int DO_NOT_DISPOSE = 1020; /** A label that identifies components of this widget */ static public final String ID_LABEL = "controller.dialog.component"; /** A value for the label key (ignored; but must be non-null) */ static final String ID_LABEL_VALUE = "val"; //map toolitems to contributed actions private final Map /* <ToolItem,IControllerAction>*/ _actionMap = new HashMap(); /** * @param parent */ public ControllerDialog() { _sShell = new Shell(SWT.ON_TOP); //keep the dialog on top! _isShellDisposed = false; _isRoot = true; _positionBasedOn = false; } public Point getLastLocation () { if (_isRoot && _delegator != null) return _delegator.getLastLocation(); if (_sShell != null && !_sShell.isDisposed()) return _sShell.getLocation(); return _lastLocation; } /** * A non-blocking method that opens the control center dialog */ public void openDialog() { _originalPosition = new Point(0, 0); _sShell = new Shell(SWT.ON_TOP); _sShell.setText("Control Center"); _sShell.setLayout(new GridLayout()); if (_lastLocation != null) _sShell.setLocation(_lastLocation); _sShell.addMouseListener(this); _sShell.addMouseMoveListener(this); _sShell.addDisposeListener(this); /* We would like the macro recorder to ignore this shell */ //sShell.setData(MacroManager.IGNORE, Boolean.TRUE); _sShell.setFocus(); createToolbarComposite(); createStatusComposite(); /* We need to always stay on top of every shell */ if (_isRoot) { _displayListener = new DisplayListener(); Display.getCurrent().addFilter(SWT.Activate, _displayListener); } /* Set initial status to idle */ setStatus("idle"); makeTestFriendly(); /* Open the dialog */ _sShell.pack(); _sShell.open(); } private void makeTestFriendly() { RecorderDialogTestHelper.tagAsRecorderShell(_sShell); RecorderDialogTestHelper.tagAsRecorderStartButton(_start); } public void keyPressed(KeyEvent e) { keyReleased (null); } public void keyReleased(KeyEvent e) { if (_verificationName.getText().trim().equals("")) { _insertButton.setEnabled(false); return; } _insertButton.setEnabled(true); } public void mouseDoubleClick(MouseEvent e) { /* Doesn't need to be implemented */ } public void mouseDown(MouseEvent e) { _isMouseDown = true; _originalPosition.x = e.x; _originalPosition.y = e.y; } public void mouseUp(MouseEvent e) { _isMouseDown = false; } /** * Move the dialog box when the user clicks on the shell and moves the cursor */ public void mouseMove(MouseEvent e) { _sShell.setFocus(); if (_isMouseDown) { int xMove = e.x - _originalPosition.x; int yMove = e.y - _originalPosition.y; Point currentLocation = _sShell.getLocation(); _sShell.setLocation(currentLocation.x + xMove, currentLocation.y + yMove); _lastLocation = _sShell.getLocation(); } } /** * This method initializes toolbarComposite * */ private void createToolbarComposite() { GridLayout gridLayout = new GridLayout(); gridLayout.numColumns = 2; _toolbarComposite = new Composite(_sShell, SWT.NONE); createControlGrp(); //createVerificationGrp(); _toolbarComposite.addMouseListener(this); _toolbarComposite.addMouseMoveListener(this); _toolbarComposite.setLayout(gridLayout); } private void createStatusComposite() { GridData gd = new GridData(); gd.horizontalAlignment = SWT.FILL; gd.grabExcessHorizontalSpace = true; GridLayout gridLayout = new GridLayout(); _statusComposite = new Composite (_sShell, SWT.NONE); _statusComposite.setLayout(gridLayout); _statusComposite.setLayoutData(gd); _statusComposite.addMouseListener(this); _statusComposite.addMouseMoveListener(this); GridData gd2 = new GridData(); gd2.horizontalAlignment = SWT.FILL; gd2.grabExcessHorizontalSpace = true; _statuslbl = new Label (_statusComposite, SWT.NONE); _statuslbl.setAlignment(SWT.CENTER); _statuslbl.setLayoutData(gd2); _statuslbl.addMouseListener(this); _statuslbl.addMouseMoveListener(this); } /** * This method initializes verificationGrp * */ private void createVerificationGrp() { GridLayout gridLayout1 = new GridLayout(); gridLayout1.numColumns = 3; _verificationGrp = new Group(_toolbarComposite, SWT.NONE); _verificationGrp.setText("verifier"); _verificationGrp.setLayout(gridLayout1); _verificationGrp.addMouseListener(this); _verificationGrp.addMouseMoveListener(this); _nameLbl = new Label(_verificationGrp, SWT.NONE); _nameLbl.setText("new"); _nameLbl.addMouseListener(this); _nameLbl.addMouseMoveListener(this); _verificationName = new Text(_verificationGrp, SWT.BORDER); _verificationName.addKeyListener(this); _insertButton = new Button(_verificationGrp, SWT.NONE); _insertButton.setText("insert verification hook"); _insertButton.addSelectionListener(this); _insertButton.setEnabled(false); _sShell.setDefaultButton(_insertButton); } /** * This method initializes controlGrp * */ private void createControlGrp() { _controlGrp = new Group(_toolbarComposite, SWT.NONE); _controlGrp.setLayout(new GridLayout()); _controlGrp.addMouseListener(this); _controlGrp.addMouseMoveListener(this); createToolBar(); _controlGrp.setText("Control"); } /** * This method initializes toolBar * */ private void createToolBar() { GridData gridData = new GridData(); gridData.horizontalAlignment = SWT.CENTER; gridData.grabExcessHorizontalSpace = true; _toolBar = new ToolBar(_controlGrp, SWT.NONE); /* The start button */ _start = new ToolItem(_toolBar, SWT.PUSH); _start.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.START)); _start.setToolTipText("Start/Resume Recording"); _start.addSelectionListener(this); /* The pause events button */ _pause = new ToolItem(_toolBar, SWT.PUSH); _pause.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.PAUSE)); _pause.setToolTipText("Pause Recording"); _pause.addSelectionListener(this); /* The insert assertion button */ _assert = new ToolItem(_toolBar, SWT.PUSH); _assert.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.WRITE)); //for now, this ico will do... _assert.setToolTipText("Add Assertion Hook"); _assert.addSelectionListener(this); /** * termination is tricky since we need to kill the app under test... * disabled for now */ /* The terminate button */ // _terminate = new ToolItem(_toolBar, SWT.PUSH); // _terminate.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.TERMINATE)); // _terminate.setToolTipText("Stop Recording"); // _terminate.addSelectionListener(this); // tag(_terminate); /* The restart button */ _restart = new ToolItem(_toolBar, SWT.PUSH); _restart.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.RESTART)); _restart.setToolTipText("Restart Recording"); _restart.addSelectionListener(this); /* The write events button */ // _write = new ToolItem(_toolBar, SWT.PUSH); // _write.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.WRITE)); // _write.setToolTipText("Write Captured Events"); // _write.addSelectionListener(this); // tag(_write); addContributedItems(); // PQ: needs to be contributed by codegen plugin /* The generate button */ // _generate = new ToolItem(_toolBar, SWT.PUSH); // _generate.setImage(RecorderUIImages.INSTANCE.getImage(RecorderUIImages.RESTART)); // _generate.setToolTipText("Generate Test Case"); // _generate.addSelectionListener(this); // /* A separator */ // ToolItem sep = new ToolItem(toolBar, SWT.SEPARATOR); // sep.setText(""); // // /* The position-based recording button */ // positionBasedRecording = new ToolItem (toolBar, SWT.CHECK); // positionBasedRecording.setImage(AutoGUIImages.INSTANCE.getImage(AutoGUIImages.POSITION_BASED)); // positionBasedRecording.setToolTipText(AutoGUIMessages.AUTO_GUI_CONTROL_POSITION_BASED); // positionBasedRecording.setSelection(positionBasedOn); // positionBasedRecording.addSelectionListener(this); // // /* The wait time toggle button */ // recordWaitTime = new ToolItem (toolBar, SWT.CHECK); // recordWaitTime .setImage(AutoGUIImages.INSTANCE.getImage(AutoGUIImages.WAIT_TIME)); // recordWaitTime.setToolTipText(AutoGUIMessages.AUTO_GUI_CONTROL_WAIT_TIME); // recordWaitTime.setSelection(waitTimeOn); // recordWaitTime.addSelectionListener(this); _toolBar.setLayoutData(gridData); } /** * Add tool items/actions, contributed via the Controller Action extension point */ private void addContributedItems() { IControllerAction[] contributedActions = ControllerManager.getContributedActions(); if (contributedActions == null || contributedActions.length == 0) return; //nothing to do! //TODO: if non-empty- want to create a new group for these actions... //create a new group... //get contributed actions and make associated tool items IControllerAction action; ToolItem contrib; for (int i = 0; i < contributedActions.length; i++) { action = contributedActions[i]; contrib = new ToolItem(_toolBar, SWT.PUSH); //map this tool-item to its action (for later dispatch) _actionMap.put(contrib,action); //fill in presentation details contrib.setImage(action.getImage()); contrib.setToolTipText(action.getToolTipText()); contrib.addSelectionListener(this); } } public void widgetDefaultSelected(SelectionEvent e) { /* Doesn't need to be implemented */ } /** * A chance to clean up after our selves */ public void dispose() { stopRecorder(); //TODO: is stopping enough? should we prompt to write if there are stored events? _isShellDisposed = true; if (_sShell != null && !_sShell.isDisposed()) { _sShell.close(); _toolBar.dispose(); // _verificationName.dispose(); // _insertButton.dispose(); // _nameLbl.dispose(); _start.dispose(); // _write.dispose(); _pause.dispose(); // _generate.dispose(); // _terminate.dispose(); _restart.dispose(); disposeContributedControls(); // _positionBasedRecording.dispose(); //_verificationGrp.dispose(); _controlGrp.dispose(); _toolbarComposite.dispose(); _sShell.dispose(); // @jve:decl-index=0:visual-constraint="10,10" } _toolBar = null; _verificationName = null; _insertButton = null; _nameLbl = null; _start = null; _terminate = null; _verificationGrp = null; _controlGrp = null; _toolbarComposite = null; _sShell = null; // @jve:decl-index=0:visual-constraint="10,10" _originalPosition = null; } /** * Dispose of all the contributed controls. * Note: it's funny that this is required... aren't all widgets created * with a specified parent disposed by that parent? */ private void disposeContributedControls() { Set contrbs = _actionMap.keySet(); for (Iterator iter = contrbs.iterator(); iter.hasNext();) { ToolItem item = (ToolItem) iter.next(); item.dispose(); } } // /** // * A helper method used to notify the registered listeners // * // * @param eventType The type of event that caused this invokation // */ // protected void notifyListeners (byte eventType, Object value) // { // // /* If we're not the root control center, then we're expected to delegate the tasks */ // if (!_isRoot) // { // _rootControl.notifyListeners(eventType, value); // } // else // { // /* Remove the listener if we were terminated */ // if (eventType == TERMINATE) // Display.getCurrent().removeFilter(SWT.Activate, _displayListener); // // /* Update appropriate fields if delegator updated the position-based or wait-time-recording options */ // if (_delegator != null) // { // if (eventType == POSITION_BASED) // _positionBasedOn = _delegator.isPositionBasedOn(); // else if (eventType == WAIT_TIME) // _waitTimeOn = _delegator.isWaitTimeOn(); // } // // /* Start notifying the listeners */ // int size = _listenerBucket.size(); // for (int i = 0; i < size; i++) // { // ((AutoGUIControllerListener)listenerBucket.get(i)).handleEvent(eventType, value); // } // } // } public void widgetSelected(SelectionEvent e) { byte eventType = 0; /* Determine the type of event */ // handle contributed actions IControllerAction contribedAction = (IControllerAction)_actionMap.get(e.widget); if (contribedAction != null) { contribedAction.perform(); return; } // then go on to the hard-wired, recorder-provided ones... if (e.widget == _terminate) { eventType = TERMINATE; // notifyListeners (eventType, null); stopRecorder(); // if (e.detail != DO_NOT_DISPOSE) // dispose(); return; } if (e.widget == _start) { eventType = START; startRecorder(); return; } // if (e.widget == _write) // { // eventType = WRITE; // writeEvents(); // return; // } if (e.widget == _pause) { eventType = WRITE; pauseRecorder(); return; } if (e.widget == _restart) { eventType = RESTART; restartRecorder(); return; } if (e.widget == _assert) { eventType = ASSERT; addAssertion(); return; } /* This is the verification insertion event */ else if (e.widget == _insertButton) eventType = VERIFICATION_HOOK_INSERT; // else if (e.widget == _restart) // eventType = RESTART; else if (e.widget == _positionBasedRecording) { _positionBasedOn = ((ToolItem)e.widget).getSelection(); eventType = POSITION_BASED; } else if (e.widget == _recordWaitTime) { _waitTimeOn = ((ToolItem)e.widget).getSelection(); eventType = WAIT_TIME; } // if (eventType != 0) // notifyListeners (eventType, _verificationName.getText()); } private TaggedInputDialog _assertionDialog; /** the port that the application under recording controller is listening */ private int port; /** * Add an assertion to the event stream. */ private void addAssertion() { TaggedInputDialog d = getAssertionDialog(); int result = d.open(); if (result == Window.OK) { String hookName = d.getValue(); _usedHookNames.add(hookName); EventRecorderPlugin.send(new RecorderAssertionHookAddedEvent(hookName), port); } } private TaggedInputDialog getAssertionDialog() { if (_assertionDialog == null) { _assertionDialog = new TaggedInputDialog(_sShell, "Insert Assertion Hook", "Input the name of an assertion method hook.", getHookNameSuggestion(), new IInputValidator() { public String isValid(String newText) { // TODO do we want to validate more (e.g., hook exists, etc?) return (newText == null || newText.length() == 0) ? " " : null; //$NON-NLS-1$ } }); } _assertionDialog.setValue(getHookNameSuggestion()); return _assertionDialog; } private String getHookNameSuggestion() { String name = "assert_1"; for (; ;) { if (!_usedHookNames.contains(name)) return name; name = increment(name); } } /** * Restart the recorder. */ private void restartRecorder() { EventRecorderPlugin.send(RecorderMetaEvent.RESTART, port); } /** * Pause the recorder. */ private void pauseRecorder() { setStatus("paused"); EventRecorderPlugin.send(RecorderMetaEvent.PAUSE, port); } public void widgetDisposed(DisposeEvent e) { if (!_isShellDisposed && _isRoot) { _isShellDisposed = true; /* The shell is been disposed. Simulate a termination */ // Event event = new Event(); // event.widget = _terminate; // event.detail = DO_NOT_DISPOSE; // widgetSelected(new SelectionEvent(event)); //TODO PQ: removed above as a workaround for widget disposal errors... should have a better strategy } } /** * Updates the status of the control dialog center * * @param status The status to be printed */ public void setStatus (String status) { /* The delegator has to handle this (if one exists) */ if (_isRoot && _delegator != null) { _delegator.setStatus(status); return; } if (status == null) return; _lastStatus = status; String finalStatus = "Status: " + status; boolean toolong = false; if (finalStatus.length() > 30) toolong = true; _statuslbl.setToolTipText(finalStatus); if (toolong) finalStatus = finalStatus.substring (0, 30) + "..."; _statuslbl.setText (finalStatus); } public String getLastStatus() { if (_isRoot && _delegator != null) return _delegator.getLastStatus(); return _lastStatus; } public void setLocation (Point location) { _lastLocation = location; } private class DisplayListener implements Listener { public void handleEvent(final Event event) { class ChangeDialogParentOp implements Runnable { private DisplayListener listener; public ChangeDialogParentOp(DisplayListener listener) { this.listener = listener; } public void run() { // try // { // boolean isNewShellPresent = event.widget instanceof Shell && // !event.widget.isDisposed() && // /* pq: event.widget.getData(MacroManager.IGNORE) == null && */ // (_lastParent.isDisposed() || !event.widget.equals(_lastParent)); // // if (!isNewShellPresent) // return; // // Point location = getLastLocation(); // String lastStatus = getLastStatus(); // // /* Get rid of the last delegator if one was created. Otherwise dispose this control dialog */ // if (_delegator != null) // _delegator.dispose(); // else // dispose(); // // _delegator = new ControllerDialog((Shell)event.widget, ControllerDialog.this); // _delegator.setLocation(location); // _delegator.setPositionBasedOn(isPositionBasedOn()); // _delegator.setWaitTimeOn(isWaitTimeOn()); // _delegator.openDialog(); // _delegator.setStatus(lastStatus); // _lastParent = (Shell)event.widget; // } // // catch (Throwable t) // { // /* If at any point an error occurs, then de-register this listener */ // Display.getCurrent().removeFilter(SWT.Activate, listener); // } // } } /* We need to run this as a timer operation because it may otherwise cause a widget disposed exeception. The exception * is caused in cases where we get an activation event on shells that are activated only for short periods of time. Running * this operation as a timer operation will guarantee that an activated shell is present and not disposed after 50 milliseconds. * This ultimately avoids processing activated shells that have a very short life span. */ EventRecorderPlugin.getDefault().getDisplay().timerExec(50, new ChangeDialogParentOp(this)); } } public void setPositionBasedOn(boolean isPositionBasedOn) { _positionBasedOn = isPositionBasedOn; } public boolean isPositionBasedOn() { return _positionBasedOn; } public boolean isWaitTimeOn() { return _waitTimeOn; } public void setWaitTimeOn(boolean waitTimeOn) { _waitTimeOn = waitTimeOn; } /** * Start the event recorder */ public void startRecorder() { setStatus("recording"); EventRecorderPlugin.send(RecorderMetaEvent.START, port); } /** * Start the event recorder */ public void stopRecorder() { setStatus("stopped"); EventRecorderPlugin.send(RecorderMetaEvent.STOP, port); } /** * Write the recorded events */ private void writeEvents() { setStatus("writing"); EventRecorderPlugin.writeRecording(); // remove this if not needed: the contract was changed setStatus("stopped"); } //TODO: this should be moved into a utility class... public static String increment(String methodName) { ParsedName name = parseName(methodName); if (name.index == -1) name.index = 1; //skip zero else name.index++; return name.toString(); } /** * Parse this name into a name piece and an index * @param name - the name to parse * @return a ParsedName */ public static ParsedName parseName(String name) { boolean done = false; StringBuffer sb = new StringBuffer(); int i; for (i=name.length()-1; !done && i >= 0; --i) { char ch = name.charAt(i); if (Character.isDigit(ch)) sb.append(ch); else done = true; } ParsedName parsedName = new ParsedName(); parsedName.index = sb.length() == 0 ? -1 : Integer.parseInt(sb.reverse().toString()); parsedName.name = sb.length() == 0 ? name : name.substring(0,i+2); return parsedName; } /** * A data holder class for parsed names. */ static class ParsedName { /** The name component */ public String name; /** The integer index */ public int index; public String toString() { return name + index; } } public void setEnabled(int port) { this.port = port; } public boolean isDisposed() { return _sShell.isDisposed(); } }