Example #1
0
 def _update_bundle_id_list(self):
     '''
     在连接设备或者更换设备时,更新bundle_id列表
     '''
     print self._device
     self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
     original_bundle_id = self.tc_bundle_id.GetValue()
     self.tc_bundle_id.Clear()
     if self._device['simulator']:
         self._host_driver.start_simulator(self._device['udid'])
     self._app_list = self._device_driver.get_app_list(self._app_type)
     Log.i('app_list:', str(self._app_list))
     bundle_list = []
     remove_dev = None
     if self._app_list:
         for dev in self._app_list:
             bundle = dev.keys()[0]
             if bundle == 'com.apple.test.XCTestAgent-Runner':
                 remove_dev = dev
                 continue
             bundle_list.append(bundle)
             self.tc_bundle_id.Append(bundle.decode('utf-8'), dev)
         index = bundle_list.index(original_bundle_id) if original_bundle_id and original_bundle_id in bundle_list else 0
         self._run_in_main_thread(self.tc_bundle_id.Select, index)
         if remove_dev:
             self._app_list.remove(dev)
         if self.treeframe:
             self._run_in_main_thread(self.on_update_sandbox_view)
     else:
         self.create_tip_dialog(u'设备未启动或无可用APP')
         return
Example #2
0
 def on_start_app(self, event):
     if self._host_driver is None:
         self.create_tip_dialog(u"未连接设备主机,请连接设备主机")
         return
     self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
     self.statusbar.SetStatusText(u"App正在启动......", 0)
     self._scale_rate = None
     self._run_in_work_thread(self._start_app)
     self.show_dialog('App启动中......')
Example #3
0
 def on_uninstall(self, event):
     if self._device is None:
         self.create_tip_dialog(u'未选择设备,请连接设备主机并选择设备!')
         return
     if not self._device_driver:
         self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
     index = self.tc_bundle_id.GetSelection()
     bundle_id = self.tc_bundle_id.GetClientData(index).keys()[0]
     result = self._device_driver.uninstall_app(bundle_id)
     
     if result:
         uninstall_app = self.get_app(bundle_id)
         message_title = uninstall_app[bundle_id]+':'+bundle_id
         wx.MessageBox(u"卸载成功",message_title.decode('utf-8'))
         self._update_bundle_id_list()
     else:
         wx.MessageBox(u"卸载失败")
Example #4
0
 def on_install(self, pkg_path):
     
     self._run_in_main_thread(self.show_dialog, 'APP正在安装中......')
     while(True):
         if self._process_dlg_running:
             break
     dlg = self._dialog
     self._run_in_main_thread(dlg.on_update)
     
     if not self._device_driver:
         self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
     try:
         result = self._device_driver.install_app(pkg_path)
         self._process_dlg_running = False
         self._run_in_main_thread(dlg.on_destory)
         if result:
             self._run_in_main_thread(wx.MessageBox, '安装成功', '信息提示', wx.OK|wx.ICON_INFORMATION)
             self._update_bundle_id_list()
         else:
             self._run_in_main_thread(wx.MessageBox, '安装失败', '信息提示', wx.OK|wx.ICON_INFORMATION)
     except:
         error = traceback.format_exc()
         Log.e('start_app', error)
         self.create_tip_dialog(error.decode('utf-8'))
