123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644 |
- # -*- coding: utf-8 -*-
- '''
- Copyright (C) 2012-2015 Diego Torres Milano
- Created on oct 6, 2014
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- @author: Diego Torres Milano
- '''
- __version__ = '10.3.0'
- import sys
- import threading
- import warnings
- import copy
- import string
- import os
- import platform
- from __builtin__ import False
- from pkg_resources import Requirement, resource_filename
- try:
- from PIL import Image, ImageTk
- PIL_AVAILABLE = True
- except:
- PIL_AVAILABLE = False
- try:
- import Tkinter
- import tkSimpleDialog
- import tkFileDialog
- import tkFont
- import ScrolledText
- import ttk
- from Tkconstants import DISABLED, NORMAL
- TKINTER_AVAILABLE = True
- except:
- TKINTER_AVAILABLE = False
- from ast import literal_eval as make_tuple
- DEBUG = False
- DEBUG_MOVE = DEBUG and False
- DEBUG_TOUCH = DEBUG and False
- DEBUG_POINT = DEBUG and False
- DEBUG_KEY = DEBUG and False
- DEBUG_ISCCOF = DEBUG and False
- DEBUG_FIND_VIEW = DEBUG and False
- DEBUG_CONTEXT_MENU = DEBUG and False
- class Color:
- GOLD = '#d19615'
- GREEN = '#15d137'
- BLUE = '#1551d1'
- MAGENTA = '#d115af'
- DARK_GRAY = '#222222'
- LIGHT_GRAY = '#dddddd'
- class Unit:
- PX = 'PX'
- DIP = 'DIP'
- class Operation:
- ASSIGN = 'assign'
- CHANGE_LANGUAGE = 'change_language'
- DEFAULT = 'default'
- DRAG = 'drag'
- DUMP = 'dump'
- FLING_BACKWARD = 'fling_backward'
- FLING_FORWARD = 'fling_forward'
- FLING_TO_BEGINNING = 'fling_to_beginning'
- FLING_TO_END = 'fling_to_end'
- TEST = 'test'
- TEST_TEXT = 'test_text'
- TOUCH_VIEW = 'touch_view'
- TOUCH_POINT = 'touch_point'
- LONG_TOUCH_POINT = 'long_touch_point'
- OPEN_NOTIFICATION = 'open_notification'
- OPEN_QUICK_SETTINGS = 'open_quick_settings'
- TYPE = 'type'
- PRESS = 'press'
- SNAPSHOT = 'snapshot'
- SLEEP = 'sleep'
- TRAVERSE = 'traverse'
- VIEW_SNAPSHOT = 'view_snapshot'
-
- @staticmethod
- def fromCommandName(commandName):
- MAP = {'flingBackward': Operation.FLING_BACKWARD, 'flingForward': Operation.FLING_FORWARD,
- 'flingToBeginning': Operation.FLING_TO_BEGINNING, 'flingToEnd': Operation.FLING_TO_END,
- 'openNotification': Operation.OPEN_NOTIFICATION, 'openQuickSettings': Operation.OPEN_QUICK_SETTINGS,
- }
- return MAP[commandName]
- class Culebron:
- APPLICATION_NAME = "Culebra"
- UPPERCASE_CHARS = string.uppercase[:26]
-
- KEYSYM_TO_KEYCODE_MAP = {
- 'Home': 'HOME',
- 'BackSpace': 'BACK',
- 'Left': 'DPAD_LEFT',
- 'Right': 'DPAD_RIGHT',
- 'Up': 'DPAD_UP',
- 'Down': 'DPAD_DOWN',
- }
-
- KEYSYM_CULEBRON_COMMANDS = {
- 'F1': None,
- 'F5': None
- }
- canvas = None
- imageId = None
- vignetteId = None
- areTargetsMarked = False
- isDragDialogShowed = False
- isGrabbingTouch = False
- isGeneratingTestCondition = False
- isTouchingPoint = False
- isLongTouchingPoint = False
- onTouchListener = None
- snapshotDir = '/tmp'
- snapshotFormat = 'PNG'
- deviceArt = None
- dropShadow = False
- screenGlare = False
-
- @staticmethod
- def checkSupportedSdkVersion(sdkVersion):
- if sdkVersion <= 10:
- raise Exception('''culebra GUI requires Android API > 10 to work''')
- @staticmethod
- def checkDependencies():
- if not PIL_AVAILABLE:
- raise Exception('''PIL or Pillow is needed for GUI mode
- On Ubuntu install
- $ sudo apt-get install python-imaging python-imaging-tk
- On OSX install
- $ brew install homebrew/python/pillow
- ''')
- if not TKINTER_AVAILABLE:
- raise Exception('''Tkinter is needed for GUI mode
- This is usually installed by python package. Check your distribution details.
- ''')
- def __init__(self, vc, printOperation, scale=1):
- '''
- Culebron constructor.
-
- @param vc: The ViewClient used by this Culebron instance
- @type vc: ViewClient
- @param printOperation: the method invoked to print operations to the script
- @type printOperation: method
- @param scale: the scale of the device screen used to show it on the window
- @type scale: float
- '''
-
- self.vc = vc
- self.printOperation = printOperation
- self.device = vc.device
- self.serialno = vc.serialno
- self.scale = scale
- self.window = Tkinter.Tk()
- icon = resource_filename(Requirement.parse("androidviewclient"),
- "share/pixmaps/culebra.png")
- self.window.tk.call('wm', 'iconphoto', self.window._w,
- ImageTk.PhotoImage(file=icon))
- self.mainMenu = MainMenu(self)
- self.window.config(menu=self.mainMenu)
- self.mainFrame = Tkinter.Frame(self.window)
- self.placeholder = Tkinter.Frame(self.mainFrame, width=400, height=400, background=Color.LIGHT_GRAY)
- self.placeholder.grid(row=1, column=1, rowspan=4)
- self.sideFrame = Tkinter.Frame(self.window)
- self.viewTree = ViewTree(self.sideFrame)
- self.viewDetails = ViewDetails(self.sideFrame)
- self.mainFrame.grid(row=1, column=1, columnspan=1, rowspan=4, sticky=Tkinter.N+Tkinter.S)
- self.isSideFrameShown = False
- self.isViewTreeShown = False
- self.isViewDetailsShown = False
- self.statusBar = StatusBar(self.window)
- self.statusBar.grid(row=5, column=1, columnspan=2)
- self.statusBar.set("Always press F1 for help")
- self.window.update_idletasks()
- self.targetIds = []
- if DEBUG:
- self.printGridInfo()
- def printGridInfo(self):
- print >> sys.stderr, "window:", repr(self.window)
- print >> sys.stderr, "main:", repr(self.mainFrame)
- print >> sys.stderr, "main:", self.mainFrame.grid_info()
- print >> sys.stderr, "side:", repr(self.sideFrame)
- print >> sys.stderr, "side:", self.sideFrame.grid_info()
- print >> sys.stderr, "tree:", repr(self.viewTree)
- print >> sys.stderr, "tree:", self.viewTree.grid_info()
- print >> sys.stderr, "details:", repr(self.viewDetails)
- print >> sys.stderr, "details:", self.viewDetails.grid_info()
- def takeScreenshotAndShowItOnWindow(self):
- '''
- Takes the current screenshot and shows it on the main window.
- It also:
- - sizes the window
- - create the canvas
- - set the focus
- - enable the events
- - create widgets
- - finds the targets (as explained in L{findTargets})
- - hides the vignette (that could have been showed before)
- '''
-
- if DEBUG:
- print >> sys.stderr, "takeScreenshotAndShowItOnWindow()"
- self.unscaledScreenshot = self.device.takeSnapshot(reconnect=True)
- self.image = self.unscaledScreenshot
- (width, height) = self.image.size
- if self.scale != 1:
- self.image = self.image.resize((int(width*self.scale), int(height*self.scale)), Image.ANTIALIAS)
- (width, height) = self.image.size
- if self.canvas is None:
- if DEBUG:
- print >> sys.stderr, "Creating canvas", width, 'x', height
- self.placeholder.grid_forget()
- self.canvas = Tkinter.Canvas(self.mainFrame, width=width, height=height)
- self.canvas.focus_set()
- self.enableEvents()
- self.createMessageArea(width, height)
- self.createVignette(width, height)
- self.screenshot = ImageTk.PhotoImage(self.image)
- if self.imageId is not None:
- self.canvas.delete(self.imageId)
- self.imageId = self.canvas.create_image(0, 0, anchor=Tkinter.NW, image=self.screenshot)
- if DEBUG:
- try:
- print >> sys.stderr, "Grid info", self.canvas.grid_info()
- except:
- print >> sys.stderr, "Exception getting grid info"
- gridInfo = None
- try:
- gridInfo = self.canvas.grid_info()
- except:
- if DEBUG:
- print >> sys.stderr, "Adding canvas to grid (1,1)"
- self.canvas.grid(row=1, column=1, rowspan=4)
- if not gridInfo:
- self.canvas.grid(row=1, column=1, rowspan=4)
- self.findTargets()
- self.hideVignette()
- if DEBUG:
- self.printGridInfo()
- def createMessageArea(self, width, height):
- self.__message = Tkinter.Label(self.window, text='', background=Color.GOLD, font=('Helvetica', 16), anchor=Tkinter.W)
- self.__message.configure(width=width)
- self.__messageAreaId = self.canvas.create_window(0, 0, anchor=Tkinter.NW, window=self.__message)
- self.canvas.itemconfig(self.__messageAreaId, state='hidden')
- self.isMessageAreaVisible = False
-
- def showMessageArea(self):
- if self.__messageAreaId:
- self.canvas.itemconfig(self.__messageAreaId, state='normal')
- self.isMessageAreaVisible = True
- self.canvas.update_idletasks()
-
- def hideMessageArea(self):
- if self.__messageAreaId and self.isMessageAreaVisible:
- self.canvas.itemconfig(self.__messageAreaId, state='hidden')
- self.isMessageAreaVisible = False
- self.canvas.update_idletasks()
- def toggleMessageArea(self):
- if self.isMessageAreaVisible:
- self.hideMessageArea()
- else:
- self.showMessageArea()
- def message(self, text, background=None):
- self.__message.config(text=text)
- if background:
- self.__message.config(background=background)
- self.showMessageArea()
- def toast(self, text, background=None, timeout=5):
- if DEBUG:
- print >> sys.stderr, "toast(", text, ",", background, ")"
- self.message(text, background)
- if text:
- t = threading.Timer(timeout, self.hideMessageArea)
- t.start()
- else:
- self.hideMessageArea()
- def createVignette(self, width, height):
- if DEBUG:
- print >> sys.stderr, "createVignette(%d, %d)" % (width, height)
- self.vignetteId = self.canvas.create_rectangle(0, 0, width, height, fill=Color.MAGENTA,
- stipple='gray50')
- font = tkFont.Font(family='Helvetica',size=int(144*self.scale))
- msg = "Please\nwait..."
- self.waitMessageShadowId = self.canvas.create_text(width/2+2, height/2+2, text=msg,
- fill=Color.DARK_GRAY, font=font)
- self.waitMessageId = self.canvas.create_text(width/2, height/2, text=msg,
- fill=Color.LIGHT_GRAY, font=font)
- self.canvas.update_idletasks()
-
- def showVignette(self):
- if DEBUG:
- print >> sys.stderr, "showVignette()"
- if self.canvas is None:
- return
- if self.vignetteId:
- if DEBUG:
- print >> sys.stderr, " showing vignette"
- # disable events while we are processing one
- self.disableEvents()
- self.canvas.lift(self.vignetteId)
- self.canvas.lift(self.waitMessageShadowId)
- self.canvas.lift(self.waitMessageId)
- self.canvas.update_idletasks()
-
- def hideVignette(self):
- if DEBUG:
- print >> sys.stderr, "hideVignette()"
- if self.canvas is None:
- return
- if self.vignetteId:
- if DEBUG:
- print >> sys.stderr, " hiding vignette"
- self.canvas.lift(self.imageId)
- self.canvas.update_idletasks()
- self.enableEvents()
- def deleteVignette(self):
- if self.canvas is not None:
- self.canvas.delete(self.vignetteId)
- self.vignetteId = None
- self.canvas.delete(self.waitMessageShadowId)
- self.waitMessageShadowId = None
- self.canvas.delete(self.waitMessageId)
- self.waitMessageId = None
- def showPopupMenu(self, event):
- (scaledX, scaledY) = (event.x/self.scale, event.y/self.scale)
- v = self.findViewContainingPointInTargets(scaledX, scaledY)
- ContextMenu(self, view=v).showPopupMenu(event)
- def showHelp(self):
- d = HelpDialog(self)
- self.window.wait_window(d)
- def showSideFrame(self):
- if not self.isSideFrameShown:
- self.sideFrame.grid(row=1, column=2, rowspan=4, sticky=Tkinter.N+Tkinter.S)
- self.isSideFrameSown = True
- if DEBUG:
- self.printGridInfo()
- def hideSideFrame(self):
- self.sideFrame.grid_forget()
- self.isSideFrameShown = False
- if DEBUG:
- self.printGridInfo()
- def showViewTree(self):
- self.showSideFrame()
- self.viewTree.grid(row=1, column=1, rowspan=3, sticky=Tkinter.N+Tkinter.S)
- self.isViewTreeShown = True
- if DEBUG:
- self.printGridInfo()
- def hideViewTree(self):
- self.unmarkTargets()
- self.viewTree.grid_forget()
- self.isViewTreeShown = False
- if not self.isViewDetailsShown:
- self.hideSideFrame()
- if DEBUG:
- self.printGridInfo()
- def showViewDetails(self):
- self.showSideFrame()
- row = 4
- #if self.viewTree.grid_info() != {}:
- # row += 1
- self.viewDetails.grid(row=row, column=1, rowspan=1, sticky=Tkinter.S)
- self.isViewDetailsShown = True
- if DEBUG:
- self.printGridInfo()
-
- def hideViewDetails(self):
- self.viewDetails.grid_forget()
- self.isViewDetailsShown = False
- if not self.isViewTreeShown:
- self.hideSideFrame()
- if DEBUG:
- self.printGridInfo()
- def viewTreeItemClicked(self, event):
- if DEBUG:
- print >> sys.stderr, "viewTreeitemClicked:", event.__dict__
- self.unmarkTargets()
- vuid = self.viewTree.viewTree.identify_row(event.y)
- if vuid:
- view = self.vc.viewsById[vuid]
- if view:
- coords = view.getCoords()
- if view.isTarget():
- self.markTarget(coords[0][0], coords[0][1], coords[1][0], coords[1][1])
- self.viewDetails.set(view)
- def populateViewTree(self, view):
- '''
- Populates the View tree.
- '''
- vuid = view.getUniqueId()
- text = view.__smallStr__()
- if view.getParent() is None:
- self.viewTree.insert('', Tkinter.END, vuid, text=text)
- else:
- self.viewTree.insert(view.getParent().getUniqueId(), Tkinter.END, vuid, text=text, tags=('ttk'))
- self.viewTree.set(vuid, 'T', '*' if view.isTarget() else ' ')
- self.viewTree.tag_bind('ttk', '<1>', self.viewTreeItemClicked)
- def findTargets(self):
- '''
- Finds the target Views (i.e. for touches).
- '''
-
- if DEBUG:
- print >> sys.stderr, "findTargets()"
- LISTVIEW_CLASS = 'android.widget.ListView'
- ''' The ListView class name '''
- self.targets = []
- ''' The list of target coordinates (x1, y1, x2, y2) '''
- self.targetViews = []
- ''' The list of target Views '''
- if self.device.isKeyboardShown():
- print >> sys.stderr, "#### keyboard is show but handling it is not implemented yet ####"
- # FIXME: still no windows in uiautomator
- window = -1
- else:
- window = -1
- dump = self.vc.dump(window=window)
- self.printOperation(None, Operation.DUMP, window, dump)
- # the root element cannot be deleted from Treeview once added.
- # We have no option but to recreate it
- self.viewTree = ViewTree(self.sideFrame)
- for v in dump:
- if DEBUG:
- print >> sys.stderr, " findTargets: analyzing", v.getClass(), v.getId()
- if v.getClass() == LISTVIEW_CLASS:
- # We may want to touch ListView elements, not just the ListView
- continue
- parent = v.getParent()
- if (parent and parent.getClass() == LISTVIEW_CLASS and self.isClickableCheckableOrFocusable(parent)) \
- or self.isClickableCheckableOrFocusable(v):
- # If this is a touchable ListView, let's add its children instead
- # or add it if it's touchable, focusable, whatever
- ((x1, y1), (x2, y2)) = v.getCoords()
- if DEBUG:
- print >> sys.stderr, "appending target", ((x1, y1, x2, y2))
- v.setTarget(True)
- self.targets.append((x1, y1, x2, y2))
- self.targetViews.append(v)
- target = True
- else:
- target = False
- self.vc.traverse(transform=self.populateViewTree)
-
- def getViewContainingPointAndGenerateTestCondition(self, x, y):
- if DEBUG:
- print >> sys.stderr, 'getViewContainingPointAndGenerateTestCondition(%d, %d)' % (x, y)
- self.finishGeneratingTestCondition()
- vlist = self.vc.findViewsContainingPoint((x, y))
- vlist.reverse()
- for v in vlist:
- text = v.getText()
- if text:
- self.toast(u'Asserting view with text=%s' % text, timeout=2)
- # FIXME: only getText() is invoked by the generated assert(), a parameter
- # should be used to provide different alternatives to printOperation()
- self.printOperation(v, Operation.TEST, text)
- break
-
- def findViewContainingPointInTargets(self, x, y):
- vlist = self.vc.findViewsContainingPoint((x, y))
- if DEBUG_FIND_VIEW:
- print >> sys.stderr, "Views found:"
- for v in vlist:
- print >> sys.stderr, " ", v.__smallStr__()
- vlist.reverse()
- for v in vlist:
- if DEBUG:
- print >> sys.stderr, "checking if", v, "is in", self.targetViews
- if v in self.targetViews:
- if DEBUG_TOUCH:
- print >> sys.stderr
- print >> sys.stderr, "I guess you are trying to touch:", v
- print >> sys.stderr
- return v
-
- return None
- def getViewContainingPointAndTouch(self, x, y):
- if DEBUG:
- print >> sys.stderr, 'getViewContainingPointAndTouch(%d, %d)' % (x, y)
- if self.areEventsDisabled:
- if DEBUG:
- print >> sys.stderr, "Ignoring event"
- self.canvas.update_idletasks()
- return
-
- self.showVignette()
- if DEBUG_POINT:
- print >> sys.stderr, "getViewsContainingPointAndTouch(x=%s, y=%s)" % (x, y)
- print >> sys.stderr, "self.vc=", self.vc
- v = self.findViewContainingPointInTargets(x, y)
- if v is None:
- # FIXME: We can touch by DIP by default if no Views were found
- self.hideVignette()
- msg = "There are no touchable or clickable views here!"
- self.toast(msg)
- return
- clazz = v.getClass()
- if clazz == 'android.widget.EditText':
- title = "EditText"
- kwargs = {}
- if DEBUG:
- print >>sys.stderr, v
- if v.isPassword():
- title = "Password"
- kwargs = {'show': '*'}
- text = tkSimpleDialog.askstring(title, "Enter text to type into this field", **kwargs)
- self.canvas.focus_set()
- if text:
- v.type(text)
- self.printOperation(v, Operation.TYPE, text)
- else:
- self.hideVignette()
- return
- else:
- candidates = [v]
- def findBestCandidate(view):
- isccf = Culebron.isClickableCheckableOrFocusable(view)
- cd = view.getContentDescription()
- text = view.getText()
- if (cd or text) and not isccf:
- # because isccf==False this view was not added to the list of targets
- # (i.e. Settings)
- candidates.insert(0, view)
- return None
- if not (v.getText() or v.getContentDescription()) and v.getChildren():
- self.vc.traverse(root=v, transform=findBestCandidate, stream=None)
- if len(candidates) > 2:
- warnings.warn("We are in trouble, we have more than one candidate to touch", stacklevel=0)
- candidate = candidates[0]
- candidate.touch()
- # we pass root=v as an argument so the corresponding findView*() searches in this
- # subtree instead of the full tree
- self.printOperation(candidate, Operation.TOUCH_VIEW, v if candidate != v else None)
- self.printOperation(None, Operation.SLEEP, Operation.DEFAULT)
- self.vc.sleep(5)
- self.takeScreenshotAndShowItOnWindow()
- def touchPoint(self, x, y):
- '''
- Touches a point in the device screen.
- The generated operation will use the units specified in L{coordinatesUnit} and the
- orientation in L{vc.display['orientation']}.
- '''
-
- if DEBUG:
- print >> sys.stderr, 'touchPoint(%d, %d)' % (x, y)
- print >> sys.stderr, 'touchPoint:', type(x), type(y)
- if self.areEventsDisabled:
- if DEBUG:
- print >> sys.stderr, "Ignoring event"
- self.canvas.update_idletasks()
- return
- if DEBUG:
- print >> sys.stderr, "Is touching point:", self.isTouchingPoint
- if self.isTouchingPoint:
- self.showVignette()
- self.device.touch(x, y)
- if self.coordinatesUnit == Unit.DIP:
- x = round(x / self.vc.display['density'], 2)
- y = round(y / self.vc.display['density'], 2)
- self.printOperation(None, Operation.TOUCH_POINT, x, y, self.coordinatesUnit, self.vc.display['orientation'])
- self.printOperation(None, Operation.SLEEP, Operation.DEFAULT)
- self.vc.sleep(5)
- self.isTouchingPoint = False
- self.takeScreenshotAndShowItOnWindow()
- self.hideVignette()
- self.statusBar.clear()
- return
-
- def longTouchPoint(self, x, y):
- '''
- Long-touches a point in the device screen.
- The generated operation will use the units specified in L{coordinatesUnit} and the
- orientation in L{vc.display['orientation']}.
- '''
- if DEBUG:
- print >> sys.stderr, 'longTouchPoint(%d, %d)' % (x, y)
- if self.areEventsDisabled:
- if DEBUG:
- print >> sys.stderr, "Ignoring event"
- self.canvas.update_idletasks()
- return
- if DEBUG:
- print >> sys.stderr, "Is long touching point:", self.isLongTouchingPoint
- if self.isLongTouchingPoint:
- self.showVignette()
- self.device.longTouch(x, y)
- if self.coordinatesUnit == Unit.DIP:
- x = round(x / self.vc.display['density'], 2)
- y = round(y / self.vc.display['density'], 2)
- self.printOperation(None, Operation.LONG_TOUCH_POINT, x, y, 2000, self.coordinatesUnit, self.vc.display['orientation'])
- self.printOperation(None, Operation.SLEEP, 5)
- self.vc.sleep(5)
- self.isLongTouchingPoint = False
- self.takeScreenshotAndShowItOnWindow()
- self.hideVignette()
- self.statusBar.clear()
- return
-
- def onButton1Pressed(self, event):
- if DEBUG:
- print >> sys.stderr, "onButton1Pressed((", event.x, ", ", event.y, "))"
- (scaledX, scaledY) = (event.x/self.scale, event.y/self.scale)
- if DEBUG:
- print >> sys.stderr, " onButton1Pressed: scaled: (", scaledX, ", ", scaledY, ")"
- print >> sys.stderr, " onButton1Pressed: is grabbing:", self.isGrabbingTouch
- if self.isGrabbingTouch:
- self.onTouchListener((scaledX, scaledY))
- self.isGrabbingTouch = False
- elif self.isDragDialogShowed:
- self.toast("No touch events allowed while setting drag parameters", background=Color.GOLD)
- return
- elif self.isTouchingPoint:
- self.touchPoint(scaledX, scaledY)
- elif self.isLongTouchingPoint:
- self.longTouchPoint(scaledX, scaledY)
- elif self.isGeneratingTestCondition:
- self.getViewContainingPointAndGenerateTestCondition(scaledX, scaledY)
- else:
- self.getViewContainingPointAndTouch(scaledX, scaledY)
-
- def onCtrlButton1Pressed(self, event):
- if DEBUG:
- print >> sys.stderr, "onCtrlButton1Pressed((", event.x, ", ", event.y, "))"
- (scaledX, scaledY) = (event.x/self.scale, event.y/self.scale)
- l = self.vc.findViewsContainingPoint((scaledX, scaledY))
- if l and len(l) > 0:
- self.saveViewSnapshot(l[-1])
- else:
- msg = "There are no views here!"
- self.toast(msg)
- return
-
- def onButton2Pressed(self, event):
- if DEBUG:
- print >> sys.stderr, "onButton2Pressed((", event.x, ", ", event.y, "))"
- osName = platform.system()
- if osName == 'Darwin':
- self.showPopupMenu(event)
- def onButton3Pressed(self, event):
- if DEBUG:
- print >> sys.stderr, "onButton3Pressed((", event.x, ", ", event.y, "))"
- self.showPopupMenu(event)
-
- def command(self, keycode):
- '''
- Presses a key.
- Generates the actual key press on the device and prints the line in the script.
- '''
-
- self.device.press(keycode)
- self.printOperation(None, Operation.PRESS, keycode)
-
- def onKeyPressed(self, event):
- if DEBUG_KEY:
- print >> sys.stderr, "onKeyPressed(", repr(event), ")"
- print >> sys.stderr, " event", type(event.char), len(event.char), repr(event.char), event.keysym, event.keycode, event.type
- print >> sys.stderr, " events disabled:", self.areEventsDisabled
- if self.areEventsDisabled:
- if DEBUG_KEY:
- print >> sys.stderr, "ignoring event"
- self.canvas.update_idletasks()
- return
- char = event.char
- keysym = event.keysym
- if len(char) == 0 and not (keysym in Culebron.KEYSYM_TO_KEYCODE_MAP or keysym in Culebron.KEYSYM_CULEBRON_COMMANDS):
- if DEBUG_KEY:
- print >> sys.stderr, "returning because len(char) == 0"
- return
-
- ###
- ### internal commands: no output to generated script
- ###
- try:
- handler = getattr(self, 'onCtrl%s' % self.UPPERCASE_CHARS[ord(char)-1])
- except:
- handler = None
- if handler:
- return handler(event)
- elif keysym == 'F1':
- self.showHelp()
- return
- elif keysym == 'F5':
- self.refresh()
- return
- elif keysym == 'F8':
- self.printGridInfo()
- return
- elif keysym == 'Alt_L':
- return
- elif keysym == 'Control_L':
- return
- elif keysym == 'Escape':
- # we cannot send Escape to the device, but I think it's fine
- self.cancelOperation()
- return
- ### empty char (modifier) ###
- # here does not process events like Home where char is ''
- #if char == '':
- # return
- ###
- ### target actions
- ###
- self.showVignette()
- if keysym in Culebron.KEYSYM_TO_KEYCODE_MAP:
- if DEBUG_KEY:
- print >> sys.stderr, "Pressing", Culebron.KEYSYM_TO_KEYCODE_MAP[keysym]
- self.command(Culebron.KEYSYM_TO_KEYCODE_MAP[keysym])
- elif char == '\r':
- self.command('ENTER')
- elif char == '':
- # do nothing
- pass
- else:
- self.command(char.decode('ascii', errors='replace'))
- self.vc.sleep(1)
- self.takeScreenshotAndShowItOnWindow()
-
- def refresh(self):
- self.showVignette()
- self.device.wake()
- display = copy.copy(self.device.display)
- self.device.initDisplayProperties()
- changed = False
- for prop in display:
- if display[prop] != self.device.display[prop]:
- changed = True
- break
- if changed:
- self.window.geometry('%dx%d' % (self.device.display['width']*self.scale, self.device.display['height']*self.scale+int(self.statusBar.winfo_height())))
- self.deleteVignette()
- self.canvas.destroy()
- self.canvas = None
- self.window.update_idletasks()
- self.takeScreenshotAndShowItOnWindow()
-
- def cancelOperation(self):
- '''
- Cancels the ongoing operation if any.
- '''
- if self.isLongTouchingPoint:
- self.toggleLongTouchPoint()
- elif self.isTouchingPoint:
- self.toggleTouchPoint()
- elif self.isGeneratingTestCondition:
- self.toggleGenerateTestCondition()
-
- def onCtrlA(self, event):
- if DEBUG:
- self.toggleMessageArea()
- def showDragDialog(self):
- d = DragDialog(self)
- self.window.wait_window(d)
- self.setDragDialogShowed(False)
- def onCtrlD(self, event):
- self.showDragDialog()
- def onCtrlF(self, event):
- self.saveSnapshot()
-
- def saveSnapshot(self):
- '''
- Saves the current shanpshot to the specified file.
- Current snapshot is the image being displayed on the main window.
- '''
-
- filename = self.snapshotDir + os.sep + '${serialno}-${focusedwindowname}-${timestamp}' + '.' + self.snapshotFormat.lower()
- # We have the snapshot already taken, no need to retake
- d = FileDialog(self, self.device.substituteDeviceTemplate(filename))
- saveAsFilename = d.askSaveAsFilename()
- if saveAsFilename:
- _format = os.path.splitext(saveAsFilename)[1][1:].upper()
- self.printOperation(None, Operation.SNAPSHOT, filename, _format, self.deviceArt, self.dropShadow, self.screenGlare)
- #FIXME: we should add deviceArt, dropShadow and screenGlare to the saved image
- #self.unscaledScreenshot.save(saveAsFilename, _format, self.deviceArt, self.dropShadow, self.screenGlare)
- self.unscaledScreenshot.save(saveAsFilename, _format)
- def saveViewSnapshot(self, view):
- '''
- Saves the View snapshot.
- '''
-
- if not view:
- raise ValueError("view must be provided to take snapshot")
- filename = self.snapshotDir + os.sep + '${serialno}-' + view.variableNameFromId() + '-${timestamp}' + '.' + self.snapshotFormat.lower()
- d = FileDialog(self, self.device.substituteDeviceTemplate(filename))
- saveAsFilename = d.askSaveAsFilename()
- if saveAsFilename:
- _format = os.path.splitext(saveAsFilename)[1][1:].upper()
- self.printOperation(view, Operation.VIEW_SNAPSHOT, filename, _format)
- view.writeImageToFile(saveAsFilename, _format)
-
- def toggleTouchPointDip(self):
- '''
- Toggles the touch point operation using L{Unit.DIP}.
- This invokes L{toggleTouchPoint}.
- '''
- self.coordinatesUnit = Unit.DIP
- self.toggleTouchPoint()
- def onCtrlI(self, event):
- self.toggleTouchPointDip()
-
- def toggleLongTouchPoint(self):
- '''
- Toggles the long touch point operation.
- '''
- if not self.isLongTouchingPoint:
- msg = 'Long touching point'
- self.toast(msg, background=Color.GREEN)
- self.statusBar.set(msg)
- self.isLongTouchingPoint = True
- # FIXME: There should be 2 methods DIP & PX
- self.coordinatesUnit = Unit.PX
- else:
- self.toast(None)
- self.statusBar.clear()
- self.isLongTouchingPoint = False
- def onCtrlL(self, event):
- self.toggleLongTouchPoint()
- def toggleTouchPoint(self):
- '''
- Toggles the touch point operation using the units specified in L{coordinatesUnit}.
- '''
-
- if not self.isTouchingPoint:
- msg = 'Touching point (units=%s)' % self.coordinatesUnit
- self.toast(msg, background=Color.GREEN)
- self.statusBar.set(msg)
- self.isTouchingPoint = True
- else:
- self.toast(None)
- self.statusBar.clear()
- self.isTouchingPoint = False
- def toggleTouchPointPx(self):
- self.coordinatesUnit = Unit.PX
- self.toggleTouchPoint()
- def onCtrlP(self, event):
- self.toggleTouchPointPx()
-
- def onCtrlQ(self, event):
- if DEBUG:
- print >> sys.stderr, "onCtrlQ(%s)" % event
- self.quit()
-
- def quit(self):
- self.window.destroy()
-
- def showSleepDialog(self):
- seconds = tkSimpleDialog.askfloat('Sleep Interval', 'Value in seconds:', initialvalue=1, minvalue=0, parent=self.window)
- if seconds is not None:
- self.printOperation(None, Operation.SLEEP, seconds)
- self.canvas.focus_set()
- def onCtrlS(self, event):
- self.showSleepDialog()
-
- def startGeneratingTestCondition(self):
- self.message('Generating test condition...', background=Color.GREEN)
- self.isGeneratingTestCondition = True
- def finishGeneratingTestCondition(self):
- self.isGeneratingTestCondition = False
- self.hideMessageArea()
- def toggleGenerateTestCondition(self):
- '''
- Toggles generating test condition
- '''
-
- if self.isGeneratingTestCondition:
- self.finishGeneratingTestCondition()
- else:
- self.startGeneratingTestCondition()
- def onCtrlT(self, event):
- if DEBUG:
- print >>sys.stderr, "onCtrlT()"
- # FIXME: This is only valid if we are generating a test case
- self.toggleGenerateTestCondition()
-
- def onCtrlU(self, event):
- if DEBUG:
- print >>sys.stderr, "onCtrlU()"
-
- def onCtrlV(self, event):
- if DEBUG:
- print >>sys.stderr, "onCtrlV()"
- self.printOperation(None, Operation.TRAVERSE)
-
- def toggleTargetZones(self):
- self.toggleTargets()
- self.canvas.update_idletasks()
- def onCtrlZ(self, event):
- if DEBUG:
- print >> sys.stderr, "onCtrlZ()"
- self.toggleTargetZones()
- def showControlPanel(self):
- from com.dtmilano.android.controlpanel import ControlPanel
- self.controlPanel = ControlPanel(self, self.vc, self.printOperation)
- def onCtrlK(self, event):
- self.showControlPanel()
-
- def drag(self, start, end, duration, steps, units=Unit.DIP):
- self.showVignette()
- # the operation on this device is always done in PX
- self.device.drag(start, end, duration, steps)
- if units == Unit.DIP:
- x0 = round(start[0] / self.vc.display['density'], 2)
- y0 = round(start[1] / self.vc.display['density'], 2)
- x1 = round(end[0] / self.vc.display['density'], 2)
- y1 = round(end[1] / self.vc.display['density'], 2)
- start = (x0, y0)
- end = (x1, y1)
- self.printOperation(None, Operation.DRAG, start, end, duration, steps, units, self.vc.display['orientation'])
- self.printOperation(None, Operation.SLEEP, 1)
- self.vc.sleep(1)
- self.takeScreenshotAndShowItOnWindow()
-
- def enableEvents(self):
- self.canvas.update_idletasks()
- self.canvas.bind("<Button-1>", self.onButton1Pressed)
- self.canvas.bind("<Control-Button-1>", self.onCtrlButton1Pressed)
- self.canvas.bind("<Button-2>", self.onButton2Pressed)
- self.canvas.bind("<Button-3>", self.onButton3Pressed)
- self.canvas.bind("<BackSpace>", self.onKeyPressed)
- #self.canvas.bind("<Control-Key-S>", self.onCtrlS)
- self.canvas.bind("<Key>", self.onKeyPressed)
- self.areEventsDisabled = False
- def disableEvents(self):
- if self.canvas is not None:
- self.canvas.update_idletasks()
- self.areEventsDisabled = True
- self.canvas.unbind("<Button-1>")
- self.canvas.unbind("<Control-Button-1>")
- self.canvas.unbind("<Button-2>")
- self.canvas.unbind("<Button-3>")
- self.canvas.unbind("<BackSpace>")
- #self.canvas.unbind("<Control-Key-S>")
- self.canvas.unbind("<Key>")
-
- def toggleTargets(self):
- if DEBUG:
- print >> sys.stderr, "toggletargets: aretargetsmarked=", self.areTargetsMarked
- if not self.areTargetsMarked:
- self.markTargets()
- else:
- self.unmarkTargets()
- def markTargets(self):
- if DEBUG:
- print >> sys.stderr, "marktargets: aretargetsmarked=", self.areTargetsMarked
- print >> sys.stderr, " marktargets: targets=", self.targets
- colors = ["#ff00ff", "#ffff00", "#00ffff"]
- self.targetIds = []
- c = 0
- for (x1, y1, x2, y2) in self.targets:
- if DEBUG:
- print "adding rectangle:", x1, y1, x2, y2
- self.markTarget(x1, y1, x2, y2, colors[c%len(colors)])
- c += 1
- self.areTargetsMarked = True
- def markTarget(self, x1, y1, x2, y2, color='#ff00ff'):
- '''
- @return the id of the rectangle added
- '''
- self.areTargetsMarked = True
- return self.targetIds.append(self.canvas.create_rectangle(x1*self.scale, y1*self.scale, x2*self.scale, y2*self.scale, fill=color, stipple="gray25"))
- def unmarkTargets(self):
- if not self.areTargetsMarked:
- return
- for t in self.targetIds:
- self.canvas.delete(t)
- self.targetIds = []
- self.areTargetsMarked = False
- def setDragDialogShowed(self, showed):
- self.isDragDialogShowed = showed
- if showed:
- pass
- else:
- self.isGrabbingTouch = False
- def drawTouchedPoint(self, x, y):
- size = 50
- return self.canvas.create_oval((x-size)*self.scale, (y-size)*self.scale, (x+size)*self.scale, (y+size)*self.scale, fill=Color.MAGENTA)
-
- def drawDragLine(self, x0, y0, x1, y1):
- width = 15
- return self.canvas.create_line(x0*self.scale, y0*self.scale, x1*self.scale, y1*self.scale, width=width, fill=Color.MAGENTA, arrow="last", arrowshape=(50, 50, 30), dash=(50, 25))
-
- def executeCommandAndRefresh(self, command):
- self.showVignette()
- if DEBUG:
- print >> sys.stderr, 'DEBUG: command=', command, command.__name__
- print >> sys.stderr, 'DEBUG: command=', command.__self__, command.__self__.view
- try:
- view = command.__self__.view
- except AttributeError:
- view = None
- self.printOperation(view, Operation.fromCommandName(command.__name__))
- command()
- self.printOperation(None, Operation.SLEEP, Operation.DEFAULT)
- self.vc.sleep(5)
- # FIXME: perhaps refresh() should be invoked here just in case size or orientation changed
- self.takeScreenshotAndShowItOnWindow()
-
- def changeLanguage(self):
- code = tkSimpleDialog.askstring("Change language", "Enter the language code")
- self.vc.uiDevice.changeLanguage(code)
- self.printOperation(None, Operation.CHANGE_LANGUAGE, code)
- self.refresh()
- def setOnTouchListener(self, listener):
- self.onTouchListener = listener
- def setGrab(self, state):
- if DEBUG:
- print >> sys.stderr, "Culebron.setGrab(%s)" % state
- if state and not self.onTouchListener:
- warnings.warn('Starting to grab but no onTouchListener')
- self.isGrabbingTouch = state
- if state:
- self.toast('Grabbing drag points...', background=Color.GREEN)
- else:
- self.hideMessageArea()
- @staticmethod
- def isClickableCheckableOrFocusable(v):
- if DEBUG_ISCCOF:
- print >> sys.stderr, "isClickableCheckableOrFocusable(", v.__tinyStr__(), ")"
- try:
- return v.isClickable()
- except AttributeError:
- pass
- try:
- return v.isCheckable()
- except AttributeError:
- pass
- try:
- return v.isFocusable()
- except AttributeError:
- pass
- return False
- def mainloop(self):
- self.window.title("%s v%s" % (Culebron.APPLICATION_NAME, __version__))
- self.window.resizable(width=Tkinter.FALSE, height=Tkinter.FALSE)
- self.window.lift()
- self.window.mainloop()
- if TKINTER_AVAILABLE:
- class MainMenu(Tkinter.Menu):
- def __init__(self, culebron):
- Tkinter.Menu.__init__(self, culebron.window)
- self.culebron = culebron
- self.fileMenu = Tkinter.Menu(self, tearoff=False)
- self.fileMenu.add_command(label="Quit", underline=0, accelerator='Command-Q', command=self.culebron.quit)
- self.add_cascade(label="File", underline=0, menu=self.fileMenu)
- self.viewMenu = Tkinter.Menu(self, tearoff=False)
- self.showViewTree = Tkinter.BooleanVar()
- self.showViewTree.set(False)
- self.viewMenu.add_checkbutton(label="Tree", underline=0, accelerator='Command-T', onvalue=True, offvalue=False, variable=self.showViewTree, state=NORMAL, command=self.onshowViewTreeChanged)
- self.showViewDetails = Tkinter.BooleanVar()
- self.viewMenu.add_checkbutton(label="View details", underline=0, accelerator='Command-V', onvalue=True, offvalue=False, variable=self.showViewDetails, state=NORMAL, command=self.onShowViewDetailsChanged)
- self.add_cascade(label="View", underline=0, menu=self.viewMenu)
- self.uiDeviceMenu = Tkinter.Menu(self, tearoff=False)
- self.uiDeviceMenu.add_command(label="Open Notification", underline=6, command=lambda: culebron.executeCommandAndRefresh(self.culebron.vc.uiDevice.openNotification))
- self.uiDeviceMenu.add_command(label="Open Quick settings", underline=6, command=lambda: culebron.executeCommandAndRefresh(command=self.culebron.vc.uiDevice.openQuickSettings))
- self.uiDeviceMenu.add_command(label="Change Language", underline=7, command=self.culebron.changeLanguage)
- self.add_cascade(label="UiDevice", menu=self.uiDeviceMenu)
- self.helpMenu = Tkinter.Menu(self, tearoff=False)
- self.helpMenu.add_command(label="Keyboard shortcuts", underline=0, accelerator='Command-K', command=self.culebron.showHelp)
- self.add_cascade(label="Help", underline=0, menu=self.helpMenu)
-
- def callback(self):
- pass
- def onshowViewTreeChanged(self):
- if self.showViewTree.get() == 1:
- self.culebron.showViewTree()
- else:
- self.culebron.hideViewTree()
-
- def onShowViewDetailsChanged(self):
- if self.showViewDetails.get() == 1:
- self.culebron.showViewDetails()
- else:
- self.culebron.hideViewDetails()
-
- class ViewTree(Tkinter.Frame):
- def __init__(self, parent):
- Tkinter.Frame.__init__(self, parent)
- self.viewTree = ttk.Treeview(self, columns=['T'], height=35)
- self.viewTree.column(0, width=20)
- self.viewTree.heading('#0', None, text='View', anchor=Tkinter.W)
- self.viewTree.heading(0, None, text='T', anchor=Tkinter.W)
- self.scrollbar = ttk.Scrollbar(self, orient=Tkinter.HORIZONTAL, command=self.__xscroll)
- self.viewTree.grid(row=1, rowspan=1, column=1, sticky=Tkinter.N+Tkinter.S)
- self.scrollbar.grid(row=2, rowspan=1, column=1, sticky=Tkinter.E+Tkinter.W)
- self.viewTree.configure(xscrollcommand=self.scrollbar.set)
- def __xscroll(self, *args):
- if DEBUG:
- print >> sys.stderr, "__xscroll:", args
- self.viewTree.xview(*args)
- def insert(self, parent, index, iid=None, **kw):
- """Creates a new item and return the item identifier of the newly
- created item.
-
- parent is the item ID of the parent item, or the empty string
- to create a new top-level item. index is an integer, or the value
- end, specifying where in the list of parent's children to insert
- the new item. If index is less than or equal to zero, the new node
- is inserted at the beginning, if index is greater than or equal to
- the current number of children, it is inserted at the end. If iid
- is specified, it is used as the item identifier, iid must not
- already exist in the tree. Otherwise, a new unique identifier
- is generated."""
- return self.viewTree.insert(parent, index, iid, **kw)
- def set(self, item, column=None, value=None):
- """With one argument, returns a dictionary of column/value pairs
- for the specified item. With two arguments, returns the current
- value of the specified column. With three arguments, sets the
- value of given column in given item to the specified value."""
- return self.viewTree.set(item, column, value)
- def tag_bind(self, tagname, sequence=None, callback=None):
- if DEBUG:
- print >> sys.stderr, 'ViewTree.tag_bind(', tagname, ',', sequence, ',', callback, ')'
- return self.viewTree.tag_bind(tagname, sequence, callback)
- class ViewDetails(Tkinter.Frame):
- VIEW_DETAILS = "View Details:\n"
- def __init__(self, parent):
- Tkinter.Frame.__init__(self, parent)
- self.label = Tkinter.Label(self, bd=1, width=30, wraplength=200, justify=Tkinter.LEFT, anchor=Tkinter.NW)
- self.label.configure(text=self.VIEW_DETAILS)
- self.label.configure(bg="white")
- self.label.grid(row=3, column=1, rowspan=1)
- def set(self, view):
- self.label.configure(text=self.VIEW_DETAILS + view.__str__())
- class StatusBar(Tkinter.Frame):
- def __init__(self, parent):
- Tkinter.Frame.__init__(self, parent)
- self.label = Tkinter.Label(self, bd=1, relief=Tkinter.SUNKEN, anchor=Tkinter.W)
- self.label.grid(row=1, column=1, columnspan=2, sticky=Tkinter.E+Tkinter.W)
-
- def set(self, fmt, *args):
- self.label.config(text=fmt % args)
- self.label.update_idletasks()
-
- def clear(self):
- self.label.config(text="")
- self.label.update_idletasks()
-
- class LabeledEntry():
- def __init__(self, parent, text, validate, validatecmd):
- self.f = Tkinter.Frame(parent)
- Tkinter.Label(self.f, text=text, anchor="w", padx=8).grid(row=1, column=1, sticky=Tkinter.E)
- self.entry = Tkinter.Entry(self.f, validate=validate, validatecommand=validatecmd)
- self.entry.grid(row=1, column=2, padx=5, sticky=Tkinter.E)
-
- def grid(self, **kwargs):
- self.f.grid(kwargs)
-
- def get(self):
- return self.entry.get()
-
- def set(self, text):
- self.entry.delete(0, Tkinter.END)
- self.entry.insert(0, text)
-
- class LabeledEntryWithButton(LabeledEntry):
- def __init__(self, parent, text, buttonText, command, validate, validatecmd):
- LabeledEntry.__init__(self, parent, text, validate, validatecmd)
- self.button = Tkinter.Button(self.f, text=buttonText, command=command)
- self.button.grid(row=1, column=3)
-
- class DragDialog(Tkinter.Toplevel):
-
- DEFAULT_DURATION = 1000
- DEFAULT_STEPS = 20
-
- spX = None
- spY = None
- epX = None
- epY = None
- spId = None
- epId = None
-
- def __init__(self, culebron):
- self.culebron = culebron
- self.parent = culebron.window
- Tkinter.Toplevel.__init__(self, self.parent)
- self.transient(self.parent)
- self.culebron.setDragDialogShowed(True)
- self.title("Drag: selecting parameters")
-
- # valid percent substitutions (from the Tk entry man page)
- # %d = Type of action (1=insert, 0=delete, -1 for others)
- # %i = index of char string to be inserted/deleted, or -1
- # %P = value of the entry if the edit is allowed
- # %s = value of entry prior to editing
- # %S = the text string being inserted or deleted, if any
- # %v = the type of validation that is currently set
- # %V = the type of validation that triggered the callback
- # (key, focusin, focusout, forced)
- # %W = the tk name of the widget
- self.validate = (self.parent.register(self.onValidate), '%P')
- self.sp = LabeledEntryWithButton(self, "Start point", "Grab", command=self.onGrabSp, validate="focusout", validatecmd=self.validate)
- self.sp.grid(row=1, column=1, columnspan=3, pady=5)
-
- self.ep = LabeledEntryWithButton(self, "End point", "Grab", command=self.onGrabEp, validate="focusout", validatecmd=self.validate)
- self.ep.grid(row=2, column=1, columnspan=3, pady=5)
-
- l = Tkinter.Label(self, text="Units")
- l.grid(row=3, column=1, sticky=Tkinter.E)
- self.units = Tkinter.StringVar()
- self.units.set(Unit.DIP)
- col = 2
- for u in dir(Unit):
- if u.startswith('_'):
- continue
- rb = Tkinter.Radiobutton(self, text=u, variable=self.units, value=u)
- rb.grid(row=3, column=col, padx=20, sticky=Tkinter.E)
- col += 1
-
- self.d = LabeledEntry(self, "Duration", validate="focusout", validatecmd=self.validate)
- self.d.set(DragDialog.DEFAULT_DURATION)
- self.d.grid(row=4, column=1, columnspan=3, pady=5)
-
- self.s = LabeledEntry(self, "Steps", validate="focusout", validatecmd=self.validate)
- self.s.set(DragDialog.DEFAULT_STEPS)
- self.s.grid(row=5, column=1, columnspan=2, pady=5)
-
- self.buttonBox()
-
- def buttonBox(self):
- # add standard button box. override if you don't want the
- # standard buttons
-
- box = Tkinter.Frame(self)
-
- self.ok = Tkinter.Button(box, text="OK", width=10, command=self.onOk, default=Tkinter.ACTIVE, state=Tkinter.DISABLED)
- self.ok.grid(row=6, column=1, sticky=Tkinter.E, padx=5, pady=5)
- w = Tkinter.Button(box, text="Cancel", width=10, command=self.onCancel)
- w.grid(row=6, column=2, sticky=Tkinter.E, padx=5, pady=5)
-
- self.bind("<Return>", self.onOk)
- self.bind("<Escape>", self.onCancel)
-
- box.grid(row=6, column=1, columnspan=3)
-
- def onValidate(self, value):
- if self.sp.get() and self.ep.get() and self.d.get() and self.s.get():
- self.ok.configure(state=Tkinter.NORMAL)
- else:
- self.ok.configure(state=Tkinter.DISABLED)
-
- def onOk(self, event=None):
- if DEBUG:
- print >> sys.stderr, "onOK()"
- print >> sys.stderr, "values are:",
- print >> sys.stderr, self.sp.get(),
- print >> sys.stderr, self.ep.get(),
- print >> sys.stderr, self.d.get(),
- print >> sys.stderr, self.s.get(),
- print >> sys.stderr, self.units.get()
-
- sp = make_tuple(self.sp.get())
- ep = make_tuple(self.ep.get())
- d = int(self.d.get())
- s = int(self.s.get())
- self.cleanUp()
- # put focus back to the parent window's canvas
- self.culebron.canvas.focus_set()
- self.destroy()
- self.culebron.drag(sp, ep, d, s, self.units.get())
-
- def onCancel(self, event=None):
- self.culebron.setGrab(False)
- self.cleanUp()
- # put focus back to the parent window's canvas
- self.culebron.canvas.focus_set()
- self.destroy()
-
- def onGrabSp(self):
- '''
- Grab starting point
- '''
-
- self.sp.entry.focus_get()
- self.onGrab(self.sp)
-
- def onGrabEp(self):
- '''
- Grab ending point
- '''
-
- self.ep.entry.focus_get()
- self.onGrab(self.ep)
-
- def onGrab(self, entry):
- '''
- Generic grab method.
-
- @param entry: the entry being grabbed
- @type entry: Tkinter.Entry
- '''
-
- self.culebron.setOnTouchListener(self.onTouchListener)
- self.__grabbing = entry
- self.culebron.setGrab(True)
-
- def onTouchListener(self, point):
- '''
- Listens for touch events and draws the corresponding shapes on the Culebron canvas.
- If the starting point is being grabbed it draws the touching point via
- C{Culebron.drawTouchedPoint()} and if the end point is being grabbed it draws
- using C{Culebron.drawDragLine()}.
-
- @param point: the point touched
- @type point: tuple
- '''
-
- x = point[0]
- y = point[1]
- value = "(%d,%d)" % (x, y)
- self.__grabbing.set(value)
- self.onValidate(value)
- self.culebron.setGrab(False)
- if self.__grabbing == self.sp:
- self.__cleanUpSpId()
- self.__cleanUpEpId()
- self.spX = x
- self.spY = y
- elif self.__grabbing == self.ep:
- self.__cleanUpEpId()
- self.epX = x
- self.epY = y
- if self.spX and self.spY and not self.spId:
- self.spId = self.culebron.drawTouchedPoint(self.spX, self.spY)
- if self.spX and self.spY and self.epX and self.epY and not self.epId:
- self.epId = self.culebron.drawDragLine(self.spX, self.spY, self.epX, self.epY)
- self.__grabbing = None
- self.culebron.setOnTouchListener(None)
-
- def __cleanUpSpId(self):
- if self.spId:
- self.culebron.canvas.delete(self.spId)
- self.spId = None
-
- def __cleanUpEpId(self):
- if self.epId:
- self.culebron.canvas.delete(self.epId)
- self.epId = None
-
- def cleanUp(self):
- self.__cleanUpSpId()
- self.__cleanUpEpId()
-
- class ContextMenu(Tkinter.Menu):
- # FIXME: should get rid of the nested classes, otherwise it's not possible to create a parent class
- # SubMenu for UiScrollableSubMenu
- '''
- The context menu (popup).
- '''
-
- PADDING = ' '
- ''' Padding used to separate menu entries from border '''
- class Separator():
- SEPARATOR = 'SEPARATOR'
-
- def __init__(self):
- self.description = self.SEPARATOR
-
- class Command():
- def __init__(self, description, underline, shortcut, event, command):
- self.description = description
- self.underline = underline
- self.shortcut = shortcut
- self.event = event
- self.command = command
-
- class UiScrollableSubMenu(Tkinter.Menu):
- def __init__(self, menu, description, view, culebron):
- # Tkninter.Menu is not extending object, so we can't do this:
- #super(ContextMenu, self).__init__(culebron.window, tearoff=False)
- Tkinter.Menu.__init__(self, menu, tearoff=False)
- self.description = description
- self.add_command(label='Fling backward', command=lambda: culebron.executeCommandAndRefresh(view.uiScrollable.flingBackward))
- self.add_command(label='Fling forward', command=lambda: culebron.executeCommandAndRefresh(view.uiScrollable.flingForward))
- self.add_command(label='Fling to beginning', command=lambda: culebron.executeCommandAndRefresh(view.uiScrollable.flingToBeginning))
- self.add_command(label='Fling to end', command=lambda: culebron.executeCommandAndRefresh(view.uiScrollable.flingToEnd))
-
- def __init__(self, culebron, view):
- # Tkninter.Menu is not extending object, so we can't do this:
- #super(ContextMenu, self).__init__(culebron.window, tearoff=False)
- Tkinter.Menu.__init__(self, culebron.window, tearoff=False)
- if DEBUG_CONTEXT_MENU:
- print >> sys.stderr, "Creating ContextMenu for", view.__smallStr__() if view else "No View"
- self.view = view
- items = []
- if self.view:
- _saveViewSnapshotForSelectedView = lambda: culebron.saveViewSnapshot(self.view)
- items.append(ContextMenu.Command('Take view snapshot and save to file', 5, 'Ctrl+W', '<Control-W>', _saveViewSnapshotForSelectedView))
- if self.view.uiScrollable:
- items.append(ContextMenu.UiScrollableSubMenu(self, 'UiScrollable', view, culebron))
- else:
- parent = self.view.parent
- while parent:
- if parent.uiScrollable:
- # WARNING:
- # A bit dangerous, but may work
- # If we click ona ListView then the View pased to this ContextMenu is the child,
- # perhaps we want to scroll the parent
- items.append(ContextMenu.UiScrollableSubMenu(self, 'UiScrollable', parent, culebron))
- break
- parent = parent.parent
- items.append(ContextMenu.Separator())
-
- items.append(ContextMenu.Command('Drag dialog', 0, 'Ctrl+D', '<Control-D>', culebron.showDragDialog))
- items.append(ContextMenu.Command('Take snapshot and save to file', 26, 'Ctrl+F', '<Control-F>', culebron.saveSnapshot))
- items.append(ContextMenu.Command('Control Panel', 0, 'Ctrl+K', '<Control-K>', culebron.showControlPanel))
- items.append(ContextMenu.Command('Long touch point using PX', 0, 'Ctrl+L', '<Control-L>', culebron.toggleLongTouchPoint))
- items.append(ContextMenu.Command('Touch using DIP', 13, 'Ctrl+I', '<Control-I>', culebron.toggleTouchPointDip))
- items.append(ContextMenu.Command('Touch using PX', 12, 'Ctrl+P', '<Control-P>', culebron.toggleTouchPointPx))
- items.append(ContextMenu.Command('Generates a Sleep() on output script', 12, 'Ctrl+S', '<Control-S>', culebron.showSleepDialog))
- items.append(ContextMenu.Command('Toggle generating Test Condition', 18, 'Ctrl+T', '<Control-T>', culebron.toggleGenerateTestCondition))
- items.append(ContextMenu.Command('Touch Zones', 6, 'Ctrl+Z', '<Control-Z>', culebron.toggleTargetZones))
- items.append(ContextMenu.Command('Refresh', 0, 'F5', '<F5>', culebron.refresh))
- items.append(ContextMenu.Separator())
- items.append(ContextMenu.Command('Quit', 0, 'Ctrl+Q', '<Control-Q>', culebron.quit))
- for item in items:
- self.addItem(item)
- def addItem(self, item):
- if isinstance(item, ContextMenu.Separator):
- self.addSeparator()
- elif isinstance(item, ContextMenu.Command):
- self.addCommand(item)
- elif isinstance(item, ContextMenu.UiScrollableSubMenu):
- self.addSubMenu(item)
- else:
- raise RuntimeError("Unsupported item=" + str(item))
- def addSeparator(self):
- self.add_separator()
-
- def addCommand(self, item):
- self.add_command(label=self.PADDING + item.description, underline=item.underline + len(self.PADDING), command=item.command, accelerator=item.shortcut)
- #if item.event:
- # # These bindings remain even after the menu has been dismissed, so it seems not a good idea
- # #self.bind_all(item.event, item.command)
- # pass
-
- def addSubMenu(self, item):
- self.add_cascade(label=self.PADDING + item.description, menu=item)
-
- def showPopupMenu(self, event):
- try:
- self.tk_popup(event.x_root, event.y_root)
- finally:
- # make sure to release the grab (Tk 8.0a1 only)
- #self.grab_release()
- pass
- class HelpDialog(Tkinter.Toplevel):
-
- def __init__(self, culebron):
- self.culebron = culebron
- self.parent = culebron.window
- Tkinter.Toplevel.__init__(self, self.parent)
- #self.transient(self.parent)
- self.title("%s: help" % Culebron.APPLICATION_NAME)
-
- self.text = ScrolledText.ScrolledText(self, width=60, height=40)
- self.text.insert(Tkinter.INSERT, '''
- Special keys
- ------------
-
- F1: Help
- F5: Refresh
-
- Mouse Buttons
- -------------
- <1>: Touch the underlying View
-
- Commands
- --------
- Ctrl-A: Toggle message area
- Ctrl-D: Drag dialog
- Ctrl-F: Take snapshot and save to file
- Ctrl-K: Control Panel
- Ctrl-L: Long touch point using PX
- Ctrl-I: Touch using DIP
- Ctrl-P: Touch using PX
- Ctrl-Q: Quit
- Ctrl-S: Generates a sleep() on output script
- Ctrl-T: Toggle generating test condition
- Ctrl-V: Verifies the content of the screen dump
- Ctrl-Z: Touch zones
- ''')
- self.text.grid(row=1, column=1)
-
- self.buttonBox()
-
- def buttonBox(self):
- # add standard button box. override if you don't want the
- # standard buttons
-
- box = Tkinter.Frame(self)
-
- w = Tkinter.Button(box, text="Dismiss", width=10, command=self.onDismiss, default=Tkinter.ACTIVE)
- w.grid(row=1, column=1, padx=5, pady=5)
-
- self.bind("<Return>", self.onDismiss)
- self.bind("<Escape>", self.onDismiss)
-
- box.grid(row=1, column=1)
-
- def onDismiss(self, event=None):
- # put focus back to the parent window's canvas
- self.culebron.canvas.focus_set()
- self.destroy()
- class FileDialog():
- def __init__(self, culebron, filename):
- self.parent = culebron.window
- self.filename = filename
- self.basename = os.path.basename(self.filename)
- self.dirname = os.path.dirname(self.filename)
- self.ext = os.path.splitext(self.filename)[1]
- self.fileTypes = [('images', self.ext)]
-
- def askSaveAsFilename(self):
- return tkFileDialog.asksaveasfilename(parent=self.parent, filetypes=self.fileTypes, defaultextension=self.ext, initialdir=self.dirname, initialfile=self.basename)
|