Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 394179 | Differences between
and this patch

Collapse All | Expand All

(-)a/org.eclipse.jubula.rc.common/src/org/eclipse/jubula/rc/common/caps/AbstractApplicationCAPs.java (+749 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2012 BREDEX GmbH.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     BREDEX GmbH - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jubula.rc.common.caps;
12
13
import java.awt.AWTException;
14
import java.awt.Dimension;
15
import java.awt.Graphics2D;
16
import java.awt.Rectangle;
17
import java.awt.RenderingHints;
18
import java.awt.Toolkit;
19
import java.awt.datatransfer.StringSelection;
20
import java.awt.image.BufferedImage;
21
import java.io.File;
22
import java.io.IOException;
23
24
import javax.imageio.ImageIO;
25
26
import org.eclipse.jubula.rc.common.driver.ClickOptions;
27
import org.eclipse.jubula.rc.common.driver.IRobot;
28
import org.eclipse.jubula.rc.common.driver.KeyTyper;
29
import org.eclipse.jubula.rc.common.exception.ExecutionEvent;
30
import org.eclipse.jubula.rc.common.exception.OsNotSupportedException;
31
import org.eclipse.jubula.rc.common.exception.RobotException;
32
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
33
import org.eclipse.jubula.rc.common.implclasses.Comparer;
34
import org.eclipse.jubula.rc.common.implclasses.IBaseImplementationClass;
35
import org.eclipse.jubula.rc.common.implclasses.Verifier;
36
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
37
import org.eclipse.jubula.rc.common.util.KeyStrokeUtil;
38
import org.eclipse.jubula.tools.constants.StringConstants;
39
import org.eclipse.jubula.tools.objects.event.EventFactory;
40
import org.eclipse.jubula.tools.objects.event.TestErrorEvent;
41
import org.eclipse.jubula.tools.utils.ExternalCommandExecutor;
42
import org.eclipse.jubula.tools.utils.TimeUtil;
43
import org.eclipse.jubula.tools.utils.ExternalCommandExecutor.MonitorTask;
44
/**
45
 * 
46
 * @author BREDEX GmbH
47
 *
48
 */
49
public abstract class AbstractApplicationCAPs
50
    implements IBaseImplementationClass {
51
    /**
52
     * String for sequential numbering for screenshots
53
     */
54
    public static final String RENAME = "rename"; //$NON-NLS-1$
55
    
56
    /**
57
     * String for overwriting for screenshots
58
     */
59
    public static final String OVERWRITE = "overwrite"; //$NON-NLS-1$
60
61
    /**
62
     * The default format to use when writing images to disk.
63
     */
64
    private static final String DEFAULT_IMAGE_FORMAT = "png"; //$NON-NLS-1$
65
    
66
    /**
67
     * The string used to separate filename and file extension.
68
     */
69
    private static final String EXTENSION_SEPARATOR = "."; //$NON-NLS-1$
70
    
71
    /** constants for communication */
72
    private static final String POS_UNIT_PIXEL = "Pixel"; //$NON-NLS-1$
73
    
74
    /** constants for communication */
75
    private static final String POS_UNI_PERCENT = "Percent"; //$NON-NLS-1$
76
        
77
    /**
78
     * The logging.
79
     */
80
    private static AutServerLogger log = 
81
        new AutServerLogger(AbstractApplicationCAPs.class);
82
83
    /**
84
     * @param text text to type
85
     */
86
    public void gdInputText(String text) {
87
        getRobot().type(getFocusOwner(), text);
88
    }
89
    
90
    /**
91
     * Executes the given command and waits for it to finish. If the 
92
     * execution does not finish in good time, a timeout will occur. If the 
93
     * exit code for the execution is not the same as the expected code, the 
94
     * test step fails.
95
     * 
96
     * @param cmd The command to execute.
97
     * @param expectedExitCode The expected exit code of the command.
98
     * @param local <code>true</code> if the command should be executed on the
99
     *        local (client) machine. Otherwise (should run on the server side),
100
     *        this value should be <code>false</code>.
101
     * @param timeout The amount of time (in milliseconds) to wait for the 
102
     *        execution to finish.
103
     */
104
    public void gdExecuteExternalCommand(String cmd, int expectedExitCode, 
105
        boolean local, int timeout) {
106
107
        if (!local) {
108
            MonitorTask mt = new ExternalCommandExecutor().executeCommand(
109
                null, cmd, timeout);
110
111
            if (!mt.wasCmdValid()) {
112
                throw new StepExecutionException(
113
                    "Command not found.", //$NON-NLS-1$
114
                    EventFactory.createActionError(
115
                        TestErrorEvent.NO_SUCH_COMMAND));
116
            }
117
            
118
            if (mt.hasTimeoutOccurred()) {
119
                throw new StepExecutionException(
120
                    "Timeout received before completing execution of script.", //$NON-NLS-1$
121
                    EventFactory.createActionError(
122
                        TestErrorEvent.CONFIRMATION_TIMEOUT));
123
            }
124
            
125
            int actualExitValue = mt.getExitCode();
126
            if (actualExitValue != expectedExitCode) {
127
                throw new StepExecutionException(
128
                    "Verification of exit code failed.", //$NON-NLS-1$
129
                    EventFactory.createVerifyFailed(
130
                        String.valueOf(expectedExitCode), 
131
                        String.valueOf(actualExitValue)));
132
            }
133
        } 
134
        
135
    }
136
    
137
    /**
138
     * {@inheritDoc}
139
     */
140
    public void setComponent(Object graphicsComponent) {
141
        // Do nothing; Application has no correspaonding component
142
    }
143
144
    /**
145
     * Takes a screenshot and saves the image to disk.
146
     * 
147
     * @param destination
148
     *          Path and filename for the created image. If the extension is not
149
     *          ".jpeg" (case-insensitive), ".jpeg" will be appended to the 
150
     *          filename.
151
     * @param delay
152
     *          Amount of time to wait (in milliseconds) before taking the
153
     *          screenshot.
154
     * @param fileAccess
155
     *          Determines how the file will be created if a file with the
156
     *          given name and path already exists:<br>
157
     *          <code>SwingApplicationImplClass.RENAME</code> -
158
     *          The screenshot will be saved with a sequential integer appended 
159
     *          to the filename.<br>
160
     *          <code>SwingApplicationImplClass.OVERWRITE</code> -
161
     *          The screenshot will overwrite the file.
162
     * @param scaling
163
     *          Degree to which the image should be scaled, in percent. A
164
     *          <code>scaling</code> value of <code>100</code> produces an
165
     *          unscaled image. This value must be greater than <code>0</code>
166
     *          and less than or equal to <code>200</code>.
167
     * @param createDirs
168
     *          Determines whether a path will be created if it does not already
169
     *          exist. A value of <code>true</code> means that all necessary 
170
     *          directories that do not exist will be created automatically.
171
     */
172
    public void gdTakeScreenshot(String destination, int delay,
173
            String fileAccess, int scaling, boolean createDirs) {
174
        // Determine current screen size
175
        Toolkit toolkit = Toolkit.getDefaultToolkit();
176
        Dimension screenSize = toolkit.getScreenSize();
177
178
        // If screen !(resolution%2==0) --> bad scaling
179
        int screenWidth = (int)screenSize.getWidth();
180
        int screenHeight = (int)screenSize.getHeight();
181
        if (!(screenWidth % 2 == 0)) {
182
            screenWidth = screenWidth - 1;
183
        }
184
        if (!(screenHeight % 2 == 0)) {
185
            screenHeight = screenHeight - 1;
186
        }
187
188
        screenSize.setSize(screenWidth, screenHeight);
189
190
        Rectangle screenRect = new Rectangle(screenSize);
191
192
        takeScreenshot(destination, delay, fileAccess, scaling, createDirs,
193
                screenRect);
194
    }
195
196
    /**
197
     * Takes a screenshot and saves the image to disk.
198
     * 
199
     * @param destination
200
     *            Path and filename for the created image. If the extension is
201
     *            not ".jpeg" (case-insensitive), ".jpeg" will be appended to
202
     *            the filename.
203
     * @param delay
204
     *            Amount of time to wait (in milliseconds) before taking the
205
     *            screenshot.
206
     * @param fileAccess
207
     *            Determines how the file will be created if a file with the
208
     *            given name and path already exists:<br>
209
     *            <code>SwingApplicationImplClass.RENAME</code> - The screenshot
210
     *            will be saved with a sequential integer appended to the
211
     *            filename.<br>
212
     *            <code>SwingApplicationImplClass.OVERWRITE</code> - The
213
     *            screenshot will overwrite the file.
214
     * @param scaling
215
     *            Degree to which the image should be scaled, in percent. A
216
     *            <code>scaling</code> value of <code>100</code> produces an
217
     *            unscaled image. This value must be greater than <code>0</code>
218
     *            and less than or equal to <code>200</code>.
219
     * @param createDirs
220
     *            Determines whether a path will be created if it does not
221
     *            already exist. A value of <code>true</code> means that all
222
     *            necessary directories that do not exist will be created
223
     *            automatically.
224
     * @param marginTop
225
     *            the extra top margin
226
     * @param marginRight
227
     *            the extra right margin
228
     * @param marginBottom
229
     *            the extra bottom margin
230
     * @param marginLeft
231
     *            the extra left margin
232
     */
233
    public void gdTakeScreenshotOfActiveWindow(String destination, int delay,
234
            String fileAccess, int scaling, boolean createDirs, int marginTop,
235
            int marginRight, int marginBottom, int marginLeft) {
236
        Rectangle activeWindowBounds = getActiveWindowBounds();
237
        
238
        if (activeWindowBounds == null) {
239
            throw new StepExecutionException("No active window found", //$NON-NLS-1$
240
                    EventFactory
241
                        .createActionError(TestErrorEvent.NO_ACTIVE_WINDOW));
242
        }
243
        
244
        int x = activeWindowBounds.x - marginLeft;
245
        int y = activeWindowBounds.y - marginTop;
246
        int width = activeWindowBounds.width + marginLeft + marginRight;
247
        int height = activeWindowBounds.height + marginTop + marginBottom;
248
        
249
        if (width < 1 || height < 1) {
250
            throw new StepExecutionException("Margin parameter lead to negative height or width", //$NON-NLS-1$
251
                    EventFactory
252
                        .createActionError(TestErrorEvent.INVALID_INPUT));
253
        }
254
        
255
        Rectangle screenRect = new Rectangle(x, y, width, height);
256
257
        takeScreenshot(destination, delay, fileAccess, scaling, createDirs,
258
                screenRect);
259
    }
260
261
    /**
262
     * @return an awt rectangle which represents the absolute active window
263
     *         bounds; may return null e.g. if no active window could be found
264
     */
265
    public abstract Rectangle getActiveWindowBounds();
266
267
    
268
    /**
269
     * Takes a screenshot and saves the image to disk, in JPEG format.
270
     * 
271
     * @param destination
272
     *          Path and filename for the created image. If the extension is not
273
     *          ".jpeg" (case-insensitive), ".jpeg" will be appended to the 
274
     *          filename.
275
     * @param delay
276
     *          Amount of time to wait (in milliseconds) before taking the
277
     *          screenshot.
278
     * @param fileAccess
279
     *          Determines how the file will be created if a file with the
280
     *          given name and path already exists:<br>
281
     *          <code>SwingApplicationImplClass.RENAME</code> -
282
     *          The screenshot will be saved with a sequential integer appended 
283
     *          to the filename.<br>
284
     *          <code>SwingApplicationImplClass.OVERWRITE</code> -
285
     *          The screenshot will overwrite the file.
286
     * @param scaling
287
     *          Degree to which the image should be scaled, in percent. A
288
     *          <code>scaling</code> value of <code>100</code> produces an
289
     *          unscaled image. This value must be greater than <code>0</code>
290
     *          and less than or equal to <code>200</code>.
291
     * @param createDirs
292
     *          Determines whether a path will be created if it does not already
293
     *          exist. A value of <code>true</code> means that all necessary 
294
     *          directories that do not exist will be created automatically.
295
     * @param screenShotRect 
296
     *          the rectangle to take the screenshot of
297
     */
298
    private void takeScreenshot(String destination, int delay,
299
            String fileAccess, int scaling, boolean createDirs,
300
            Rectangle screenShotRect) {
301
        if (scaling <= 0 || scaling > 200) {
302
            throw new StepExecutionException(
303
                    "Invalid scaling factor: Must be between 1 and 200", //$NON-NLS-1$
304
                    EventFactory.createActionError(
305
                            TestErrorEvent.INVALID_PARAM_VALUE));
306
        }
307
        
308
        double scaleFactor = scaling * 0.01;
309
310
        // Check if file name is valid
311
        String outFileName = destination;
312
        String imageExtension = getExtension(outFileName);
313
        if (imageExtension.length() == 0) {
314
            // If not, then we simply append the default extension
315
            imageExtension = DEFAULT_IMAGE_FORMAT;
316
            outFileName += EXTENSION_SEPARATOR + imageExtension;
317
        }
318
319
        // Wait for a user-specified time
320
        if (delay > 0) {
321
            TimeUtil.delay(delay);
322
        }
323
324
        // Create path, if necessary
325
        File pic = new File(outFileName);
326
        if (pic.getParent() == null) {
327
            throw new StepExecutionException(
328
                    "Invalid file name: specify a file name", //$NON-NLS-1$
329
                    EventFactory.createActionError(
330
                            TestErrorEvent.INVALID_PARAM_VALUE));
331
        }
332
        
333
        File path = new File(pic.getParent());
334
        if (createDirs && !path.exists() && !path.mkdirs()) {
335
            throw new StepExecutionException(
336
                "Directory path does not exist and could not be created", //$NON-NLS-1$
337
                EventFactory.createActionError(
338
                    TestErrorEvent.FILE_IO_ERROR));
339
        }
340
341
        // Rename file if file already exists
342
        // FIXME zeb This naming scheme can lead to sorting problems when 
343
        //           filenames have varying numbers of digits (ex. "pic_9" and 
344
        //           "pic_10")
345
        if (fileAccess.equals(RENAME)) {
346
            String completeExtension = 
347
                EXTENSION_SEPARATOR + imageExtension.toLowerCase();
348
            int extensionIndex = 
349
                pic.getName().toLowerCase().lastIndexOf(completeExtension);
350
            String fileName = pic.getName().substring(0, extensionIndex);
351
            for (int i = 1; pic.exists(); i++) {
352
                pic = new File(pic.getParent(), fileName + "_" + i + completeExtension); //$NON-NLS-1$
353
            }
354
        }
355
        
356
        takeScreenshot(screenShotRect, scaleFactor, pic);
357
    }
358
359
    /**
360
     * Takes a screenshot and saves the image to disk. This method will attempt
361
     * to encode the image according to the file extension of the given
362
     * output file. If this is not possible (because the encoding type
363
     * is not supported), then the default encoding type will be used. If
364
     * the default encoding type is used, an appropriate extension will be added
365
     * to the filename.
366
     * 
367
     * @param captureRect
368
     *          Rect to capture in screen coordinates.
369
     * @param scaleFactor
370
     *          Degree to which the image should be scaled, in percent. A
371
     *          <code>scaleFactor</code> of <code>100</code> produces an
372
     *          unscaled image. This value must be greater than <code>0</code>
373
     *          and less than or equal to <code>200</code>.
374
     * @param outputFile
375
     *          Path and filename for the created image.
376
     */
377
    public void takeScreenshot(
378
            Rectangle captureRect, double scaleFactor, File outputFile) {
379
        // Create screenshot
380
        java.awt.Robot robot;
381
        File out = outputFile;
382
        
383
        try {
384
            robot = new java.awt.Robot();
385
            BufferedImage image = robot.createScreenCapture(captureRect);
386
387
            int scaledWidth = (int) Math.floor(image.getWidth() * scaleFactor);
388
            int scaledHeight = 
389
                (int) Math.floor(image.getHeight() * scaleFactor);
390
            BufferedImage imageOut = 
391
                new BufferedImage(scaledWidth,
392
                    scaledHeight, BufferedImage.TYPE_INT_RGB);
393
            // Scale it to the new size on-the-fly
394
            Graphics2D graphics2D = imageOut.createGraphics();
395
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,
396
                    RenderingHints.VALUE_RENDER_QUALITY);
397
            graphics2D.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
398
399
            // Save captured image using given format, if supported.
400
            String extension = getExtension(out.getName());
401
            if (extension.length() == 0
402
                    || !ImageIO.getImageWritersBySuffix(extension).hasNext()
403
                    || !ImageIO.write(imageOut, extension, out)) {
404
                
405
                // Otherwise, save using default format
406
                out = new File(outputFile.getPath() 
407
                        + EXTENSION_SEPARATOR + DEFAULT_IMAGE_FORMAT);
408
                if (!ImageIO.write(imageOut, DEFAULT_IMAGE_FORMAT, out)) {
409
                    
410
                    // This should never happen, so log as error if it does.
411
                    // In this situation, the screenshot will not be saved, but
412
                    // the test step will still be marked as successful.
413
                    log.error("Screenshot could not be saved. " + //$NON-NLS-1$
414
                            "Default image format (" + DEFAULT_IMAGE_FORMAT  //$NON-NLS-1$
415
                            + ") is not supported."); //$NON-NLS-1$
416
                }
417
            }
418
            
419
        } catch (AWTException e) {
420
            throw new RobotException(e);
421
        } catch (IOException e) {
422
            throw new StepExecutionException(
423
                    "Screenshot could not be saved", //$NON-NLS-1$
424
                    EventFactory.createActionError(
425
                            TestErrorEvent.FILE_IO_ERROR));
426
        }
427
    }
428
    
429
    /**
430
     * Waits a specified time. 
431
     * @param timeMilliSec the time to wait in MilliSec
432
     */
433
    public void gdWait(int timeMilliSec) {
434
        TimeUtil.delay(timeMilliSec);
435
    }
436
437
    /**
438
     * shows a ConfirmDialog and Pause the Execution of the Test until Window 
439
     * is closed
440
     */
441
    public void gdPause() {
442
        throw new ExecutionEvent(ExecutionEvent.PAUSE_EXECUTION);
443
    }
444
445
    /**
446
     * Does nothing! The restart is implemented in the client but the server
447
     * must have an action to execute.
448
     */
449
    public void gdRestart() {
450
        // nothing
451
    }
452
    
453
    /**
454
     * Types the given text without checking location or event confirmation.
455
     * 
456
     * @param text The text to type.
457
     */
458
    public void gdNativeInputText(String text) {
459
        try {
460
            KeyTyper.getInstance().nativeTypeString(text);
461
        } catch (AWTException e) {
462
            throw new RobotException(e);
463
        }
464
    }
465
466
    /**
467
     * Action to perform a manual test step on server side; opens a window and
468
     * wait's for real user interaction
469
     * 
470
     * @param actionToPerform
471
     *            a textual description of the action to perform in the AUT
472
     * @param expectedBehavior
473
     *            a textual description of the expected behaviour
474
     * @param timeout
475
     *            the timeout
476
     */
477
    public void gdManualTestStep(String actionToPerform, 
478
            String expectedBehavior, int timeout) {
479
    // empty implementation: implementation can be found in the corresponding
480
    // postExecutionCommand
481
    }
482
    
483
    /**
484
     * Perform a keystroke specified according <a
485
     * href=http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)>
486
     * string representation of a keystroke </a>.
487
     * This method does not wait for event confirmation, as we have no way of 
488
     * confirming events on OS-native widgets.
489
     * 
490
     * @param modifierSpec the string representation of the modifiers
491
     * @param keySpec the string representation of the key
492
     */
493
    public void gdNativeKeyStroke(String modifierSpec, String keySpec) {        
494
        if (keySpec == null || keySpec.trim().length() == 0) {
495
            throw new StepExecutionException("The base key of the key stroke " //$NON-NLS-1$
496
                + "must not be null or empty", //$NON-NLS-1$
497
                EventFactory.createActionError());
498
        }
499
500
        try {
501
        
502
            KeyTyper typer = KeyTyper.getInstance();
503
            String keyStrokeSpec = keySpec.trim().toUpperCase();
504
            String mod = KeyStrokeUtil.getModifierString(modifierSpec);
505
            if (mod.length() > 0) {
506
                keyStrokeSpec = mod + " " + keyStrokeSpec; //$NON-NLS-1$
507
            }
508
            
509
            typer.type(keyStrokeSpec, null, null, null);
510
511
        } catch (AWTException e) {
512
            throw new RobotException(e);
513
        }
514
        
515
    }
516
517
    /**
518
     * Action to set the value of a variable in the Client.
519
     * 
520
     * @param variable The name of the variable.
521
     * @param value The new value for the variable.
522
     * @return the new value for the variable.
523
     */
524
    public String gdSetValue(String variable, String value) {
525
        return value;
526
    }
527
    
528
    /**
529
     * @return The Robot instance
530
     */
531
    protected abstract IRobot getRobot();
532
533
    /**
534
     * 
535
     * @param filename A filename for which to find the extension.
536
     * @return the file extension for the given filename. For example,
537
     *         "png" for "example.png". Returns an empty string if the given
538
     *         name has no extension. This is the case if the given name does
539
     *         not contain an instance of the extension separator or ends with
540
     *         the extension separator. For example, "example" or "example.".
541
     */
542
    private String getExtension(String filename) {
543
        File file = new File(filename);
544
        int extensionIndex = file.getName().lastIndexOf(EXTENSION_SEPARATOR);
545
        return extensionIndex == -1 
546
                    || extensionIndex == file.getName().length() - 1 
547
                ? StringConstants.EMPTY
548
                : file.getName().substring(extensionIndex + 1); 
549
    }
550
    
551
    /**
552
     * method to copy a string to the system clipboard
553
     * 
554
     * @param text The text to copy
555
     */
556
    public void gdCopyToClipboard(final String text) {      
557
        StringSelection strSel = new StringSelection(text);
558
        try {
559
            Toolkit.getDefaultToolkit().getSystemClipboard()
560
                .setContents(strSel, null);
561
        } catch (IllegalStateException ise) {
562
            throw new StepExecutionException(
563
                    "Clipboard not available.", //$NON-NLS-1$
564
                    EventFactory.createActionError(
565
                            TestErrorEvent.CLIPBOARD_NOT_AVAILABLE));
566
        }
567
    }
568
    
569
    /**
570
     * method to compare to values
571
     * 
572
     * @param value1
573
     *            the first value for comparison
574
     * @param comparisonMethod
575
     *            the comparison method
576
     * @param value2
577
     *            the second value for comparison
578
     */
579
    public void gdCheckValues(final String value1,
580
            final String comparisonMethod, final String value2) {
581
        Comparer.compare(value1, value2, comparisonMethod);
582
    }
583
    
584
    /**
585
     * method to compare to strings
586
     * 
587
     * @param value1
588
     *            the first value for comparison
589
     * @param operator
590
     *            the comparison method
591
     * @param value2
592
     *            the second value for comparison
593
     */
594
    public void gdCheckStringValues(final String value1,
595
            final String operator, final String value2) {
596
        Verifier.match(value1, value2, operator);
597
    }
598
599
    /**
600
     * Does nothing! The start timer is implemented in the client but the server
601
     * must have an action to execute.
602
     * @param timerName the name for the timer
603
     * @param variableName the variable name to store the current time in millisecs in
604
     */
605
    public void gdStartTimer(String timerName, String variableName) {
606
    // empty
607
    }
608
609
    /**
610
     * Does nothing! The read timer is implemented in the client but the server
611
     * must have an action to execute.
612
     * @param timerName the name for the timer
613
     * @param variableName the variable name to store the current time delta in millisecs in
614
     */
615
    public void gdReadTimer(String timerName, String variableName) {
616
    // empty
617
    }
618
    
619
    /**
620
     * activate the AUT
621
     * 
622
     * @param method activation method
623
     */
624
    public void gdActivate(String method) {
625
        getRobot().activateApplication(method);
626
    }
627
    
628
    /**
629
     * clicks into the active window.
630
     * 
631
     * @param count amount of clicks
632
     * @param button what mouse button should be used
633
     * @param xPos what x position
634
     * @param xUnits should x position be pixel or percent values
635
     * @param yPos what y position
636
     * @param yUnits should y position be pixel or percent values
637
     * @throws StepExecutionException error
638
     */
639
    public void gdClickDirect(int count, int button, 
640
        int xPos, String xUnits, int yPos, String yUnits) 
641
        throws StepExecutionException {
642
        
643
        Object activeWindow = getActiveWindow();
644
        if (activeWindow != null) {
645
            getRobot().click(activeWindow, null, 
646
                ClickOptions.create()
647
                    .setClickCount(count)
648
                    .setConfirmClick(false)
649
                    .setMouseButton(button), 
650
                xPos, 
651
                xUnits.equalsIgnoreCase(POS_UNIT_PIXEL), 
652
                yPos, 
653
                yUnits.equalsIgnoreCase(POS_UNIT_PIXEL));
654
        } else {
655
            throw new StepExecutionException("No active window.", //$NON-NLS-1$
656
                EventFactory.createActionError(
657
                    TestErrorEvent.NO_ACTIVE_WINDOW));
658
        }
659
    }
660
661
    /**
662
     * Checks for the existence of a window with the given title
663
     * 
664
     * @param title
665
     *            the title
666
     * @param operator
667
     *            the comparing operator
668
     * @param exists
669
     *            <code>True</code> if the window is expected to exist and be
670
     *            visible, otherwise <code>false</code>.
671
     */
672
//    public void gdCheckExistenceOfWindow(final String title, String operator,
673
//            boolean exists) {
674
//        Verifier.equals(exists, isWindowOpen(title, operator));
675
//    }
676
    
677
    /**
678
     * Just a server side method, not useable as action.
679
     * 
680
     * @param keyCode The key code
681
     */
682
    public void gdKeyType(int keyCode) {
683
        getRobot().keyType(null, keyCode);
684
    }
685
    
686
    /**
687
     * Just a server side method, not useable as action.
688
     * 
689
     * note : this action only works if application got focus,
690
     * because using defaultToolkit does not work. You have to
691
     * use component.getToolKit()s
692
     * @param key to set
693
     *      numlock Num Lock 1
694
     *      caplock Caps Lock 2 
695
     *      scolllock Scroll 3
696
     * @param activated 
697
     *      boolean
698
     */
699
    public void gdToggle(int key, boolean activated) {
700
        
701
        int event = getEventCode(key);
702
        if (event != 0) {
703
            try {
704
                getRobot().keyToggle(getFocusOwner(), 
705
                    event, activated);
706
            } catch (UnsupportedOperationException usoe) {
707
                throw new StepExecutionException(
708
                    TestErrorEvent.UNSUPPORTED_OPERATION_ERROR,
709
                    EventFactory.createActionError(
710
                        TestErrorEvent.UNSUPPORTED_OPERATION_ERROR));
711
            } catch (OsNotSupportedException e) {
712
                throw new StepExecutionException(
713
                        TestErrorEvent.UNSUPPORTED_OPERATION_ERROR,
714
                        EventFactory.createActionError(
715
                            TestErrorEvent.UNSUPPORTED_OPERATION_ERROR));
716
            }
717
        }
718
    }
719
720
    /**
721
     * perform a keystroke
722
     * @param modifierSpec the string representation of the modifiers
723
     * @param keySpec the string representation of the key
724
     */
725
    public abstract void gdKeyStroke(String modifierSpec, String keySpec);
726
    /**
727
     * 
728
     * @return the Focus Owner
729
     */
730
    protected abstract Object getFocusOwner();
731
    /**
732
     * 
733
     * @param key to set
734
     *      numlock Num Lock 1
735
     *      caplock Caps Lock 2 
736
     *      scolllock Scroll 3
737
     * @return the toolkit specific eventcode
738
     */
739
    protected abstract int getEventCode(int key);
740
741
    /**
742
     * 
743
     * @return The active application window, or <code>null</code> if no 
744
     *         application window is currently active.
745
     */
746
    protected abstract Object getActiveWindow();
747
    
748
    
749
}
(-)a/org.eclipse.jubula.rc.swing/src/org/eclipse/jubula/rc/swing/swing/caps/SwingApplicationCAPs.java (+577 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2012 BREDEX GmbH.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     BREDEX GmbH - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jubula.rc.swing.swing.caps;
12
13
import java.awt.AWTEvent;
14
import java.awt.Component;
15
import java.awt.Dialog;
16
import java.awt.Frame;
17
import java.awt.Rectangle;
18
import java.awt.Toolkit;
19
import java.awt.Window;
20
import java.awt.event.AWTEventListener;
21
import java.awt.event.ComponentEvent;
22
import java.awt.event.KeyEvent;
23
import java.awt.event.WindowEvent;
24
import java.util.Collection;
25
import java.util.ConcurrentModificationException;
26
import java.util.Iterator;
27
28
import org.eclipse.jubula.rc.common.AUTServer;
29
import org.eclipse.jubula.rc.common.caps.AbstractApplicationCAPs;
30
import org.eclipse.jubula.rc.common.driver.ClickOptions;
31
import org.eclipse.jubula.rc.common.driver.IRobot;
32
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
33
import org.eclipse.jubula.rc.common.implclasses.MatchUtil;
34
import org.eclipse.jubula.rc.common.implclasses.Verifier;
35
import org.eclipse.jubula.rc.common.listener.EventLock;
36
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
37
import org.eclipse.jubula.rc.common.util.KeyStrokeUtil;
38
import org.eclipse.jubula.rc.swing.components.SwingComponent;
39
import org.eclipse.jubula.rc.swing.listener.ComponentHandler;
40
import org.eclipse.jubula.rc.swing.listener.FocusTracker;
41
import org.eclipse.jubula.rc.swing.swing.implclasses.EventListener;
42
import org.eclipse.jubula.rc.swing.swing.implclasses.WindowHelper;
43
import org.eclipse.jubula.rc.swing.swing.interfaces.IGraphicApplication;
44
import org.eclipse.jubula.tools.constants.StringConstants;
45
import org.eclipse.jubula.tools.objects.event.EventFactory;
46
import org.eclipse.jubula.tools.objects.event.TestErrorEvent;
47
import org.eclipse.jubula.tools.utils.TimeUtil;
48
/**
49
 * 
50
 * @author BREDEX GmbH
51
 *
52
 */
53
public class SwingApplicationCAPs extends AbstractApplicationCAPs implements
54
        IGraphicApplication {
55
56
    /**
57
     * This condition is true if the event is an 'window opened' event
58
     * and the event source is a frame/dialog with a certain title.
59
     * It is also true if the event is a 'component shown' event and the 
60
     * event source is a frame/dialog with a certain title.
61
     */
62
    private static class WindowOpenedCondition 
63
        implements EventListener.Condition {
64
        /**
65
         * the title
66
         */
67
        private final String m_title;
68
        
69
        /** the matches operation */
70
        private final String m_operator;
71
        
72
        /**
73
         * constructor
74
         * 
75
         * @param title the title
76
         * @param operator the matches operation
77
         */
78
        public WindowOpenedCondition(String title, String operator) {
79
            m_title = title;
80
            m_operator = operator;
81
        }
82
        /**
83
         * {@inheritDoc}
84
         */
85
        public boolean isTrue(AWTEvent event) {
86
            if (event.getID() != WindowEvent.WINDOW_OPENED
87
                && event.getID() != ComponentEvent.COMPONENT_SHOWN) {
88
                return false;
89
            }
90
            if (event.getSource() instanceof Frame) {
91
                Frame frame = (Frame)event.getSource();
92
                return MatchUtil.getInstance().match(
93
                    frame.getTitle(), m_title, m_operator);
94
            } else if (event.getSource() instanceof Dialog) {
95
                Dialog dialog = (Dialog)event.getSource();
96
                return MatchUtil.getInstance().match(
97
                    dialog.getTitle(), m_title, m_operator);
98
            } else {
99
                return false;
100
            }
101
        }
102
    }
103
    
104
    /**
105
     * This condition is true if the event is an 'window activated' event
106
     * and the event source is a frame/dialog with a certain title.
107
     */
108
    private static class WindowActivatedCondition 
109
        implements EventListener.Condition {
110
        /**
111
         * the title
112
         */
113
        private final String m_title;
114
        
115
        /** the matches operation */
116
        private final String m_operator;
117
        
118
        /**
119
         * constructor
120
         * 
121
         * @param title the title
122
         * @param operator the matches operation
123
         */
124
        public WindowActivatedCondition(String title, String operator) {
125
            m_title = title;
126
            m_operator = operator;
127
        }
128
        /**
129
         * {@inheritDoc}
130
         */
131
        public boolean isTrue(AWTEvent event) {
132
            if (event.getID() != WindowEvent.WINDOW_ACTIVATED) {
133
                return false;
134
            }
135
            if (event.getSource() instanceof Frame) {
136
                Frame frame = (Frame)event.getSource();
137
                return MatchUtil.getInstance().match(
138
                    frame.getTitle(), m_title, m_operator);
139
            } else if (event.getSource() instanceof Dialog) {
140
                Dialog dialog = (Dialog)event.getSource();
141
                return MatchUtil.getInstance().match(
142
                    dialog.getTitle(), m_title, m_operator);
143
            } else {
144
                return false;
145
            }
146
        }
147
    }
148
149
    /**
150
     * This condition is true if the event is an 'window closed' event
151
     * and the event source is a frame/dialog with a certain title.
152
     * It is also true if the event is a 'component hidden' event and the 
153
     * event source is a frame/dialog with a certain title.
154
     */
155
    private static class WindowClosedCondition 
156
        implements EventListener.Condition {
157
        /**
158
         * the title
159
         */
160
        private final String m_title;
161
        
162
        /** the matches operation */
163
        private final String m_operator;
164
        
165
        /**
166
         * constructor
167
         * 
168
         * @param title the title
169
         * @param operator the matches operation
170
         */
171
        public WindowClosedCondition(String title, String operator) {
172
            m_title = title;
173
            m_operator = operator;
174
        }
175
        /**
176
         * {@inheritDoc}
177
         */
178
        public boolean isTrue(AWTEvent event) {
179
            if (event.getID() != WindowEvent.WINDOW_CLOSED
180
                && event.getID() != ComponentEvent.COMPONENT_HIDDEN) {
181
                return false;
182
            }
183
            if (event.getSource() instanceof Frame) {
184
                Frame frame = (Frame)event.getSource();
185
                return MatchUtil.getInstance().match(
186
                    frame.getTitle(), m_title, m_operator);
187
            } else if (event.getSource() instanceof Dialog) {
188
                Dialog dialog = (Dialog)event.getSource();
189
                return MatchUtil.getInstance().match(
190
                    dialog.getTitle(), m_title, m_operator);
191
            } else {
192
                return false;
193
            }
194
        }
195
    }
196
197
    /**
198
     * The logging.
199
     */
200
    private static AutServerLogger log = 
201
        new AutServerLogger(SwingApplicationCAPs.class);
202
    
203
    /**
204
     * {@inheritDoc}
205
     */
206
    public String[] getTextArrayFromComponent() {
207
        return null;
208
    }
209
210
211
    /**
212
     * Types <code>text</code> into the component. This replaces the shown
213
     * content.
214
     * @param text the text to type in
215
     * @deprecated Removed without substitution:
216
     * Testcases with this action are fragile, because this action assumes the
217
     * availabality of a text component. Any other case breaks the test.
218
     */
219
    public void gdReplaceText(String text) {
220
        getRobot().click(FocusTracker.getFocusOwner(), null, 
221
            ClickOptions.create().setClickCount(3).left());
222
        if (StringConstants.EMPTY.equals(text)) {
223
            getRobot().keyStroke("DELETE"); //$NON-NLS-1$
224
        }
225
        gdInputText(text);
226
    }
227
228
    /**
229
     * Waits <code>timeMillSec</code> if the application opens a window
230
     * with the given title.
231
     * 
232
     * @param title the title
233
     * @param operator the comparing operator
234
     * @param pTimeout the time in ms
235
     * @param delay delay after the window is shown
236
     */
237
    public void gdWaitForWindow(final String title, String operator, 
238
        int pTimeout, int delay) {
239
        
240
        EventListener.Condition cond = 
241
            new WindowOpenedCondition(title, operator);
242
        EventLock lock = new EventLock();
243
        AWTEventListener listener = new EventListener(lock, cond);
244
        Toolkit.getDefaultToolkit().addAWTEventListener(listener,
245
                AWTEvent.WINDOW_EVENT_MASK | AWTEvent.COMPONENT_EVENT_MASK);
246
247
        if (isWindowOpen(title, operator)) {
248
            lock.release();
249
        }
250
        try {
251
            synchronized (lock) {
252
                long timeout = pTimeout;
253
                long done = System.currentTimeMillis() + timeout;
254
                long now;
255
                while (!lock.isReleased() && (timeout > 0)) {
256
                    try {
257
                        lock.wait(timeout);
258
                        now = System.currentTimeMillis();
259
                        timeout = done - now;
260
                    } catch (InterruptedException e) {
261
                        // ignore
262
                    }
263
                }
264
            }
265
        } finally {
266
            Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
267
        }
268
        if (!lock.isReleased() && !isWindowOpen(title, operator)) {
269
            throw new StepExecutionException("window did not open", //$NON-NLS-1$
270
                    EventFactory.createActionError(
271
                            TestErrorEvent.TIMEOUT_EXPIRED));
272
        }
273
        TimeUtil.delay(delay);
274
    }
275
    /**
276
     * Waits <code>timeMillSec</code> if the application activates a window
277
     * with the given title.
278
     * 
279
     * @param title the title
280
     * @param operator the comparing operator
281
     * @param pTimeout the time in ms
282
     * @param delay delay after the window is activated
283
     */
284
    public void gdWaitForWindowActivation(final String title, String operator, 
285
        int pTimeout, int delay) {
286
        
287
        EventListener.Condition cond = new WindowActivatedCondition(title, 
288
                operator);
289
        EventLock lock = new EventLock();
290
        AWTEventListener listener = new EventListener(lock, cond);
291
        Toolkit.getDefaultToolkit().addAWTEventListener(listener,
292
                AWTEvent.WINDOW_EVENT_MASK);
293
        
294
        if (isWindowActive(title, operator)) {
295
            lock.release();
296
        }
297
        try {
298
            synchronized (lock) {
299
                long timeout = pTimeout;
300
                long done = System.currentTimeMillis() + timeout;
301
                long now;
302
                while (!lock.isReleased() && (timeout > 0)) {
303
                    try {
304
                        lock.wait(timeout);
305
                        now = System.currentTimeMillis();
306
                        timeout = done - now;
307
                    } catch (InterruptedException e) {
308
                        // ignore
309
                    }
310
311
                }
312
            }
313
        } finally {
314
            Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
315
        }
316
        if (!lock.isReleased() && !isWindowActive(title, operator)) {
317
            throw new StepExecutionException("window was not activated", //$NON-NLS-1$
318
                    EventFactory.createActionError(
319
                            TestErrorEvent.TIMEOUT_EXPIRED));
320
        }
321
        TimeUtil.delay(delay);
322
    }
323
    
324
    /**
325
     * Waits <code>timeMillSec</code> if the application closes (or hides) 
326
     * a window with the given title. If no window with the given title can
327
     * be found, then it is assumed that the window has already closed.
328
     * 
329
     * @param title the title
330
     * @param operator the comparing operator
331
     * @param pTimeout the time in ms
332
     * @param delay delay after the window is closed
333
     */
334
    public void gdWaitForWindowToClose(final String title, String operator, 
335
        int pTimeout, int delay) {
336
        
337
        EventListener.Condition cond = 
338
            new WindowClosedCondition(title, operator);
339
        EventLock lock = new EventLock();
340
        AWTEventListener listener = new EventListener(lock, cond);
341
342
        Toolkit.getDefaultToolkit().addAWTEventListener(listener,
343
                AWTEvent.WINDOW_EVENT_MASK);
344
        if (!isWindowOpen(title, operator)) {
345
            lock.release();
346
        }
347
        
348
        try {
349
            synchronized (lock) {
350
                long timeout = pTimeout;
351
                long done = System.currentTimeMillis() + timeout;
352
                long now;
353
                while (!lock.isReleased() && (timeout > 0)) {
354
                    try {
355
                        lock.wait(timeout);
356
                        now = System.currentTimeMillis();
357
                        timeout = done - now;
358
                    } catch (InterruptedException e) {
359
                        // ignore
360
                    }
361
                }
362
            }
363
        } finally {
364
            Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
365
        }
366
        if (!lock.isReleased() && isWindowOpen(title, operator)) {
367
            throw new StepExecutionException("window did not close", //$NON-NLS-1$
368
                    EventFactory.createActionError(
369
                            TestErrorEvent.TIMEOUT_EXPIRED));
370
        }
371
        
372
        TimeUtil.delay(delay);
373
    }
374
375
    /**
376
     * Checks for the existence of a window with the given title
377
     * 
378
     * @param title
379
     *            the title
380
     * @param operator
381
     *            the comparing operator
382
     * @param exists
383
     *            <code>True</code> if the window is expected to exist and be
384
     *            visible, otherwise <code>false</code>.
385
     */
386
    public void gdCheckExistenceOfWindow(final String title, String operator,
387
            boolean exists) {
388
        Verifier.equals(exists, isWindowOpen(title, operator));
389
    }
390
391
    /**
392
     * {@inheritDoc}
393
     */
394
    public Rectangle getActiveWindowBounds() {
395
        Window activeWindow = WindowHelper.getActiveWindow();
396
        if (activeWindow != null) {
397
            Rectangle activeWindowBounds = 
398
                new Rectangle(activeWindow.getBounds()); 
399
            activeWindowBounds.setLocation(activeWindow.getLocationOnScreen());
400
            
401
            return activeWindowBounds;
402
        }
403
        return null;
404
    }
405
    /**
406
     * {@inheritDoc}
407
     */
408
    protected IRobot getRobot() {
409
        return AUTServer.getInstance().getRobot();
410
    }
411
412
    /**
413
     * perform a keystroke specified according <a
414
     * href=http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)>
415
     * string representation of a keystroke </a>,
416
     * 
417
     * @param modifierSpec the string representation of the modifiers
418
     * @param keySpec the string representation of the key
419
     */
420
    public void gdKeyStroke(String modifierSpec, String keySpec) {
421
        if (keySpec == null || keySpec.trim().length() == 0) {
422
            throw new StepExecutionException(
423
                "The base key of the key stroke must not be null or empty", //$NON-NLS-1$
424
                EventFactory.createActionError());
425
        }
426
        String key = keySpec.trim().toUpperCase();
427
        String mod = KeyStrokeUtil.getModifierString(modifierSpec);
428
        if (mod.length() > 0) {
429
            getRobot().keyStroke(mod.toString() + " " + key); //$NON-NLS-1$
430
        } else {
431
            int code = getKeyCode(key);
432
            if (code != -1) {
433
                gdKeyType(code);
434
            } else {
435
                getRobot().keyStroke(key);
436
            }
437
        }
438
    }
439
    /**
440
     * {@inheritDoc}
441
     */
442
    protected Object getFocusOwner() {
443
        return FocusTracker.getFocusOwner();
444
    }
445
446
    /**
447
     * {@inheritDoc}
448
     */
449
    protected int getEventCode(int key) {
450
        int event = 0;
451
        switch (key) {
452
            case 1 : 
453
                event = KeyEvent.VK_NUM_LOCK;
454
                break;
455
            case 2 : 
456
                event = KeyEvent.VK_CAPS_LOCK;
457
                break;
458
            case 3 : 
459
                event = KeyEvent.VK_SCROLL_LOCK;
460
                break;
461
            default : 
462
                break;
463
        }
464
        return event;
465
    }
466
467
    /**
468
     * {@inheritDoc}
469
     */
470
    protected Object getActiveWindow() {
471
        return WindowHelper.getActiveWindow();
472
    }
473
474
    /**
475
     * Returns <code>true</code> if a window with the given title is open and 
476
     * visible.
477
     * 
478
     * @param title the title
479
     * @param operator the matches/equals operator
480
     * @return if the window is open and visible
481
     */
482
    private boolean isWindowOpen(String title, String operator) {
483
        boolean wasInterrupted;
484
        do {
485
            try {
486
                wasInterrupted = false;
487
                Collection components = ComponentHandler.getAutHierarchy()
488
                        .getHierarchyMap().keySet();
489
                for (Iterator it = components.iterator(); it.hasNext();) {
490
                    Component c = ((SwingComponent)it.next())
491
                        .getRealComponent();
492
                    if (c.isShowing()) {
493
                        if (c instanceof Frame) {
494
                            Frame frame = (Frame)c;
495
                            if (MatchUtil.getInstance().match(frame.getTitle(), 
496
                                title, operator)) {
497
498
                                return true;
499
                            }
500
                        }
501
                        if (c instanceof Dialog) {
502
                            Dialog dialog = (Dialog)c;
503
                            if (MatchUtil.getInstance().match(dialog.getTitle(),
504
                                title, operator)) {
505
506
                                return true;
507
                            }
508
                        }
509
                    }
510
                }
511
            } catch (ConcurrentModificationException e) {
512
                log.debug("hierarchy modified while traversing", e); //$NON-NLS-1$
513
                wasInterrupted = true;
514
            }
515
        } while (wasInterrupted);
516
        return false;
517
    }
518
    
519
    /**
520
     * Returns <code>true</code> if a window with the given title has focus
521
     * 
522
     * @param title the title
523
     * @param operator the matches/equals operator
524
     * @return if the window has focus
525
     */
526
    private boolean isWindowActive(String title, String operator) {
527
        
528
        Window activeWindow = WindowHelper.getActiveWindow();
529
        if (activeWindow != null) {
530
            String windowTitle = null;
531
            if (activeWindow instanceof Dialog) {
532
                windowTitle = ((Dialog)activeWindow).getTitle();
533
            } else if (activeWindow instanceof Frame) {
534
                windowTitle = ((Frame)activeWindow).getTitle();
535
            }
536
            
537
            if (MatchUtil.getInstance().match(windowTitle, title, operator)) {
538
                return true;
539
            }
540
        }
541
542
        return false;
543
    }
544
    
545
    /**
546
     * @param keyCodeName
547
     *            The name of a key code, e.g. <code>TAB</code> for a
548
     *            tabulator key code
549
     * @return The key code or <code>-1</code>, if the key code name doesn't
550
     *         exist in the <code>KeyEvent</code> class
551
     * @throws StepExecutionException
552
     *             If the key code name cannot be converted to a key code due to
553
     *             the reflection call
554
     */
555
    public int getKeyCode(String keyCodeName) throws StepExecutionException {
556
        int code = -1;
557
        String codeName = "VK_" + keyCodeName; //$NON-NLS-1$
558
        try {
559
            code = KeyEvent.class.getField(codeName).getInt(KeyEvent.class);
560
        } catch (IllegalArgumentException e) {
561
            throw new StepExecutionException(e.getMessage(), EventFactory
562
                .createActionError());
563
        } catch (SecurityException e) {
564
            throw new StepExecutionException(e.getMessage(), EventFactory
565
                .createActionError());
566
        } catch (IllegalAccessException e) {
567
            throw new StepExecutionException(e.getMessage(), EventFactory
568
                .createActionError());
569
        } catch (NoSuchFieldException e) {
570
            if (log.isInfoEnabled()) {
571
                log.info("The key expression '" + keyCodeName //$NON-NLS-1$
572
                    + "' is not a key code, typed as key stroke instead"); //$NON-NLS-1$
573
            }
574
        }
575
        return code;
576
    }
577
}
(-)a/org.eclipse.jubula.rc.swt/src/org/eclipse/jubula/rc/swt/caps/SwtApplicationCAPs.java (+548 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2012 BREDEX GmbH.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     BREDEX GmbH - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jubula.rc.swt.caps;
12
13
import java.awt.Rectangle;
14
import java.util.Collection;
15
import java.util.ConcurrentModificationException;
16
import java.util.Iterator;
17
18
import org.eclipse.jubula.rc.common.AUTServer;
19
import org.eclipse.jubula.rc.common.caps.AbstractApplicationCAPs;
20
import org.eclipse.jubula.rc.common.driver.ClickOptions;
21
import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer;
22
import org.eclipse.jubula.rc.common.driver.IRobot;
23
import org.eclipse.jubula.rc.common.driver.IRobotFactory;
24
import org.eclipse.jubula.rc.common.driver.IRunnable;
25
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
26
import org.eclipse.jubula.rc.common.implclasses.MatchUtil;
27
import org.eclipse.jubula.rc.common.implclasses.Verifier;
28
import org.eclipse.jubula.rc.common.listener.EventLock;
29
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
30
import org.eclipse.jubula.rc.common.util.KeyStrokeUtil;
31
import org.eclipse.jubula.rc.common.util.WorkaroundUtil;
32
import org.eclipse.jubula.rc.swt.SwtAUTServer;
33
import org.eclipse.jubula.rc.swt.components.SwtComponent;
34
import org.eclipse.jubula.rc.swt.driver.EventThreadQueuerSwtImpl;
35
import org.eclipse.jubula.rc.swt.driver.RobotFactoryConfig;
36
import org.eclipse.jubula.rc.swt.implclasses.EventListener;
37
import org.eclipse.jubula.rc.swt.implclasses.EventListener.Condition;
38
import org.eclipse.jubula.rc.swt.interfaces.IGraphicApplication;
39
import org.eclipse.jubula.rc.swt.listener.ComponentHandler;
40
import org.eclipse.jubula.rc.swt.listener.FocusTracker;
41
import org.eclipse.jubula.rc.swt.utils.SwtPointUtil;
42
import org.eclipse.jubula.tools.constants.StringConstants;
43
import org.eclipse.jubula.tools.objects.event.EventFactory;
44
import org.eclipse.jubula.tools.objects.event.TestErrorEvent;
45
import org.eclipse.jubula.tools.utils.EnvironmentUtils;
46
import org.eclipse.jubula.tools.utils.TimeUtil;
47
import org.eclipse.swt.SWT;
48
import org.eclipse.swt.widgets.Display;
49
import org.eclipse.swt.widgets.Event;
50
import org.eclipse.swt.widgets.Listener;
51
import org.eclipse.swt.widgets.Shell;
52
import org.eclipse.swt.widgets.Widget;
53
/**
54
 * 
55
 * @author BREDEX GmbH
56
 *
57
 */
58
public class SwtApplicationCAPs extends AbstractApplicationCAPs implements
59
        IGraphicApplication {
60
61
    /** The logging. */
62
    private static AutServerLogger log = 
63
        new AutServerLogger(SwtApplicationCAPs.class);
64
    
65
    /**
66
     * This condition is true if the event source is a Shell with a matching
67
     * title.
68
     *
69
     * @author BREDEX GmbH
70
     * @created Jun 17, 2009
71
     */
72
    private static class WindowEventCondition implements Condition {
73
74
        /** the expected window title */
75
        private String m_windowTitle;
76
        
77
        /** the operator used for matching the window title */
78
        private String m_matchingOperator;
79
80
        /** 
81
         * determines whether the event source being disposed should be 
82
         * treated as a match 
83
         */
84
        private boolean m_valForDisposed;
85
        
86
        /**
87
         * Constructor
88
         * 
89
         * @param windowTitle The expected window title.
90
         * @param matchingOperator The operator used for matching the
91
         *                         window title.
92
         * @param valForDisposed Whether the event source being disposed
93
         *                       should be treated as a match.
94
         */
95
        public WindowEventCondition(String windowTitle, 
96
                String matchingOperator, boolean valForDisposed) {
97
            m_windowTitle = windowTitle;
98
            m_matchingOperator = matchingOperator;
99
            m_valForDisposed = valForDisposed;
100
        }
101
        
102
        /**
103
         * {@inheritDoc}
104
         */
105
        public boolean isTrue(Event event) {
106
            if (event.widget instanceof Shell) {
107
                Shell window = (Shell)event.widget;
108
                if (window.isDisposed()) {
109
                    return m_valForDisposed;
110
                }
111
                return MatchUtil.getInstance().match(window.getText(), 
112
                        m_windowTitle, m_matchingOperator);
113
114
            }
115
116
            return false;
117
        }
118
        
119
    }
120
    
121
    /** The Robot factory. */
122
    private IRobotFactory m_robotFactory;
123
    
124
    /**
125
     * @return The Robot factory instance
126
     */
127
    private IRobotFactory getRobotFactory() {
128
        if (m_robotFactory == null) {
129
            m_robotFactory = new RobotFactoryConfig().getRobotFactory();
130
        }
131
        return m_robotFactory;
132
    }
133
    
134
    /**
135
     * {@inheritDoc}
136
     */
137
    public String[] getTextArrayFromComponent() {
138
        return null;
139
    }
140
141
    /**
142
     * Types <code>text</code> into the component. This replaces the shown
143
     * content.
144
     * @param text the text to type in
145
     * @deprecated Removed without substitution:
146
     * Testcases with this action are fragile, because this action assumes the
147
     * availabality of a text component. Any other case breaks the test.
148
     */
149
    public void gdReplaceText(String text) {
150
        // The number of clicks differs from the Swing implementation
151
        // because a double-click selects all of the text
152
        getRobot().click(FocusTracker.getFocusOwner(), null, 
153
            ClickOptions.create().setClickCount(2).left());
154
        if (StringConstants.EMPTY.equals(text)) {
155
            getRobot().keyStroke("DELETE"); //$NON-NLS-1$
156
        }
157
        gdInputText(text);
158
    }
159
160
    /**
161
     * Waits <code>timeMillSec</code> if the application opens a window with the given title.
162
     * @param title the title
163
     * @param operator the comparing operator
164
     * @param timeout the time in ms
165
     * @param delay delay after the window is shown
166
     */
167
    public void gdWaitForWindow(final String title, final String operator, 
168
        int timeout, int delay) {
169
170
        final EventListener.Condition cond = 
171
            new WindowEventCondition(title, operator, false);
172
        final EventLock lock = new EventLock();
173
        final Listener listener = new EventListener(lock, cond);
174
        final Display display = 
175
            ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay();
176
        final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl();
177
        
178
        queuer.invokeAndWait("addWindowOpenedListeners", new IRunnable() { //$NON-NLS-1$
179
            public Object run() {
180
                display.addFilter(SWT.Activate, listener);
181
                display.addFilter(SWT.Show, listener);
182
                if (isWindowOpen(title, operator)) {
183
                    lock.release();
184
                }
185
                
186
                return null;
187
            }
188
        });
189
190
        try {
191
            synchronized (lock) {
192
                long currentTimeout = timeout;
193
                long done = System.currentTimeMillis() + timeout; 
194
                long now;
195
                while (!lock.isReleased() && (currentTimeout > 0)) {
196
                    try {
197
                        lock.wait(currentTimeout);                    
198
                        now = System.currentTimeMillis();
199
                        currentTimeout = done - now;
200
                    } catch (InterruptedException e) {
201
                        // ignore
202
                    }
203
                }                    
204
            }
205
        } finally {
206
            queuer.invokeAndWait("removeWindowOpenedListeners", new IRunnable() { //$NON-NLS-1$
207
                public Object run() {
208
                    display.removeFilter(SWT.Activate, listener);
209
                    display.removeFilter(SWT.Show, listener);
210
                    
211
                    return null;
212
                }
213
            });
214
        }
215
        if (!lock.isReleased()) {
216
            throw new StepExecutionException("window did not open", //$NON-NLS-1$
217
                    EventFactory.createActionError(
218
                            TestErrorEvent.TIMEOUT_EXPIRED));
219
        }
220
        TimeUtil.delay(delay);
221
    }
222
    
223
    /**
224
     * Waits <code>timeMillSec</code> if the application activates a window
225
     * with the given title.
226
     * 
227
     * @param title the title
228
     * @param operator the comparing operator
229
     * @param timeout the time in ms
230
     * @param delay delay after the window is activated
231
     */
232
    public void gdWaitForWindowActivation(final String title, 
233
            final String operator, final int timeout, int delay) {
234
        
235
        final EventListener.Condition cond = 
236
            new WindowEventCondition(title, operator, false);
237
        final EventLock lock = new EventLock();
238
        final Listener listener = new EventListener(lock, cond);
239
        final Display display = 
240
            ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay();
241
        final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl();
242
        
243
        queuer.invokeAndWait("addWindowActiveListeners", new IRunnable() { //$NON-NLS-1$
244
            public Object run() {
245
                display.addFilter(SWT.Activate, listener);
246
                if (isWindowActive(title, operator)) {
247
                    lock.release();
248
                }
249
250
                return null;
251
            }
252
        });
253
        
254
        try {
255
            synchronized (lock) {
256
                long currentTimeout = timeout;
257
                long done = System.currentTimeMillis() + timeout; 
258
                long now;
259
                while (!lock.isReleased() && (currentTimeout > 0)) {
260
                    try {
261
                        lock.wait(currentTimeout);                    
262
                        now = System.currentTimeMillis();
263
                        currentTimeout = done - now;
264
                    } catch (InterruptedException e) {
265
                        // ignore
266
                    }
267
                }                    
268
            }
269
        } finally {
270
            queuer.invokeAndWait("removeWindowActiveListeners", new IRunnable() { //$NON-NLS-1$
271
                public Object run() {
272
                    display.removeFilter(SWT.Activate, listener);
273
                    
274
                    return null;
275
                }
276
            });
277
        }
278
        if (!lock.isReleased()) {
279
            throw new StepExecutionException("window was not activated", //$NON-NLS-1$
280
                    EventFactory.createActionError(
281
                            TestErrorEvent.TIMEOUT_EXPIRED));
282
        }
283
        TimeUtil.delay(delay);
284
    }
285
286
    /**
287
     * Waits <code>timeMillSec</code> if the application closes (or hides) 
288
     * a window with the given title. If no window with the given title can
289
     * be found, then it is assumed that the window has already closed.
290
     * 
291
     * @param title the title
292
     * @param operator the comparing operator
293
     * @param timeout the time in ms
294
     * @param delay delay after the window is activated
295
     */
296
    public void gdWaitForWindowToClose(final String title, 
297
            final String operator, int timeout, int delay) {
298
299
        final EventListener.Condition cond = 
300
            new WindowEventCondition(title, operator, true);
301
        final EventLock lock = new EventLock();
302
        final Listener listener = new EventListener(lock, cond);
303
        final Display display = 
304
            ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay();
305
        final IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl();
306
        
307
        queuer.invokeAndWait("addWindowClosedListeners", new IRunnable() { //$NON-NLS-1$
308
            public Object run() {
309
                display.addFilter(SWT.Close, listener);
310
                display.addFilter(SWT.Hide, listener);
311
                display.addFilter(SWT.Dispose, listener);
312
                if (!isWindowOpen(title, operator)) {
313
                    lock.release();
314
                }
315
                
316
                return null;
317
            }
318
        });
319
320
        try {
321
            synchronized (lock) {
322
                long currentTimeout = timeout;
323
                long done = System.currentTimeMillis() + timeout; 
324
                long now;
325
                while (!lock.isReleased() && (currentTimeout > 0)) {
326
                    try {
327
                        lock.wait(currentTimeout);                    
328
                        now = System.currentTimeMillis();
329
                        currentTimeout = done - now;
330
                    } catch (InterruptedException e) {
331
                        // ignore
332
                    }
333
                }                    
334
            }
335
        } finally {
336
            queuer.invokeAndWait("removeWindowClosedListeners", new IRunnable() { //$NON-NLS-1$
337
                public Object run() {
338
                    display.removeFilter(SWT.Close, listener);
339
                    display.removeFilter(SWT.Hide, listener);
340
                    display.removeFilter(SWT.Dispose, listener);
341
                    
342
                    return null;
343
                }
344
            });
345
        }
346
        if (!lock.isReleased()) {
347
            throw new StepExecutionException("window did not close", //$NON-NLS-1$
348
                    EventFactory.createActionError(
349
                            TestErrorEvent.TIMEOUT_EXPIRED));
350
        }
351
        TimeUtil.delay(delay);
352
    }
353
354
    /**
355
     * Returns <code>true</code> if a window with the given title is open and 
356
     * visible
357
     * 
358
     * @param title the title
359
     * @param operator the matches/equals operator
360
     * @return if the window is open and visible
361
     */
362
    private boolean isWindowOpen(final String title, final String operator) {
363
        boolean wasInterrupted = false;
364
        boolean equal = false;
365
        do {
366
            try {
367
                wasInterrupted = false;
368
                Collection components = ComponentHandler
369
                    .getAutHierarchy().getHierarchyMap()
370
                        .keySet();
371
                for (Iterator it = components.iterator(); it.hasNext();) {
372
                
373
                    Widget comp = ((SwtComponent)it.next()).getRealComponent();
374
                    if (comp instanceof Shell 
375
                            && !comp.isDisposed()
376
                            && ((Shell)comp).isVisible()) {
377
378
                        Shell frame = (Shell)comp;
379
                        if (MatchUtil.getInstance().match(
380
                                frame.getText(), title, operator)) {
381
382
                            equal = true;
383
                            break;
384
                        }
385
                    }
386
                }
387
388
            } catch (ConcurrentModificationException e) {
389
                log.debug("hierarchy modified while traversing", e); //$NON-NLS-1$
390
                wasInterrupted = true;
391
            }
392
        } while (wasInterrupted);
393
        return equal;
394
    }
395
396
    /**
397
     * Checks for the existence of a window with the given title
398
     * 
399
     * @param title
400
     *            the title
401
     * @param operator
402
     *            the comparing operator
403
     * @param exists
404
     *            <code>True</code> if the component is expected to exist and be
405
     *            visible, otherwise <code>false</code>.
406
     */
407
    public void gdCheckExistenceOfWindow(final String title,
408
            final String operator, final boolean exists) {
409
        IEventThreadQueuer queuer = new EventThreadQueuerSwtImpl();
410
        Boolean windowExists = (Boolean)queuer.invokeAndWait(
411
                "isWindowOpen", new IRunnable() { //$NON-NLS-1$
412
                    public Object run() throws StepExecutionException {
413
                        return new Boolean(isWindowOpen(title, operator));
414
                    }
415
                });
416
        Verifier.equals(exists, windowExists.booleanValue());
417
    }
418
419
    /**
420
     * {@inheritDoc}
421
     */
422
    public Rectangle getActiveWindowBounds() {
423
        org.eclipse.swt.graphics.Rectangle activeWindowSize = 
424
            (org.eclipse.swt.graphics.Rectangle)getRobotFactory()
425
                .getEventThreadQueuer().invokeAndWait(
426
                    this.getClass().getName() + ".getActiveWindowBounds", //$NON-NLS-1$
427
                        new IRunnable() {
428
                            public Object run() { // SYNCH THREAD START
429
                                Display d = ((SwtAUTServer)AUTServer
430
                                        .getInstance()).getAutDisplay();
431
                                if (d != null && d.getActiveShell() != null) {
432
                                    return d.getActiveShell().getBounds();
433
                                }
434
                                return null;
435
                            }
436
                        });
437
        if (activeWindowSize != null) {
438
            return SwtPointUtil.toAwtRectangle(activeWindowSize);
439
        }
440
        return null;
441
    }
442
    /**
443
     * {@inheritDoc}
444
     */
445
    protected IRobot getRobot() {
446
        return AUTServer.getInstance().getRobot();
447
    }
448
449
    /**
450
     * perform a keystroke
451
     * @param modifierSpec the string representation of the modifiers
452
     * @param keySpec the string representation of the key
453
     */
454
    public void gdKeyStroke(String modifierSpec, String keySpec) {
455
        if (keySpec == null || keySpec.trim().length() == 0) {
456
            throw new StepExecutionException(
457
                "The base key of the key stroke must not be null or empty", //$NON-NLS-1$
458
                EventFactory.createActionError(
459
                        TestErrorEvent.INVALID_PARAM_VALUE));
460
        }
461
        String keyStrokeSpec = keySpec.trim();
462
        String mod = KeyStrokeUtil.getModifierString(modifierSpec);
463
        if (mod.length() > 0) {
464
            keyStrokeSpec = mod + " " + keyStrokeSpec; //$NON-NLS-1$
465
        }
466
        String keySpecification = keySpec.trim().toLowerCase();
467
        if (EnvironmentUtils.isMacOS() && keySpecification.length() == 1
468
                && keySpecification.charAt(0) == WorkaroundUtil.CHAR_B) {
469
            gdNativeKeyStroke(modifierSpec, keySpec);
470
        } else {
471
            // at this the key stroke specification is not fully fullfilled as the
472
            // key stroke spec base key is not definitly upper case
473
            getRobot().keyStroke(keyStrokeSpec);
474
        }
475
    }
476
    /**
477
     * {@inheritDoc}
478
     */
479
    protected Object getFocusOwner() {
480
        return FocusTracker.getFocusOwner();
481
    }
482
    /**
483
     * {@inheritDoc}
484
     */
485
    protected int getEventCode(int key) {
486
        int event = 0;
487
        switch (key) {
488
            case 1 : 
489
                event = SWT.NUM_LOCK;
490
                break;
491
            case 2 : 
492
                event = SWT.CAPS_LOCK;
493
                break;
494
            case 3 : 
495
                event = SWT.SCROLL_LOCK;
496
                break;
497
            default : 
498
                break;
499
        }
500
        return event;
501
    }
502
    /**
503
     * {@inheritDoc}
504
     */
505
    protected Object getActiveWindow() {
506
        Shell activeWindow = (Shell)getRobotFactory().getEventThreadQueuer()
507
        .invokeAndWait(this.getClass().getName() + ".getActiveWindow", //$NON-NLS-1$
508
            
509
            new IRunnable() {
510
                public Object run() { // SYNCH THREAD START
511
                    Display d = 
512
                        ((SwtAUTServer)AUTServer.getInstance()).getAutDisplay();
513
                    return d.getActiveShell();
514
                    
515
                }
516
            }
517
                    
518
        );
519
        
520
        return activeWindow;
521
    }
522
    
523
    /**
524
     * Returns <code>true</code> if a window with the given title is active
525
     * (the window with focus).
526
     * 
527
     * @param title the title
528
     * @param operator the matches/equals operator
529
     * @return if the window is open and visible
530
     */
531
    private boolean isWindowActive(final String title, final String operator) {
532
        final Shell activeWindow = (Shell) getActiveWindow();
533
        
534
        if (activeWindow == null) {
535
            if (log.isWarnEnabled()) {
536
                log.warn("No active Window found while searching for Window with title: '" //$NON-NLS-1$
537
                        + String.valueOf(title) + "'! " + //$NON-NLS-1$
538
                    "(SwtApplicationImplClass#isWindowActive(String, String))"); //$NON-NLS-1$
539
            }
540
            return false;
541
        }
542
        
543
        final String windowTitle = activeWindow.getText();
544
        
545
        return MatchUtil.getInstance().match(windowTitle, title, operator);
546
    }
547
548
}
(-)a/org.eclipse.jubula.toolkit.provider.swing/resources/xml/ComponentConfiguration.xml (-1 / +1 lines)
Lines 21-27 Link Here
21
			<typeFactory>org.eclipse.jubula.rc.common.implclasses.DefaultComponentFactory</typeFactory>
21
			<typeFactory>org.eclipse.jubula.rc.common.implclasses.DefaultComponentFactory</typeFactory>
22
		</defaultMapping>
22
		</defaultMapping>
23
		<realizes>guidancer.concrete.GraphicApplication</realizes>
23
		<realizes>guidancer.concrete.GraphicApplication</realizes>
24
		<testerClass>org.eclipse.jubula.rc.swing.swing.implclasses.SwingApplicationImplClass</testerClass>
24
		<testerClass>org.eclipse.jubula.rc.swing.swing.caps.SwingApplicationCAPs</testerClass>
25
		<componentClass name="com.bredexsw.guidancer.autserver.swing.implclasses.GraphicApplication" />
25
		<componentClass name="com.bredexsw.guidancer.autserver.swing.implclasses.GraphicApplication" />
26
	</toolkitComponent>
26
	</toolkitComponent>
27
	
27
	
(-)a/org.eclipse.jubula.toolkit.provider.swt/resources/xml/ComponentConfiguration.xml (-2 / +1 lines)
Lines 194-200 Link Here
194
			<typeFactory>org.eclipse.jubula.rc.common.implclasses.DefaultComponentFactory</typeFactory>
194
			<typeFactory>org.eclipse.jubula.rc.common.implclasses.DefaultComponentFactory</typeFactory>
195
		</defaultMapping>
195
		</defaultMapping>
196
		<realizes>guidancer.concrete.GraphicApplication</realizes>
196
		<realizes>guidancer.concrete.GraphicApplication</realizes>
197
		<testerClass>org.eclipse.jubula.rc.swt.implclasses.SwtApplicationImplClass</testerClass>
197
		<testerClass>org.eclipse.jubula.rc.swt.caps.SwtApplicationCAPs</testerClass>
198
		<componentClass name="com.bredexsw.guidancer.autswtserver.implclasses.GraphicApplication" />
198
		<componentClass name="com.bredexsw.guidancer.autswtserver.implclasses.GraphicApplication" />
199
	</toolkitComponent>
199
	</toolkitComponent>
200
200
201
- 

Return to bug 394179