Example #5
0
class MainFrame(wx.Frame):
    
    def __init__(self):
        self._init_controls()
        self._driver_type = EnumDriverType.XCTest
        self._device = None 
        self._host_driver = None
        self._device_driver = None
        self._element_tree = None
        self._focused_element = None
        self._app_started = False
        self._orientation = 1
        self._app_type = 'user'
        self._process_dlg_running = False
        self.treeframe = None
    
    def _init_controls(self):
        #设置MacOS X的偏移
        if sys.platform == 'darwin':
            osx_offset = 5
        else:
            osx_offset = 0
        uispy_stype = wx.DEFAULT_FRAME_STYLE & ~wx.MAXIMIZE_BOX & ~wx.RESIZE_BORDER #屏蔽掉最大化按钮和窗口缩放功能
        wx.Frame.__init__(self, None, wx.ID_ANY, "UISpy "+VERSION, size=(900, 750-osx_offset*6), style=uispy_stype)
        
        # 设置左上角图标(仅限windows系统)
        if sys.platform == 'win32':
            logo = wx.Icon(os.path.join(RESOURCE_PATH, 'qt4i_win.ico'), wx.BITMAP_TYPE_ICO)
            self.SetIcon(logo)
            
        # 设置菜单栏
        menu_bar = wx.MenuBar()
        #应用菜单
        app_menu = wx.Menu()
        install_menu = app_menu.Append(wx.ID_ANY, u"安装App", u"安装app到被测手机")
        uninstall_menu = app_menu.Append(wx.ID_ANY, u"卸载App", u"卸载被测手机上的app")
        sandbox_menu = app_menu.Append(wx.ID_ANY, u"浏览App沙盒", u"打开并查看设备的沙盒目录")
        app_menu.AppendSeparator()
        menu_bar.Append(app_menu, u'应用')
        #高级菜单
        advance_menu = wx.Menu()
        log_menu = advance_menu.Append(wx.ID_ANY, u"查看日志", u"打开日志文件夹")
        debug_menu = advance_menu.Append(wx.ID_ANY, u"Debug模式", u"使用Debug模式运行UISpy")
        setting_menu = advance_menu.Append(wx.ID_ANY, u"设置", u"环境参数设置")
        self.show_qpath_menu = advance_menu.Append(wx.ID_ANY, u'显示QPath', u"打开即可显示控件Qpath",kind=wx.ITEM_CHECK)
        self.remote_operator_menu = advance_menu.Append(wx.ID_ANY, u'远程控制', u"打开即可远程控制手机",kind=wx.ITEM_CHECK)

        advance_menu.AppendSeparator()
        menu_bar.Append(advance_menu, u'高级')
        
        self.Bind(wx.EVT_MENU, self.on_select_install_pkg, install_menu) 
        self.Bind(wx.EVT_MENU, self.on_uninstall, uninstall_menu)
        self.Bind(wx.EVT_MENU, self.on_log, log_menu)
        self.Bind(wx.EVT_MENU, self.on_debug, debug_menu)
        self.Bind(wx.EVT_MENU, self.on_settings, setting_menu)
        self.Bind(wx.EVT_MENU, self.on_sandbox_view, sandbox_menu)
        self.SetMenuBar(menu_bar)
        
        # 设置主面板
        self.panel = wx.Panel(self, wx.ID_ANY)
        self.Bind(wx.EVT_CLOSE, self.on_close)
        
        # 第一行控件
        line1_y = 10
        wx.StaticText(self.panel, wx.ID_ANY, u"设备主机:", pos=(10,line1_y+osx_offset/2), size=wx.DefaultSize)
        self.tc_device_host_ip = wx.TextCtrl(self.panel, wx.ID_ANY, pos=(80, line1_y), size=(140, 24), style=wx.TE_LEFT)
        self.tc_device_host_ip.SetValue('127.0.0.1')
        self.tc_device_host_ip.SetToolTip(u"请输入设备主机或者设备DriverServer的IP")
        wx.StaticText(self.panel, wx.ID_ANY, u":", pos=(225, line1_y), size=wx.DefaultSize)
        self.tc_device_host_port = wx.TextCtrl(self.panel, wx.ID_ANY, pos=(235, line1_y), size=(60, 24), style=wx.TE_LEFT)
        self.tc_device_host_port.SetValue('12306')
        self.tc_device_host_port.SetToolTip(u"请输入设备主机或者设备DriverServer的端口号")
        self.btn_connect_device_host = wx.Button(self.panel, wx.ID_ANY, label=u'连接', pos=wx.Point(310, line1_y), size=wx.Size(50, 24), style=0)
        self.btn_connect_device_host.Bind(wx.EVT_BUTTON, self.on_connect_device_host)
        
        # 第二行控件
        line2_y = 45
        wx.StaticText(self.panel, wx.ID_ANY, u"设备:", pos=(10,line2_y+osx_offset/2), size=wx.DefaultSize)
        self.cb_devicelist = wx.ComboBox(self.panel, wx.ID_ANY, pos=(50, line2_y), size=(245+osx_offset/2, 24), style=wx.CB_READONLY)
        self.cb_devicelist.Bind(wx.EVT_COMBOBOX, self.on_select_device)
        self.btn_refresh = wx.Button(self.panel, wx.ID_ANY, u'刷新', pos=wx.Point(310, line2_y), size=wx.Size(50, 25), style=0)
        self.btn_refresh.Bind(wx.EVT_BUTTON, self.on_update_device_list)
        self.btn_refresh.Enable(False)
        
        # 第三行控件
        line3_y = 80
        wx.StaticText(self.panel, wx.ID_ANY, u"BundleID:", pos=(10,line3_y+osx_offset/2), size=wx.DefaultSize)
        self.tc_bundle_id = wx.ComboBox(self.panel, wx.ID_ANY, pos=(80-osx_offset, line3_y), size=(230+osx_offset*2, 24))
        self._config_file_path = os.path.join(RESOURCE_PATH, 'uispy.conf') 
        if os.path.exists(self._config_file_path):
            config_parser = ConfigParser.ConfigParser()
            config_parser.read(self._config_file_path)
            try:
                bunlde_id = config_parser.get('uispy', 'bundle_id')
            except ConfigParser.NoOptionError:
                bunlde_id = 'com.tencent.sng.test.gn'
        else:
            with open(self._config_file_path, 'w+') as fd:
                config_parser = ConfigParser.ConfigParser()
                config_parser.add_section('uispy')
                config_parser.write(fd)
            bunlde_id = DEFAULT_BUNDLE_ID
        self.tc_bundle_id.SetValue(bunlde_id)
        self.tc_bundle_id.SetToolTip(u"请选择被测App的Bundle ID")
        self.tc_bundle_id.Bind(wx.EVT_COMBOBOX, self.on_select_bundle_id)
        self.btn_refresh_applist = wx.Button(self.panel, wx.ID_ANY, u'刷新', pos=wx.Point(320, line3_y), size=wx.Size(40, 25), style=0)
        self.btn_refresh_applist.Bind(wx.EVT_BUTTON, self.on_update_app_list)
        self.btn_refresh_applist.Enable(False)
        self.tc_bundle_all = wx.CheckBox(self.panel, label = 'All',pos = (370, line3_y+osx_offset/2))
        self.tc_bundle_all.Bind(wx.EVT_CHECKBOX, self.on_select_bundle_all)
        self.btn_start_app = wx.Button(self.panel, wx.ID_ANY, label=u'启动App', pos=wx.Point(420, line3_y), size=wx.Size(80, 24), style=0)
        self.btn_start_app.Bind(wx.EVT_BUTTON, self.on_start_app)
        
        # 第四行控件  
        line4_y = 115
        wx.StaticText(self.panel, wx.ID_ANY, u"QPath:", pos=(10,line4_y+osx_offset/2), size=wx.DefaultSize)
        self.tc_qpath = wx.TextCtrl(self.panel, wx.ID_ANY, pos=(70-osx_offset, line4_y), size=(340, 24), style=wx.TE_LEFT)
        self.tc_qpath.SetValue('')
        self.btn_get_uitree = wx.Button(self.panel, wx.ID_ANY, label=u'获取控件', pos=wx.Point(420, line4_y), size=wx.Size(80, 24), style=0)
        self.btn_get_uitree.Bind(wx.EVT_BUTTON, self.on_get_uitree)
        
        #截屏区域的控件
        self.image_shown_width = 375
        self.image_shown_height = 667
        self.image_screenshot = wx.StaticBitmap(parent=self.panel, id=wx.ID_ANY, pos=(520, 5), size=(self.image_shown_width, self.image_shown_height))   
        self.image_screenshot.Bind(wx.EVT_MOUSE_EVENTS, self.on_screenshot_mouse_event)
        self.image_screenshot.Bind(wx.EVT_SET_FOCUS, self.on_screenshot_focus)
        startup_image = wx.Image(os.path.join(RESOURCE_PATH, 'startup.png'), wx.BITMAP_TYPE_ANY, index=-1)
        startup_image = startup_image.Scale(self.image_shown_width, self.image_shown_height)
        self.image_screenshot.SetBitmap(wx.Bitmap(startup_image))
        self.mask_panel = CanvasPanel(self.panel, id=wx.ID_ANY, pos=(520, 5), size=(self.image_shown_width, self.image_shown_height))
        self.mask_panel.Bind(wx.EVT_MOUSE_EVENTS, self.on_screenshot_mouse_event)
        #文件拖拽
        self.drop_target = FileDropTarget(self, self.image_screenshot)  
        self.image_screenshot.SetDropTarget(self.drop_target)
         
        #控件树
        line5_y = 150
        self.tc_uitree = wx.TreeCtrl(self.panel, wx.ID_ANY,pos=(10, line5_y), size=(500, 380))
        self.tc_uitree.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_uitree_node_click)
        self.tc_uitree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.on_uitree_node_right_click)
        
        #控件详细属性
        line6_y = 525
        st_control_properties = wx.StaticBox(self.panel, wx.ID_ANY, u"控件属性", pos=(10, line6_y+osx_offset*2), size=(500, 140))
        wx.StaticText(st_control_properties, wx.ID_ANY, u"name", pos=(10, 30-osx_offset*2), size=wx.DefaultSize)
        self.tc_id = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(55, 30-osx_offset*2), size=(135, 24), style=wx.TE_LEFT)
        wx.StaticText(st_control_properties, wx.ID_ANY, u"classname", pos=(200, 30-osx_offset*2), size=wx.DefaultSize)
        self.tc_classname = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(280, 30-osx_offset*2), size=(160, 24), style=wx.TE_LEFT)
        wx.StaticText(st_control_properties, wx.ID_ANY, u"label", pos=(10, 65-osx_offset*2), size=wx.DefaultSize)
        self.tc_label = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(55, 65-osx_offset*2), size=(135, 24), style=wx.TE_LEFT)
        wx.StaticText(st_control_properties, wx.ID_ANY, u"rect", pos=(200, 65-osx_offset*2), size=wx.DefaultSize)
        self.tc_rect = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(250, 65-osx_offset*2), size=(190, 24), style=wx.TE_LEFT)
        wx.StaticText(st_control_properties, wx.ID_ANY, u"value", pos=(10, 100-osx_offset*2), size=wx.DefaultSize)
        self.tc_value = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(55, 100-osx_offset*2), size=(135, 24), style=wx.TE_LEFT)
        wx.StaticText(st_control_properties, wx.ID_ANY, u"visible", pos=(200, 100-osx_offset*2), size=wx.DefaultSize)
        self.tc_visible = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(250, 100-osx_offset*2), size=(50, 24), style=wx.TE_LEFT)  
        wx.StaticText(st_control_properties, wx.ID_ANY, u"enable", pos=(310, 100-osx_offset*2), size=wx.DefaultSize)
        self.tc_enable = wx.TextCtrl(st_control_properties, wx.ID_ANY, pos=(370, 100-osx_offset*2), size=(50, 24), style=wx.TE_LEFT)            
  
        #状态栏
        self.statusbar = self.CreateStatusBar()
        # 将状态栏分割为3个区域,比例为1:2:3
        self.statusbar.SetFieldsCount(3)
        self.statusbar.SetStatusWidths([-6, -6, -1])
        
    def on_close(self, event):
        if self.treeframe:
            self.treeframe.Destroy()
        config_parser = ConfigParser.ConfigParser()   
        config_parser.read(self._config_file_path)
        config_parser.set("uispy", "bundle_id", self.tc_bundle_id.GetValue())
        with open(self._config_file_path, 'w') as fd:
            config_parser.write(fd)
        import atexit
        atexit._exithandlers = []  # 禁止退出时弹出错误框
        event.Skip()
        
    def create_tip_dialog(self, msg, title=u"错误"):
        dialog = wx.MessageDialog(self, msg, title, style=wx.OK)
        dialog.ShowModal()
        dialog.Destroy()
        
    def show_process_dialog(self, msg):
        progress_max = 100
        dialog = wx.ProgressDialog(u"提示", msg, progress_max, parent=self.panel, style=wx.PD_CAN_ABORT|wx.PD_APP_MODAL|wx.PD_AUTO_HIDE)
        count = 0
        while self._process_dlg_running and count < progress_max:
            count = (count + 1) % 100
            wx.MilliSleep(100)
            if not dialog.Update(count)[0]:
                break
        dialog.Destroy()
        
    def _run_in_main_thread(self, func, *args, **kwargs):
        wx.CallAfter(func, *args, **kwargs)
        
    def _run_in_work_thread(self, func, *args, **kwargs):
        t = threading.Thread(target=func, args=args, kwargs=kwargs)
        t.setDaemon(True)
        t.start()

    def _update_device_list(self):
        self._run_in_main_thread(self.statusbar.SetStatusText, u"正在获取设备列表......", 0)
        time.sleep(2)
        try:
            start_time = time.time()
            devices = self._host_driver.list_devices()
            end_time = time.time()
            print 'cost time:',(end_time - start_time)
        except:
            error = traceback.format_exc()
            Log.e('update_device_list', error)
            self._run_in_main_thread(self.statusbar.SetStatusText, u"更新设备列表失败", 0)
            self._run_in_main_thread(self.create_tip_dialog, error.decode('utf-8'))
            return
        self.cb_devicelist.Clear()
        for dev in devices:
            self.cb_devicelist.Append(dev['name'].decode('utf-8'), dev)
        self._device = devices[0]
        self._run_in_main_thread(self.btn_refresh.Enable)
        self._run_in_main_thread(self.btn_refresh_applist.Enable)
        self._run_in_main_thread(self.cb_devicelist.Select, 0)
        self._run_in_main_thread(self.statusbar.SetStatusText, u"更新设备列表完毕", 0)
        self._update_bundle_id_list()
    
    def _update_bundle_id_list(self):
        '''
        在连接设备或者更换设备时,更新bundle_id列表
        '''
        print self._device
        self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
        original_bundle_id = self.tc_bundle_id.GetValue()
        self.tc_bundle_id.Clear()
        if self._device['simulator']:
            self._host_driver.start_simulator(self._device['udid'])
        self._app_list = self._device_driver.get_app_list(self._app_type)
        Log.i('app_list:', str(self._app_list))
        bundle_list = []
        remove_dev = None
        if self._app_list:
            for dev in self._app_list:
                bundle = dev.keys()[0]
                if bundle == 'com.apple.test.XCTestAgent-Runner':
                    remove_dev = dev
                    continue
                bundle_list.append(bundle)
                self.tc_bundle_id.Append(bundle.decode('utf-8'), dev)
            index = bundle_list.index(original_bundle_id) if original_bundle_id and original_bundle_id in bundle_list else 0
            self._run_in_main_thread(self.tc_bundle_id.Select, index)
            if remove_dev:
                self._app_list.remove(dev)
            if self.treeframe:
                self._run_in_main_thread(self.on_update_sandbox_view)
        else:
            self.create_tip_dialog(u'设备未启动或无可用APP')
            return
    
    def on_connect_device_host(self, event):
        '''连接设备主机
        '''
        self.statusbar.SetStatusText(u"正在连接设备主机......", 0)
        host_ip = self.tc_device_host_ip.GetValue()
        host_port = self.tc_device_host_port.GetValue()
        self._host_driver = HostDriver(host_ip, host_port)
        if not self._host_driver.connect_to_host(self._driver_type):
            self.statusbar.SetStatusText(u"连接设备主机异常!", 0)
            config_parser = ConfigParser.ConfigParser()
            config_parser.read(os.path.join(RESOURCE_PATH, 'uispy.conf'))
            qt4i_manage = config_parser.get('uispy', 'qt4i-manage')
            if not os.path.exists(qt4i_manage):
                self.create_tip_dialog(u"请检查qt4i是否安装或者qt4i-manage路径是否配置正确!\n"
                                       u"1.qt4i安装方法:pip install qt4i --user\n"
                                       u"2.qt4i-manage配置方法:高级->设置")
            else:
                self.create_tip_dialog(u"连接设备主机异常,请检查设备主机地址")
            return
        self._run_in_work_thread(self._update_device_list)
    
    def on_select_device(self, event):
        '''从设备列表中选择设备
        '''
        index = self.cb_devicelist.GetSelection()
        selected_device = self.cb_devicelist.GetClientData(index)
        
        if self._device != selected_device:
            self._device = selected_device
            print 'current_device:', self._device
            self._run_in_work_thread(self._update_bundle_id_list)
        
    def on_update_device_list(self, event):
        '''刷新设备列表
        '''
        self.statusbar.SetStatusText(u"正在更新设备列表......", 0)
        self._run_in_work_thread(self._update_device_list)
    
    def _update_screenshot(self, img_data):
        image = wx.Image(StringIO.StringIO(img_data))
        if self._orientation == 3 and image.GetWidth() > image.GetHeight():
            image = image.Rotate90()
        w = image.GetWidth()
        h = image.GetHeight()
        print 'Width:', w
        print 'Height:', h
        image = image.Scale(self.image_shown_width, self.image_shown_height)
        self.image_screenshot.SetBitmap(wx.Bitmap(image)) 
    
    def _add_child(self, parent, children, depth):
        for child in children: 
            properties = dict.copy(child)
            del properties['children']
            new_item = self.tc_uitree.AppendItem(parent, child['classname'], data = properties) 
            child['item_id'] = new_item
            child['depth'] = depth
            self._add_child(new_item, child['children'], depth+1)
    
    def _update_uitree(self):
        self.tc_uitree.DeleteAllItems()
        if 'UIATarget' == self._element_tree['classname']:
            self._element_tree = self._element_tree['children'][0]
        app = dict.copy(self._element_tree)
        del app['children']
        
        self._app_shown_width = app['rect']['size']['width']
        self._app_shown_height = app['rect']['size']['height']
        
        if self._scale_rate is None:
            self._scale_rate = ((self.image_shown_width * 1.0) / self._app_shown_width,
                                (self.image_shown_height * 1.0) / self._app_shown_height)
