/*
* Copyright (c) 2010-2016, Sikuli.org, sikulix.com
* Released under the MIT License.
*
*/
package org.sikuli.guide;
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.sikuli.guide.Transition.TransitionListener;
import org.sikuli.basics.Debug;
import org.sikuli.util.EventObserver;
import org.sikuli.util.EventSubject;
import org.sikuli.script.Location;
import org.sikuli.script.Pattern;
import org.sikuli.script.Region;
import org.sikuli.script.Screen;
import org.sikuli.util.OverlayTransparentWindow;
import org.sikuli.basics.Settings;
import org.sikuli.natives.SysUtil;
public class Guide extends OverlayTransparentWindow implements EventObserver {
static float DEFAULT_TIMEOUT = 10.0f;
static public final int FIRST = 0;
static public final int MIDDLE = 1;
static public final int LAST = 2;
static public final int SIMPLE = 4;
final float DIMMING_OPACITY = 0.5f;
Robot robot;
Region _region;
JPanel content = null;
Transition transition;
ArrayList<Transition> transitions = new ArrayList<Transition>();
ArrayList<Tracker> trackers = new ArrayList<Tracker>();
Transition triggeredTransition;
ClickableWindow clickableWindow = null;
SxBeam beam = null;
/**
* create a new guide overlay on whole primary screen
*/
public Guide() {
super(new Color(0.1f, 0f, 0f, 0.1f), null);
super.addObserver(this);
init(new Screen());
}
/**
* create a new guide overlay on given region
*/
public Guide(Region region) {
super(new Color(0.1f, 0f, 0f, 0.1f), null);
super.addObserver(this);
init(region);
}
private void init(Region region) {
try {
robot = new Robot();
} catch (AWTException e1) {
e1.printStackTrace();
}
content = getJPanel();
_region = region;
Rectangle rect = _region.getRect();
content.setPreferredSize(rect.getSize());
add(content);
setBounds(rect);
getRootPane().putClientProperty( "Window.shadow", Boolean.FALSE );
((JPanel)getContentPane()).setDoubleBuffered(true);
setVisible(false);
setFocusableWindowState(false);
}
public void focusBelow() {
if (Settings.isMac()) {
// TODO: replace this hack with a more robust method
// Mac's hack to bring focus to the window directly underneath
// this hack works on the assumption that the caller has
// the input focus but no interaction area at the current
// mouse cursor position
// This hack does not work well with applications that
// can receive mouse clicks without having the input focus
// (e.g., finder, system preferences)
// robot.mousePress(InputEvent.BUTTON1_MASK);
// robot.mouseRelease(InputEvent.BUTTON1_MASK);
// Another temporary hack to switch to the previous window on Mac
robot.keyPress(KeyEvent.VK_META);
robot.keyPress(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_META);
robot.keyRelease(KeyEvent.VK_TAB);
// wait a little bit for the switch to complete
robot.delay(1000);
}
}
@Override
public void toFront() {
if ( Settings.isMac() || Settings.isWindows() ) {
// this call is necessary to allow clicks to go through the window (ignoreMouse == true)
if (Settings.JavaVersion < 7) {
SysUtil.getOSUtil().bringWindowToFront(this, true);
} else {
}
}
super.toFront();
}
@Override
public void update(EventSubject es) {
//TODO transparent paint
}
public String showNow(float secs) {
transitions.add(new TimeoutTransition((int) secs * 1000));
return showNow();
}
public String showNow() {
String cmd = "Next";
if (content.getComponentCount() == 0
&& transitions.isEmpty() ) {
//&& search == null) {
return cmd;
}
// startAnimation();
startTracking();
setVisible(true);
toFront();
//<editor-fold defaultstate="collapsed" desc="deal with interactive search elements">
/* if (search != null) {
* search.setVisible(true);
* search.requestFocus();
* synchronized (this) {
* try {
* wait();
* } catch (InterruptedException e) {
* e.printStackTrace();
* }
* }
* search.dispose();
* search.setVisible(false);
* String key = search.getSelectedKey();
* search = null;
* reset();
* focusBelow();
* return key;
* }*/ //</editor-fold>
if (transitions.isEmpty()) {
// if no transition is added, use the default timeout transition
transitions.add(new TimeoutTransition((int) DEFAULT_TIMEOUT * 1000));
}
final Object token = new Object();
synchronized (token) {
for (Transition transition : transitions) {
transition.waitForTransition(new TransitionListener() {
@Override
public void transitionOccurred(Object source) {
triggeredTransition = (Transition) source;
synchronized (token) {
token.notify();
}
}
});
}
try {
token.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (triggeredTransition instanceof ClickableWindow) {
ClickableWindow cw = (ClickableWindow) triggeredTransition;
//TODO click through if not button
cmd = cw.getLastClicked().getName();
} else if (triggeredTransition instanceof TimeoutTransition) {
cmd = "timeout";
}
reset();
return cmd;
}
public void addToFront(JComponent comp) {
addComponent(comp);
}
public void addComponent(JComponent comp) {
content.add(comp, 0);
}
public void addToFront(Visual comp) {
addComponent(comp, 0);
}
public void addComponent(Visual comp, int index) {
if (comp instanceof SxClickable) {
// add to the guide window
content.add(comp, 0);
if (clickableWindow == null) {
clickableWindow = new ClickableWindow(this);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
//Debug.info("[Guide] window closed");
GlobalMouseMotionTracker.getInstance().stop();
}
});
}
clickableWindow.addClickable((SxClickable) comp);
addTransition(clickableWindow);
return;
}
content.add(comp, index);
if (comp instanceof SxSpotlight) {
setDarken(true);
}
}
public void removeComponent(Component comp) {
content.remove(comp);
}
private void reset() {
clear();
transitions.clear();
// now we dipose window so the .py script can terminate
if (clickableWindow != null) {
clickableWindow.dispose();
clickableWindow = null;
}
dispose();
}
public void clear() {
if (clickableWindow != null) {
clickableWindow.clear();
}
stopAnimation();
stopTracking();
content.removeAll();
transition = null;
beam = null;
setDarken(false);
setVisible(false);
GlobalMouseMotionTracker.getInstance().stop();
}
public void setDefaultTimeout(float timeout_in_seconds) {
DEFAULT_TIMEOUT = timeout_in_seconds;
}
public Region getRegion() {
return _region;
}
Point convertToRegionLocation(Point point_in_global_coordinate) {
Point ret = new Point(point_in_global_coordinate);
ret.translate(-_region.x, -_region.y);
return ret;
}
//<editor-fold defaultstate="collapsed" desc="TODO not used: searchDialog">
// SearchDialog search = null;
/* public void addSearchDialog() {
* search = new SearchDialog(this, "Enter the search string:");
* //search = new GUISearchDialog(this);
* search.setLocationRelativeTo(null);
* search.setAlwaysOnTop(true);
* }
*
* public void setSearchDialog(SearchDialog search) {
* this.search = search;
* }
*
* public void addSearchEntry(String key, Region region) {
* if (search == null) {
* addSearchDialog();
* }
* search.addEntry(key, region);
* }*/
//</editor-fold>
boolean hasSpotlight() {
for (Component comp : content.getComponents()) {
if (comp instanceof SxSpotlight) {
return true;
}
}
return false;
}
public void updateSpotlights(ArrayList<Region> regions) {
removeSpotlights();
if (regions.isEmpty()) {
setBackground(null);
content.setBackground(null);
} else {
// if there are spotlights added, darken the background
setBackground(new Color(0f, 0f, 0f, DIMMING_OPACITY));
content.setBackground(new Color(0f, 0f, 0f, DIMMING_OPACITY));
for (Region r : regions) {
SxSpotlight spotlight = new SxSpotlight(r);
spotlight.setShape(SxSpotlight.CIRCLE);
//addSpotlight(r,SxSpotlight.CIRCLE);
}
}
repaint();
}
public void removeSpotlights() {
for (Component co : content.getComponents()) {
if (co instanceof SxSpotlight) {
content.remove(co);
}
}
}
public void setDarken(boolean darken) {
//TODO check against transparency
if (darken) {
//setBackground(new Color(0f,0f,0f,DIMMING_OPACITY));
content.setBackground(new Color(0f, 0f, 0f, DIMMING_OPACITY));
} else {
setBackground(null);
content.setBackground(null);
}
}
public Visual addBeam(Region r) {
beam = new SxBeam(this, r);
SxAnchor anchor = new SxAnchor(r);
addTransition(beam);
return anchor;
}
//<editor-fold defaultstate="collapsed" desc="TODO not used: addMagnet">
/* public void addMagnifier(Region region) {
* Magnifier mag = new Magnifier(this, region);
* content.add(mag);
* }*/
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="TODO not used: setDialog">
public void setDialog(String message) {
TransitionDialog dialog = new TransitionDialog();
dialog.setText(message);
transition = dialog;
}
public void setDialog(TransitionDialog dialog_) {
//dialog = dialog_;
transition = dialog_;
}
//</editor-fold>
public void stopAnimation() {
for (Component co : content.getComponents()) {
/* if (co instanceof Magnifier) {
* ((Magnifier) co).start();
* }*/
if (co instanceof Visual) {
((Visual) co).stopAnimation();
}
}
}
public void startAnimation() {
for (Component co : content.getComponents()) {
/* if (co instanceof Magnifier) {
* ((Magnifier) co).start();
* }*/
if (co instanceof Visual) {
((Visual) co).startAnimation();
}
}
}
public void addTransition(Transition t) {
if (! transitions.contains(t)) {
transitions.add(t);
}
}
public Transition getTransition() {
return transition;
}
public void startTracking() {
for (Component co : content.getComponents()) {
if (co instanceof SxAnchor) {
((SxAnchor) co).startTracking();
}
}
}
public void stopTracking() {
for (Component co : content.getComponents()) {
if (co instanceof SxAnchor) {
((SxAnchor) co).stopTracking();
}
}
}
//<editor-fold defaultstate="collapsed" desc="global tracking support - not used currently">
public void addTracker(Pattern pattern, SxAnchor anchor) {
Tracker tracker = null;
// // find a tracker already assigned to this pattern
// for (Tracker t : trackers){
// if (t.isAlreadyTracking(pattern,r)){
// tracker = t;
// break;
// }
// }
// if (tracker == null){
tracker = new Tracker(this, pattern, null);
trackers.add(tracker);
// }
BufferedImage img;
try {
img = pattern.getBImage();
anchor.setActualSize(img.getWidth(), img.getHeight());
tracker.setAnchor(anchor);
} catch (Exception e) {
e.printStackTrace();
}
}
public void addTracker(Pattern pattern, Region r, Visual c) {
Tracker tracker = null;
// find a tracker already assigned to this pattern
for (Tracker t : trackers) {
if (t.isAlreadyTracking(pattern, r)) {
tracker = t;
break;
}
}
if (tracker == null) {
tracker = new Tracker(this, pattern, r);
trackers.add(tracker);
}
tracker.setAnchor(c);
}
public void addTracker(Pattern pattern, Region r, ArrayList<Visual> components) {
Tracker tracker = new Tracker(this, pattern, r);
for (Visual c : components) {
tracker.setAnchor(c);
}
trackers.add(tracker);
}
abstract class TrackerAdapter {
abstract void patternAnchored();
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="TODO not used: play steps">
/* public void playStepOnWebpage(Step step, Region leftmarker, Region rightmarker) {
*
* //Point screenshotOrigin = new Point(614,166);
*
* int originalWidth = step.getScreenImage().getWidth();
* int originalHeight = step.getScreenImage().getHeight();
*
* int displayWidth = rightmarker.x + rightmarker.w - leftmarker.x;
* float scale = 1.0f * displayWidth / originalWidth;
* Debug.info("scale:" + scale);
* int displayHeight = (int) (originalHeight * scale);
*
* int originX = leftmarker.x;
* int originY = leftmarker.y - displayHeight;
* Point screenshotOrigin = new Point(originX, originY);
*
* //scale = 1.0f;
* //Point screenshotOrigin = new Point(715,192);
* //Point screenshotOrigin = new Point(953,257);//
*
* Debug.info("Step size:" + step.getView().getSize());
* //Dimension displaySize = new Dimension(480,363);
* //float scale = 480f/640f;
*
* Screen s = new Screen();
* for (Part part : step.getParts()) {
*
* Point targetOrigin = part.getTargetOrigin();
*
* Point screenOrigin = new Point();
* screenOrigin.x = screenshotOrigin.x + (int) (targetOrigin.x * scale);
* screenOrigin.y = screenshotOrigin.y + (int) (targetOrigin.y * scale);
*
* part.setAnchorScreenLocation(screenOrigin);
* }
*
* playStep(step, scale);
* }
*
* public void playStep(Step step) {
* playStep(step, 1.0f);
* }
*
* public void play(ArrayList<SklStepModel> steps) {
* for (SklStepModel step : steps) {
* String ret = playStep(step);
* if (ret == null) {
* continue;
* }
*
* if (ret.equals("Exit")) {
* return;
* }
* }
*
* }*/
// public void playStepList(SklStepListModel stepListModel){
// for (Enumeration<?> e = stepListModel.elements() ; e.hasMoreElements() ;) {
// SklStepModel eachStepModel = (SklStepModel) e.nextElement();
// String ret = playStep(eachStepModel);
// if (ret == null)
// continue;
//
// if (ret.equals("Exit")){
// return;
// }
// }
//
// }
/* public String playStep(SklStepModel step_) {
*
* SklStepModel step = null;
* try {
* step = (SklStepModel) step_.clone();
* } catch (CloneNotSupportedException e1) {
* e1.printStackTrace();
* }
*
* for (SklModel model : step.getModels()) {
* model.setOpacity(0f);
* SklView view = SklViewFactory.createView(model);
* addToFront(view);
* }
*
*
* SxButton btn = new SxButton("Exit");
* btn.setActualLocation(10, 50);
* addToFront(btn);
*
* SxButton skip = new SxButton("Skip");
* skip.setActualLocation(90, 50);
* addToFront(skip);
*
* // do these to allow static elements to be drawn
* setVisible(true);
* toFront();
*
* step.startTracking(this);
* step.startAnimation();
*
* String ret = showNow();
* Debug.info("[Guide.playStep] ret = " + ret);
*
* return ret;
* }
*
* public void playStep(Step step, final float scale) {
*
*
* SxImage screenshot = new SxImage(step.getScreenImage());
* screenshot.setLocationRelativeToRegion(new Screen(), Layout.INSIDE);
* // screenshot.setOpacity(0);
* // screenshot.addAnimation(AnimationFactory.createOpacityAnimation(screenshot,0.1f,0.9f));
* // // TODO replace these with a Pause animation
* // screenshot.addAnimation(AnimationFactory.createOpacityAnimation(screenshot,0.9f,0.9f));
* // screenshot.addAnimation(AnimationFactory.createOpacityAnimation(screenshot,0.9f,0.9f));
* // screenshot.addAnimation(AnimationFactory.createOpacityAnimation(screenshot,1,0));
* // addToFront(screenshot);
*
* for (final Part part : step.getParts()) {
*
* Pattern pattern = part.getTargetPattern();
*
* BufferedImage patternImage = null;
* try {
* patternImage = pattern.getBImage();
* } catch (Exception e) {
* }
*
* final SxAnchor anchor = new SxAnchor(pattern);
* anchor.setEditable(true);
* anchor.setOpacity(1f);
* anchor.setAnimateAnchoring(true);
*
* Point anchorLocation = new Point(part.getTargetOrigin());
* Point screenshotLocation = screenshot.getActualLocation();
* anchorLocation.translate(screenshotLocation.x, screenshotLocation.y);
* anchor.setActualLocation(anchorLocation);
*
* SxClickable clickable = new SxClickable(null);
* clickable.setLocationRelativeToComponent(anchor, Layout.OVER);
* addToFront(clickable);
*
* // add an image to visualize the target pattern
* final SxImage sklImage = new SxImage(patternImage);
* sklImage.setLocationRelativeToComponent(anchor, Layout.OVER);
*
* anchor.addListener(new AnchorListener() {
* @Override
* public void anchored() {
* sklImage.removeFromLeader();
* sklImage.setVisible(false);
* repaint();
*
*
* for (Visual comp : anchor.getFollowers()) {
* if (comp instanceof SxText || comp instanceof SxFlag) {
* comp.popin();
* }
* }
*
* anchor.popin();
* }
*
* @Override
* public void found(SxAnchor source) {
* }
* });
*
* addToFront(sklImage);
* addToFront(anchor);
*
* Point o = part.getTargetOrigin();
* Point p = anchor.getActualLocation();
*
* for (Visual compo : part.getAnnotationComponents()) {
*
* Visual comp = (Visual) compo.clone();
*
* Point loc = comp.getActualLocation();
* loc.x = (int) ((int) (loc.x - o.x) * scale) + p.x;
* loc.y = (int) ((int) (loc.y - o.y) * scale) + p.y;
*
* comp.setActualLocation(loc);
* comp.setLocationRelativeToComponent(anchor);
* comp.setActualSize((int) (comp.getActualWidth() * scale), (int) (comp.getActualHeight() * scale));
*
* addToFront(comp);
*
* comp.popout();
*
* }
*
* anchor.popout();
*
* }
*
* SxButton btn = new SxButton("Exit");
* btn.setActualLocation(50, 50);
* addToFront(btn);
*
* showNow();
*
* }
*
* public void playSteps(ArrayList<Step> steps) throws FindFailed {
*
* Screen s = new Screen();
*
* for (Step step : steps) {
* SxButton btn = new SxButton("Next");
* btn.setLocation(s.getTopRight().left(200).below(50));
*
*
* addToFront(btn);
*
* step.setTransition(getTransition());
*
* playStep(step, 1.0f);
* }
*
* }*///</editor-fold>
/**
* create a rectangle in this guide plane and add to front
* @return the rectangle
*/
public Visual rectangle() {
Visual gc = new SxRectangle();
gc.setGuide(this);
addToFront(gc);
return gc;
}
public Visual circle() {
Visual gc = new SxCircle();
gc.setGuide(this);
addToFront(gc);
return gc;
}
public Visual text(String text) {
Visual gc = new SxText(text);
gc.setGuide(this);
addToFront(gc);
return gc;
}
public Visual flag(String text) {
Visual gc = new SxFlag(text);
gc.setGuide(this);
addToFront(gc);
return gc;
}
public Visual callout(String text) {
Visual gc = new SxCallout(text);
gc.setGuide(this);
addToFront(gc);
return gc;
}
public Visual image(Object img) {
Visual gc = null;
if (img instanceof String) {
gc = new SxImage((String) img);
} else if (img instanceof BufferedImage) {
gc = new SxImage((BufferedImage) img);
}
if (gc != null) {
gc.setGuide(this);
addToFront(gc);
} else {
Debug.log(2, "Guide.image: invalid argument");
}
return gc;
}
public Visual bracket() {
Visual gc = new SxBracket();
gc.setGuide(this);
addToFront(gc);
return gc;
}
public Visual arrow(Object from, Object to) {
Visual gc = null;
if (from instanceof Region) {
gc = new SxArrow(((Region) from).getCenter().getPoint(), ((Region) to).getCenter().getPoint());
} else if (from instanceof Point || from instanceof Location) {
gc = new SxArrow((Point) from, (Point) to);
} else if (from instanceof Visual) {
gc = new SxArrow((Visual) from, (Visual) to);
}
if (gc != null) {
gc.setGuide(this);
addToFront(gc);
} else {
Debug.log(2, "Guide.arrow: invalid arguments");
}
return gc;
}
public Visual button(String name) {
Visual gc = new SxButton(name);
gc.setGuide(this);
addToFront(gc);
return gc;
}
}