#         root = self.tc_uitree.AddRoot(self._element_tree['classname'], data = wx.TreeItemData(app))
        root = self.tc_uitree.AddRoot(self._element_tree['classname'], data = app)
        self._element_tree['item_id'] = root
        self._element_tree['depth'] = -1
        self._root_item = root
        self._add_child(root, self._element_tree['children'], 0)
    
    def _start_app(self): 
        bundle_id = self.tc_bundle_id.GetValue()
        
        while(True):
            if self._process_dlg_running:
                break
        dlg = self._dialog
        try:
            xcode_version = self._device_driver.get_xcode_version()
            ios_version = self._device_driver.get_ios_version()
            Log.i('xcode_version:%s ios_version:%s' % (xcode_version, ios_version))
#             增加版本检测
            self._run_in_main_thread(dlg.on_update)
            self._app_started = self._device_driver.start_app(bundle_id)
            if self._app_started:
                self._run_in_main_thread(self.statusbar.SetStatusText, u"App启动成功", 0)
                self._run_in_main_thread(dlg.on_update_title_msg, '抓取App屏幕中......')
                img_data = self._device_driver.take_screenshot()
                self._orientation = self._device_driver.get_screen_orientation()
                self._run_in_main_thread(self._update_screenshot, img_data)
                self._run_in_main_thread(dlg.on_update_title_msg, '抓取App控件树中......')
                self._element_tree = self._device_driver.get_element_tree()
                self._run_in_main_thread(self._update_uitree)
            else:
                self._run_in_main_thread(self.statusbar.SetStatusText, u"App启动失败:", 0)
        except:
            error = traceback.format_exc()
            Log.e('start_app', error)
            self._run_in_main_thread(self.create_tip_dialog, error.decode('utf-8'))
        self._process_dlg_running = False
        self._run_in_main_thread(dlg.on_destory)
        
    def on_start_app(self, event):
        if self._host_driver is None:
            self.create_tip_dialog(u"未连接设备主机,请连接设备主机")
            return
        self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
        self.statusbar.SetStatusText(u"App正在启动......", 0)
        self._scale_rate = None
        self._run_in_work_thread(self._start_app)
        self.show_dialog('App启动中......')
        
    def show_dialog(self, msg):
        self._dialog = MyProgressDialog(msg, self.panel)
        self._process_dlg_running = True
    
    def on_get_uitree(self, event):
        img_data = self._device_driver.take_screenshot()
        self._orientation = self._device_driver.get_screen_orientation()
        print 'orientation: ', self._orientation
        self._update_screenshot(img_data)
        try:
            self._element_tree = self._device_driver.get_element_tree()
        except:
            self.create_tip_dialog(u'获取控件树失败:%s' % traceback.format_exc())
            return
        self._update_uitree()
        self.statusbar.SetStatusText(u"获取控件树成功", 0)
    
    def _check_pos_in_element(self, pos, element):
        rect = element['rect']
        if pos[0] >= rect['origin']['x'] and pos[0] <= rect['origin']['x'] + rect['size']['width'] \
           and pos[1] >= rect['origin']['y'] and pos[1] <= rect['origin']['y'] + rect['size']['height'] \
           and element['visible']:
            return True
        else:
            return False
        
    def _select_closest_element(self, pos, element1, element2):
        '''选择距离最近的控件,具体规则如下:
            1、如果element1和element2的位置是包含关系(父子关系),则选择子节点
            2、如果element1和element2的位置是部分重叠,则选择距离控件中心最近的控件
        '''
        rect1 = element1['rect']
        rect2 = element2['rect']
        if rect1['origin']['x'] >= rect2['origin']['x'] and \
            rect1['origin']['x'] + rect1['size']['width'] <= rect2['origin']['x'] + rect2['size']['width'] and \
            rect1['origin']['y'] >= rect2['origin']['y'] and \
            rect1['origin']['y'] + rect1['size']['height'] <= rect2['origin']['y'] + rect2['size']['height']:
            return element1
        
        if rect2['origin']['x'] >= rect1['origin']['x'] and \
            rect2['origin']['x'] + rect2['size']['width'] <= rect1['origin']['x'] + rect1['size']['width'] and \
            rect2['origin']['y'] >= rect1['origin']['y'] and \
            rect2['origin']['y'] + rect2['size']['height'] <= rect1['origin']['y'] + rect1['size']['height']:
            return element2
        
        center1 = (rect1['origin']['x'] + rect1['size']['width'] / 2.0, rect1['origin']['y'] + rect1['size']['height'] / 2.0)
        center2 = (rect2['origin']['x'] + rect2['size']['width'] / 2.0, rect2['origin']['y'] + rect2['size']['height'] / 2.0)
        distance1 = (pos[0] - center1[0]) ** 2 + (pos[1] - center1[1]) ** 2       
        distance2 = (pos[0] - center2[0]) ** 2 + (pos[1] - center2[1]) ** 2  
        if distance1 <= distance2:
            return element1
        else:
            return element2     
    
    def _get_focused_element(self, pos, root):
        '''优先查找叶子节点的控件(深度遍历)
        '''
        for e in root['children']:
            self._get_focused_element(pos, e)
            if self._check_pos_in_element(pos, e):
                if self._focused_element is None:
                    self._focused_element = e
                else:
                    self._focused_element = self._select_closest_element(pos, self._focused_element, e)
        
#         if self._check_pos_in_element(pos, root):
#                 if self._focused_element is None:
#                     self._focused_element = root
#                 else:
#                     self._focused_element = self._select_closest_element(pos, self._focused_element, root)    
    
    def _expand_uitree(self, item_id):
        '''展开控件树
        '''
        if item_id != self._root_item:
            parent = self.tc_uitree.GetItemParent(item_id)
            self._expand_uitree(parent)
            self.tc_uitree.Expand(item_id)
        else:
            self.tc_uitree.Expand(self._root_item)
            
    def _dfs_traverse(self, element_tree, element_list=None):
        if element_list is None:
            element_list = []
        element_list.append(element_tree)
        for e in element_tree['children']:
            self._dfs_traverse(e, element_list)
        return element_list
            
    def _recommend_qpath(self, element):
        '''推荐QPath,具体策略如下:
           1、id唯一,则推荐id作为QPath
           2、待补充
        '''    
        self.tc_qpath.SetValue("")    
        if element['name']:
            element_id = element['name']
            element_list = self._dfs_traverse(self._element_tree)
            count = 0
            for e in element_list:
                if e['name'] == element_id:
                    count += 1
            if count == 1:
                self.tc_qpath.SetValue(element_id.decode('utf-8'))
                return
            
        if self.show_qpath_menu.IsChecked():
            if element['classname'] == 'Other' and not element['label'] and not element['name'] and not element['value']:
                return
            
            qpath = '/classname = \'' + element['classname'] + '\''
            if element['label']:
                qpath += ' & label = \'' +element['label'] + '\''
            elif element['name']:
                qpath += ' & name = \'' +element['name'] + '\''
            elif element['value']:
                qpath += ' & value = \'' +element['value'] + '\''
                
            qpath += ' & visible = %s & maxdepth = %s' % (str(element['visible']).lower(), str(element['depth']).lower())
            self.tc_qpath.SetValue(qpath.decode('utf-8'))
            
    
    def on_screenshot_focus(self, event):
        print 'screenshot focus'
#         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
    
    def on_screenshot_single_click(self, event):
        self.timer.Stop()
        click_action = 'click'
        if not self._mouse_up:
            click_action = 'long_click'
        if not self._element_tree:
            Log.e('on_screenshot_single_click', 'failed to get element tree')
            return
        self._focused_element = None
        self._get_focused_element(self._single_click_position, self._element_tree)
        print 'focus_element:', self._focused_element ['rect']
        rect = (self._focused_element ['rect']['origin']['x'], self._focused_element ['rect']['origin']['y'], \
            self._focused_element ['rect']['size']['width'], self._focused_element ['rect']['size']['height'])
        #红色标记截图中鼠标位置的控件
        if self._orientation == 3:
            rect = (self.image_shown_width/self._scale_rate[0]-rect[1] - rect[3], rect[0], rect[3], rect[2])
        self.mask_panel.highlight_element(rect, self._scale_rate)
        #展开控件树
        self._expand_uitree(self._focused_element['item_id'])
        #选中对应的控件
        self.tc_uitree.SelectItem(self._focused_element['item_id'])
        self.tc_uitree.SetFocus()
        #推荐控件的QPath
        self._recommend_qpath(self._focused_element)
        #判断是否进行远程控制
        if self.remote_operator_menu.IsChecked() and self._device_driver:
            driver_method = getattr(self._device_driver, click_action)
            driver_method(self.x, self.y)
            time.sleep(1.5)
            self._run_in_work_thread(self.on_get_uitree,event)
                
    def on_screenshot_mouse_event(self, event):
        pos = event.GetPosition()
        if self._orientation == 3:
            pos = (pos[1], self.image_shown_width - pos[0])
        
        self.statusbar.SetStatusText(str(pos),2)
        if not self._app_started:
            self.statusbar.SetStatusText(u'App未启动',0)
            return

        if event.ButtonDown():
            self._mouse_up = False
            self._event_start = time.time()
            pos = (pos[0]/self._scale_rate[0], pos[1]/self._scale_rate[1])
            self.x = float(pos[0])/self._app_shown_width
            self.y = float(pos[1])/self._app_shown_height
            if not event.RightDown():
                self.timer = wx.Timer(self)
                self.timer.Start(200) # 0.2 seconds delay
                self.Bind(wx.EVT_TIMER, self.on_screenshot_single_click, self.timer)
                
        elif event.Dragging():
            self.timer.Stop()
            
        elif event.LeftDClick():
            self.timer.Stop()

        elif event.LeftUp():
            self._mouse_up = True
            self._event_interval = time.time() - self._event_start
            new_pos = (pos[0]/self._scale_rate[0], pos[1]/self._scale_rate[1])
            x1 = float(new_pos[0])/self._app_shown_width
            y1 = float(new_pos[1])/self._app_shown_height
            self._single_click_position = new_pos
            if abs(self.x - x1) > 0.02 or abs(self.y - y1) > 0.02:
                if self.remote_operator_menu.IsChecked() and self._device_driver:
                    self._device_driver.drag(self.x, self.y, x1, y1, self._event_interval)
                    time.sleep(1.5)
                    self._run_in_work_thread(self.on_get_uitree,event)
                
    def on_uitree_node_click(self, event):
        
        item_id = event.GetItem()
        
        item_data = self.tc_uitree.GetItemData(item_id)
        
        item_data['depth'] = self.get_element_depth(item_id)
        
        self.tc_classname.SetValue(item_data['classname'])
        name = item_data['name'] if item_data['name'] else 'null'
        if isinstance(name, basestring):
            name = name.decode('utf-8')
        else:
            name = str(name)
        self.tc_id.SetValue(name)
        label = item_data['label'] if item_data['label'] else 'null'
        if isinstance(label, basestring):
            label = label.decode('utf-8')
        else:
            label = str(label)    
        self.tc_label.SetValue(label)
        value = item_data['value'] if item_data['value'] else 'null'
        if isinstance(value, basestring):
            value = value.decode('utf-8')
        else:
            value = str(value)  
        self.tc_value.SetValue(value)
        rect = (item_data['rect']['origin']['x'], item_data['rect']['origin']['y'], \
                                   item_data['rect']['size']['width'], item_data['rect']['size']['height'])
        self.tc_rect.SetValue(str(rect))
        self.tc_visible.SetValue('true' if item_data['visible'] else 'False')
        self.tc_enable.SetValue('true' if item_data['enabled'] else 'False')
        
        if self._orientation == 3:
            rect = (self.image_shown_width/self._scale_rate[0]-rect[1]-rect[3], rect[0], rect[3], rect[2])
        self.mask_panel.highlight_element(rect, self._scale_rate)
        
        self._recommend_qpath(item_data)
    
    def on_uitree_node_right_click(self, event):
        self.tc_uitree.PopupMenu(TreeNodePopupMenu(self, event.GetItem()), event.Point)    

    def on_select_install_pkg(self, event):
        if self._device is None:
            self.create_tip_dialog(u'未选择设备,请连接设备主机并选择设备!')
            return
        dlg = wx.FileDialog(self, u"选择app的安装包", "", "",
                                       "app files (*.zip,*.ipa)|*.zip;*.ipa", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_CANCEL:
            return
        pkg_path = dlg.GetPath().encode('utf-8')
        dlg.Destroy()
        self._run_in_work_thread(self.on_install, pkg_path)

    def on_install(self, pkg_path):
        
        self._run_in_main_thread(self.show_dialog, 'APP正在安装中......')
        while(True):
            if self._process_dlg_running:
                break
        dlg = self._dialog
        self._run_in_main_thread(dlg.on_update)
        
        if not self._device_driver:
            self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
        try:
            result = self._device_driver.install_app(pkg_path)
            self._process_dlg_running = False
            self._run_in_main_thread(dlg.on_destory)
            if result:
                self._run_in_main_thread(wx.MessageBox, '安装成功', '信息提示', wx.OK|wx.ICON_INFORMATION)
                self._update_bundle_id_list()
            else:
                self._run_in_main_thread(wx.MessageBox, '安装失败', '信息提示', wx.OK|wx.ICON_INFORMATION)
        except:
            error = traceback.format_exc()
            Log.e('start_app', error)
            self.create_tip_dialog(error.decode('utf-8'))
        
    def on_uninstall(self, event):
        if self._device is None:
            self.create_tip_dialog(u'未选择设备,请连接设备主机并选择设备!')
            return
        if not self._device_driver:
            self._device_driver = DeviceDriver(self._host_driver.host_url, self._device['udid'])
        index = self.tc_bundle_id.GetSelection()
        bundle_id = self.tc_bundle_id.GetClientData(index).keys()[0]
        result = self._device_driver.uninstall_app(bundle_id)
        
        if result:
            uninstall_app = self.get_app(bundle_id)
            message_title = uninstall_app[bundle_id]+':'+bundle_id
            wx.MessageBox(u"卸载成功",message_title.decode('utf-8'))
            self._update_bundle_id_list()
        else:
            wx.MessageBox(u"卸载失败")

    def on_log(self, event):
        '''
        打开日志所在文件夹
        '''
        log_path = Log.gen_log_path()
        if os.path.exists(log_path):
            if sys.platform == 'darwin':
                log_cmd = 'open %s' % log_path
                subprocess.call(log_cmd,shell=True)
            elif sys.platform == 'win32':
                os.system("explorer.exe %s" % log_path)
        else:
            self.create_tip_dialog(u'日志文件被移除')
            return
    
    def on_select_bundle_id(self, event):
        '''
        当bundle_id切换选择时,更新沙盒目录
        '''
        if self.treeframe:
            self.treeframe.update_directory_tree(self.tc_bundle_id.GetValue())
            
    def on_update_app_list(self, event):
        '''
        更新app列表
        '''
        if self._device:
            self._run_in_work_thread(self._update_bundle_id_list)
        
    def on_select_bundle_all(self, event):
        '''
        根据按钮打开多有或者用户APP
        '''
        bundle_check = event.GetEventObject()
        if bundle_check.GetValue():
            self._app_type = 'all'
        else:
            self._app_type = 'user'
        if self._device:
            self._run_in_work_thread(self._update_bundle_id_list)
        
    def get_element_depth(self, item_id):
        '''
        获取element的depth
        :param item_id element的属性
        
        return depth
        '''
        depth = 0
        while item_id != self._root_item:
            item_id = self.tc_uitree.GetItemParent(item_id)
            depth += 1
        return depth
    
    def get_app(self, bundle_id):
        '''
        根据bundle_id从app_list中获取app信息
        '''
        for app in self._app_list:
            if app.has_key(bundle_id):
                return app

    def on_debug(self, event):
        '''
        '''
        current_path = os.getcwd()
        debug_path = os.path.join(current_path, DEBUG_PATH)
        if current_path.endswith('ui'):
            wx.MessageBox('本模式下不支持')
        else:
            self.Close(force=True)
            wx.Exit()
            cmd = 'open %s' % debug_path
            subprocess.call(cmd,shell=True)

    def on_settings(self, event):
        setting_dlg = Settings(self._config_file_path, None, title=u'环境参数设置')
        setting_dlg.ShowModal()
        setting_dlg.Destroy()

    def on_sandbox_view(self, event):
        '''
        查看沙盒的目录结构
        '''
        if self._device is None:
            self.create_tip_dialog(u'未选择设备,请连接设备主机并选择设备!')
            return
        self.treeframe = TreeFrame(self, '/', '%s沙盒目录' % self._device['name'], self._device_driver, self.tc_bundle_id.GetValue())
        self.treeframe.Show(show=True)
    
    def on_update_sandbox_view(self):
        if self.treeframe:
            self.treeframe.update_tree_frame('%s沙盒目录' % self._device['name'], self._device_driver, self.tc_bundle_id.GetValue())
    
    def on_close_treeFrame(self):
        '''监控沙盒frame是否关闭
        '''
        self.treeframe = None