コード例 #1
0
class AwBrowser(QDialog):
    """
        Customization and configuration of a web browser to run within Anki
    """

    _parent = None
    _fields = []
    _selectedListener = None
    _web = None
    _urlInfo = None
    
    def __init__(self, myParent):
        QDialog.__init__(self, myParent)
        self._parent = myParent
        self.setupUI()
        
    def setupUI(self):
        self.setWindowTitle('Anki :: Web Browser Addon')
        self.setGeometry(450, 200, 800, 450)
        self.setMinimumWidth (640)
        self.setMinimumHeight(450)

        mainLayout = QVBoxLayout()
        mainLayout.setContentsMargins(0,0,0,0)
        mainLayout.setSpacing(0)
        self.setLayout(mainLayout)

        topWidget = QtWidgets.QWidget(self)
        topWidget.setFixedHeight(50)

        topLayout = QtWidgets.QHBoxLayout(topWidget)
        topLayout.setObjectName("topLayout")
        
        lbSite = QtWidgets.QLabel(topWidget)
        lbSite.setObjectName("label")
        lbSite.setText("Website: ")

        topLayout.addWidget(lbSite)
        self._itAddress = QtWidgets.QLineEdit(topWidget)
        self._itAddress.setObjectName("itSite")
        topLayout.addWidget(self._itAddress)
        cbGo = QtWidgets.QCommandLinkButton(topWidget)
        cbGo.setObjectName("cbGo")
        cbGo.setFixedSize(30, 30)
        topLayout.addWidget(cbGo)
        # cbImport = QtWidgets.QCommandLinkButton(topWidget)
        # cbImport.setObjectName("cbImport")
        # cbImport.setFixedSize(30, 30)
        # topLayout.addWidget(cbImport)
        self._loadingBar = QtWidgets.QProgressBar(topWidget)
        self._loadingBar.setFixedWidth(100)
        self._loadingBar.setProperty("value", 100)
        self._loadingBar.setObjectName("loadingBar")
        topLayout.addWidget(self._loadingBar)

        mainLayout.addWidget(topWidget)

        self._web = QWebEngineView(self)
        self._web.contextMenuEvent = self.contextMenuEvent
        self._web.page().loadStarted.connect(self.onStartLoading)
        self._web.page().loadFinished.connect(self.onLoadFinish)
        self._web.page().loadProgress.connect(self.onProgress)
        self._web.page().urlChanged.connect(self.onPageChange)

        cbGo.clicked.connect(self._goToAddress)

        mainLayout.addWidget(self._web)

        if cfg.getConfig().browserAlwaysOnTop:
            self.setWindowFlags(Qt.WindowStaysOnTopHint)

    def formatTargetURL(self, website: str, query: str = ''):
        return website.format(urllib.parse.quote(query, encoding='utf8'))  #encode('utf8', 'ignore')

    def open(self, website, query: str):
        """
            Loads a given page with its replacing part with its query, and shows itself
        """

        target = self.formatTargetURL(website, query)
        self._web.load(QUrl( target ))
        self._itAddress.setText(target)
        
        self.show()
        return self._web

    def unload(self):
        try:
            self._web.setHtml(BLANK_PAGE)
        except (RuntimeError) as err:
            pass

    def onClose(self):
        self._parent = None
        self._web.close()
        self.close()

    def onStartLoading(self):
        self._loadingBar.setProperty("value", 1)

    def onProgress(self, prog):
        self._loadingBar.setProperty("value", prog)

    def onLoadFinish(self, result):
        self._loadingBar.setProperty("value", 100)
        if not result:
            Feedback.showInfo('Error loading page')
            Feedback.log('Error on loading page! ', result)

    def _goToAddress(self):
        self._web.load(QUrl( self._itAddress.text() ))
        self._web.show()
    
    def onPageChange(self, url):
        self._itAddress.setText(url.toString())

    def welcome(self):
        self._web.setHtml(WELCOME_PAGE)
        self.show()

# ------------------------------------ Menu ---------------------------------------

    def _makeMenuAction(self, field, value, isLink):
        """
            Creates correct operations for the context menu selection.
            Only with lambda, it would repeat only the last element
        """

        return lambda: self._selectedListener.handleSelection(field, value, isLink)

    def contextMenuEvent(self, evt):
        """
            Handles the context menu in the web view. 
            Shows and handle options (from field list), only if in edit mode.
        """

        if not (self._fields and self._selectedListener):
            return

        isLink = False
        value = None
        if self._web.selectedText():
            isLink = False
            value = self._web.selectedText()
        else:
            if (self._web.page().contextMenuData().mediaType() == QWebEngineContextMenuData.MediaTypeImage
                    and self._web.page().contextMenuData().mediaUrl()):
                isLink = True
                value = self._web.page().contextMenuData().mediaUrl()

        if not value:
            return

        self.createCtxMenu(value, isLink, evt)

    def createCtxMenu(self, value, isLink, evt):
        'Creates and configures the menu itself'
        
        m = QMenu(self)
        sub = QMenu(Label.BROWSER_ASSIGN_TO, m)
        m.setTitle(Label.BROWSER_ASSIGN_TO)
        for index, label in self._fields.items():
            act = QAction(label, m, 
                triggered=self._makeMenuAction(index, value, isLink))
            sub.addAction(act)

        m.addMenu(sub)
        action = m.exec_(self.mapToGlobal(evt.pos()))

    def load(self, qUrl):
        self._web.load(qUrl)

#   ----------------- getter / setter  -------------------

    def setFields(self, fList):
        self._fields = fList

    def setSelectionListener(self, value):
        self._selectedListener = value
コード例 #2
0
ファイル: browser.py プロジェクト: GalaxyGroot/anki-plugins
class AwBrowser(QDialog):
    """
        Customization and configuration of a web browser to run within Anki
    """

    SINGLETON = None
    TITLE = 'Anki :: Web Browser Addon'

    _parent = None
    _fields = []
    _selectionHandler = None
    _web = None
    _context = None
    _lastAssignedField = None
    infoList = []
    providerList = []

    def __init__(self, myParent):
        QDialog.__init__(self, None)
        self._parent = myParent
        self.setupUI()

        if myParent:

            def wrapClose(fn):
                def clozeBrowser(evt):
                    self.close()
                    fn(evt)

                return clozeBrowser

            myParent.closeEvent = wrapClose(myParent.closeEvent)

    def setupUI(self):
        self.setWindowTitle(AwBrowser.TITLE)
        self.setWindowFlags(Qt.WindowMinMaxButtonsHint
                            | Qt.WindowCloseButtonHint)
        self.setGeometry(450, 200, 800, 450)
        self.setMinimumWidth(640)
        self.setMinimumHeight(450)
        self.setStyleSheet(Style.DARK_BG)

        mainLayout = QVBoxLayout()
        mainLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.setSpacing(0)
        self.setLayout(mainLayout)

        self._web = QWebEngineView(self)
        self._web.contextMenuEvent = self.contextMenuEvent
        self._web.page().loadStarted.connect(self.onStartLoading)
        self._web.page().loadFinished.connect(self.onLoadFinish)
        self._web.page().loadProgress.connect(self.onProgress)
        self._web.page().urlChanged.connect(self.onPageChange)

        # -------------------- Top / toolbar ----------------------
        navtbar = QToolBar("Navigation")
        navtbar.setIconSize(QSize(16, 16))
        mainLayout.addWidget(navtbar)

        backBtn = QAction(
            QtGui.QIcon(os.path.join(CWD, 'assets', 'arrow-back.png')), "Back",
            self)
        backBtn.setStatusTip("Back to previous page")
        backBtn.triggered.connect(self._web.back)
        navtbar.addAction(backBtn)

        self.forwardBtn = QAction(
            QtGui.QIcon(os.path.join(CWD, 'assets', 'arrow-forward.png')),
            "Forward", self)
        self.forwardBtn.setStatusTip("Next visited page")
        self.forwardBtn.triggered.connect(self._web.forward)
        navtbar.addAction(self.forwardBtn)

        refreshBtn = QAction(
            QtGui.QIcon(os.path.join(CWD, 'assets', 'reload.png')), "Reload",
            self)
        refreshBtn.setStatusTip("Reload")
        refreshBtn.triggered.connect(self._web.reload)
        navtbar.addAction(refreshBtn)

        self.createProvidersMenu(navtbar)

        self._itAddress = QtWidgets.QLineEdit(self)
        self._itAddress.setObjectName("itSite")
        self._itAddress.setStyleSheet('background-color: #F5F5F5;')
        self._itAddress.returnPressed.connect(self._goToAddress)
        navtbar.addWidget(self._itAddress)

        cbGo = QAction(QtGui.QIcon(os.path.join(CWD, 'assets', 'go-icon.png')),
                       "Go", self)
        cbGo.setObjectName("cbGo")
        navtbar.addAction(cbGo)
        cbGo.triggered.connect(self._goToAddress)

        self.stopBtn = QAction(
            QtGui.QIcon(os.path.join(CWD, 'assets', 'stop.png')), "Stop", self)
        self.stopBtn.setStatusTip("Stop loading")
        self.stopBtn.triggered.connect(self._web.stop)
        navtbar.addAction(self.stopBtn)
        # -------------------- Center ----------------------
        mainLayout.addWidget(self._web)
        # -------------------- Bottom bar ----------------------

        bottomWidget = QtWidgets.QWidget(self)
        bottomWidget.setFixedHeight(30)

        bottomLayout = QtWidgets.QHBoxLayout(bottomWidget)
        bottomLayout.setObjectName("bottomLayout")
        bottomWidget.setStyleSheet('color: #FFF;')

        lbSite = QtWidgets.QLabel(bottomWidget)
        lbSite.setObjectName("label")
        lbSite.setText("Context: ")
        lbSite.setFixedWidth(70)
        lbSite.setStyleSheet('font-weight: bold;')
        bottomLayout.addWidget(lbSite)

        self.ctxWidget = QtWidgets.QLabel(bottomWidget)
        self.ctxWidget.width = 300
        self.ctxWidget.setStyleSheet('text-align: left;')
        bottomLayout.addWidget(self.ctxWidget)

        self._loadingBar = QtWidgets.QProgressBar(bottomWidget)
        self._loadingBar.setFixedWidth(100)
        self._loadingBar.setProperty("value", 100)
        self._loadingBar.setObjectName("loadingBar")
        bottomLayout.addWidget(self._loadingBar)

        mainLayout.addWidget(bottomWidget)

        if cfg.getConfig().browserAlwaysOnTop:
            self.setWindowFlags(Qt.WindowStaysOnTopHint)

    @classmethod
    def singleton(cls, parent):
        if not cls.SINGLETON:
            cls.SINGLETON = AwBrowser(parent)
        return cls.SINGLETON

    def formatTargetURL(self, website: str, query: str = ''):
        return website.format(urllib.parse.quote(query, encoding='utf8'))

    @exceptionHandler
    def open(self, website, query: str):
        """
            Loads a given page with its replacing part with its query, and shows itself
        """

        self._context = query
        self._updateContextWidget()
        target = self.formatTargetURL(website, query)
        self._web.load(QUrl(target))
        self._itAddress.setText(target)

        self.show()
        self.raise_()
        return self._web

    def unload(self):
        try:
            self._web.setHtml(BLANK_PAGE)
            self._itAddress.setText('about:blank')
        except (RuntimeError) as err:
            pass

    def onClose(self):
        self._parent = None
        self._web.close()
        self.close()

    def onStartLoading(self):
        self.stopBtn.setEnabled(True)
        self._loadingBar.setProperty("value", 1)

    def onProgress(self, prog):
        self._loadingBar.setProperty("value", prog)

    def onLoadFinish(self, result):
        self.stopBtn.setDisabled(True)
        self._loadingBar.setProperty("value", 100)

        if not result:
            Feedback.log('No result on loading page! ')

    def _goToAddress(self):
        q = QUrl(self._itAddress.text())
        if q.scheme() == "":
            q.setScheme("http")

        self._web.load(q)
        self._web.show()

    def onPageChange(self, url):
        if url and url.toString().startswith('http'):
            self._itAddress.setText(url.toString())
        self.forwardBtn.setEnabled(self._web.history().canGoForward())

    def welcome(self):
        self._web.setHtml(WELCOME_PAGE)
        self._itAddress.setText('about:blank')
        self.show()
        self.raise_()

    def _updateContextWidget(self):
        self.ctxWidget.setText(self._context)

# ---------------------------------------------------------------------------------

    def createProvidersMenu(self, parentWidget):
        providerBtn = QAction(
            QtGui.QIcon(os.path.join(CWD, 'assets', 'gear-icon.png')),
            "Providers", parentWidget)
        providerBtn.setStatusTip("Search with Provider")
        providerBtn.triggered.connect(
            lambda: self.newProviderMenu(providerBtn))
        parentWidget.addAction(providerBtn)

    def newProviderMenu(self, parentBtn):
        ctx = ProviderSelectionController()
        ctx.showCustomMenu(parentBtn.parentWidget(), self.reOpenSameQuery)

    @exceptionHandler
    def reOpenSameQuery(self, website):
        self.open(website, self._context)

# ------------------------------------ Menu ---------------------------------------

    def _makeMenuAction(self, field, value, isLink):
        """
            Creates correct operations for the context menu selection.
            Only with lambda, it would repeat only the last element
        """
        def _processMenuSelection():
            self._lastAssignedField = field
            self._selectionHandler(field, value, isLink)

        return _processMenuSelection

    def contextMenuEvent(self, evt):
        """
            Handles the context menu in the web view. 
            Shows and handle options (from field list), only if in edit mode.
        """

        if not (self._fields and self._selectionHandler):
            return self.createInfoMenu(evt)

        isLink = False
        value = None
        if self._web.selectedText():
            isLink = False
            value = self._web.selectedText()
        else:
            if (self._web.page().contextMenuData().mediaType()
                    == QWebEngineContextMenuData.MediaTypeImage
                    and self._web.page().contextMenuData().mediaUrl()):
                isLink = True
                value = self._web.page().contextMenuData().mediaUrl()
                Feedback.log('Link: ' + value.toString())
                Feedback.log('toLocal: ' + value.toLocalFile())

                if not self._checkSuffix(value):
                    return

        if not value:
            Feedback.log('No value')
            return self.createInfoMenu(evt)

        if QApplication.keyboardModifiers() == Qt.ControlModifier:
            if self._assignToLastField(value, isLink):
                return

        self.createCtxMenu(value, isLink, evt)

    def _checkSuffix(self, value):
        if value and not value.toString().endswith(
            ("jpg", "jpeg", "png", "gif")):
            msgLink = value.toString()
            if len(value.toString()) < 80:
                msgLink = msgLink[:50] + '...' + msgLink[50:]
            answ = QMessageBox.question(
                self, 'Anki support',
                """This link may not be accepted by Anki: \n\n "%s" \n
Usually the suffix should be one of 
(jpg, jpeg, png, gif).
Try it anyway? """ % msgLink, QMessageBox.Yes | QMessageBox.No)

            if answ != QMessageBox.Yes:
                return False

        return True

    def createCtxMenu(self, value, isLink, evt):
        'Creates and configures the menu itself'

        m = QMenu(self)
        m.addAction(QAction('Copy', m, triggered=lambda: self._copy(value)))
        m.addSeparator()

        labelAct = QAction(Label.BROWSER_ASSIGN_TO, m)
        labelAct.setDisabled(True)
        m.addAction(labelAct)
        # sub = QMenu(Label.BROWSER_ASSIGN_TO, m)
        m.setTitle(Label.BROWSER_ASSIGN_TO)
        for index, label in self._fields.items():
            act = QAction(label,
                          m,
                          triggered=self._makeMenuAction(index, value, isLink))
            m.addAction(act)

        # m.addMenu(sub)
        action = m.exec_(self.mapToGlobal(evt.pos()))

    def createInfoMenu(self, evt):
        'Creates and configures a menu with only some information'
        m = QMenu(self)
        for item in self.infoList:
            act = QAction(item)
            act.setEnabled(False)
            m.addAction(act)
        action = m.exec_(self.mapToGlobal(evt.pos()))

    def _assignToLastField(self, value, isLink):
        'Tries to set the new value to the same field used before, if set...'

        if self._lastAssignedField:
            if self._lastAssignedField in self._fields:
                self._selectionHandler(self._lastAssignedField, value, isLink)
                return True
            else:
                self._lastAssignedField = None
        return False

    def _copy(self, value):
        if not value:
            return
        clip = QApplication.clipboard()
        clip.setText(value if isinstance(value, str) else value.toString())

    def load(self, qUrl):
        self._web.load(qUrl)

#   ----------------- getter / setter  -------------------

    def setFields(self, fList):
        self._fields = fList

    def setSelectionHandler(self, value):
        self._selectionHandler = value
コード例 #3
0
class MainWindow(QtWidgets.QMainWindow):
    """Main application window."""

    def __init__(self, model_editor):
        """Initialize the class."""
        super(MainWindow, self).__init__()

        self.setMinimumSize(960, 660)

        self._hsplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal, self)
        self._model_editor = model_editor
        self.setCentralWidget(self._hsplitter)

        # tabs
        self._tab = QtWidgets.QTabWidget(self._hsplitter)
        self._tab.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        self._tab.setMinimumSize(600, 200)
        self._tab.sizeHint = lambda: QtCore.QSize(700, 250)

        self.info = QWebEngineView(self._tab)
        self.info.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.info_page = panels.InfoPanelPage()
        self.info.setPage(self.info_page)

        """info panel"""
        self.err = panels.ErrorWidget(self._tab)
        """error message panel"""
        self._tab.addTab(self.info, "Structure Info")
        self._tab.addTab(self.err, "Messages")

        # debug panel
        if cfg.config.DEBUG_MODE:
            self.debug_tab = panels.DebugPanelWidget(self._tab)
            self._tab.addTab(self.debug_tab, "Debug")

        # splitters
        self._vsplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical, self._hsplitter)
        self.editor = panels.YamlEditorWidget(self._vsplitter)
        """main editor component"""
        self.tree = panels.TreeWidget(self._vsplitter)
        """tree data display widget"""
        self._vsplitter.addWidget(self.editor)
        self._vsplitter.addWidget(self._tab)
        self._hsplitter.insertWidget(0, self.tree)

        # Menu bar
        self._menu = self.menuBar()
        self._file_menu = MainFileMenu(self, self._model_editor)
        self.update_recent_files(0)
        self._edit_menu = MainEditMenu(self, self.editor)
        self._settings_menu = MainSettingsMenu(self, self._model_editor)
        self._menu.addMenu(self._file_menu)
        self._menu.addMenu(self._edit_menu)
        self._menu.addMenu(self._settings_menu)

        # status bar
        self._column = QtWidgets.QLabel(self)
        self._column.setFrameStyle(QtWidgets.QFrame.StyledPanel)

        self._reload_icon = QtWidgets.QLabel(self)
        self._reload_icon.setPixmap(icon.get_pixmap("refresh", 16))
        self._reload_icon.setVisible(False)
        self._reload_icon_timer = QtCore.QTimer(self)
        self._reload_icon_timer.timeout.connect(lambda: self._reload_icon.setVisible(False))

        cfg.config.observers.append(self)

        self._status = self.statusBar()
        self._status.addPermanentWidget(self._reload_icon)
        self._status.addPermanentWidget(self._column)
        self.setStatusBar(self._status)
        self._status.showMessage("Ready", 5000)

        # signals
        self.err.itemSelected.connect(self._item_selected)
        self.tree.itemSelected.connect(self._item_selected)
        self.editor.nodeChanged.connect(self._reload_node)
        self.editor.cursorChanged.connect(self._cursor_changed)
        self.editor.structureChanged.connect(self._structure_changed)
        self.editor.errorMarginClicked.connect(self._error_margin_clicked)
        self.editor.elementChanged.connect(lambda new, old: self._update_info(new))
        self.editor.nodeSelected.connect(self._on_node_selected)

        # initialize components
        self._update_info(None)
        self.config_changed()

        # set focus
        self.editor.setFocus()

    def keyPressEvent(self, event):
        if event.matches(QKeySequence.Copy) and self.info.selectedText() != "":
            QtWidgets.QApplication.clipboard().setText(self.info.selectedText())
        else:
            super().keyReleaseEvent(event)

    def reload(self):
        """reload panels after structure changes"""
        self._reload_icon.setVisible(True)
        self._reload_icon.update()
        self.editor.setUpdatesEnabled(False)
        cfg.update()
        self.editor.setUpdatesEnabled(True)
        self.editor.reload()
        self.tree.reload()
        self.err.reload()
        line, index = self.editor.getCursorPosition()
        self._reload_node(line+1, index+1)
        self._reload_icon_timer.start(700)

    def show_status_message(self, message, duration=5000):
        """Show a message in status bar for the given duration (in ms)."""
        self._status.showMessage(message, duration)

    def update_recent_files(self, from_row=1):
        """Update recently opened files."""
        self._file_menu.update_recent_files(from_row)

    def _item_selected(self, start_line, start_column, end_line, end_column):
        """Handle when an item is selected from tree or error tab.

        :param int start_line: line where the selection starts
        :param int start_column: column where the selection starts
        :param int end_line: line where the selection ends
        :param int end_column: column where the selection ends
        """
        self.editor.setFocus()

        # remove empty line and whitespaces at the end of selection
        if end_line > start_line and end_line > 1:
            last_line_text = self.editor.text(end_line-1)
            if end_column > len(last_line_text):
                end_column = len(last_line_text) + 1
            last_line_text_selected = last_line_text[:end_column-1]
            if LineAnalyzer.is_empty(last_line_text_selected):
                end_line -= 1
                end_line_text = self.editor.text(end_line-1)
                end_column = len(end_line_text)

        # select in reversed order - move cursor to the beginning of selection
        self.editor.mark_selected(end_line, end_column, start_line, start_column)

    def _reload_node(self, line, index):
        """reload info after changing node selection"""
        node = cfg.get_data_node(Position(line, index))
        self.editor.set_new_node(node)
        cursor_type = self.editor.cursor_type_position
        self._update_info(cursor_type)
        if cfg.config.DEBUG_MODE:
            self.debug_tab.show_data_node(node)

    def _cursor_changed(self, line, column):
        """Editor node change signal"""
        self._column.setText("Line: {:5d}  Pos: {:3d}".format(line, column))

    def _structure_changed(self, line, column):
        """Editor structure change signal"""
        if cfg.update_yaml_file(self.editor.text()):
            self.reload()
        else:
            self._reload_node(line, column)

    def _error_margin_clicked(self, line):
        """Click error icon in margin"""
        self._tab.setCurrentIndex(self._tab.indexOf(self.err))
        self.err.select_error(line)

    def _update_info(self, cursor_type):
        """Update the info panel."""
        if self.editor.pred_parent is not None:
            self.info_page.update_from_node(self.editor.pred_parent,
                                       CursorType.parent.value)
            return
        if self.editor.curr_node is not None:
            self.info_page.update_from_node(self.editor.curr_node, cursor_type)
            return

        # show root input type info by default
        self.info_page.update_from_data({'record_id': cfg.root_input_type['id']}, True)
        return

    def _on_node_selected(self, line, column):
        """Handles nodeSelected event from editor."""
        node = cfg.get_data_node(Position(line, column))
        self.tree.select_data_node(node)

    def closeEvent(self, event):
        """Performs actions before app is closed."""
        # prompt user to save changes (if any)
        if not self._model_editor.save_old_file():
            return event.ignore()

        super(MainWindow, self).closeEvent(event)

    def config_changed(self):
        """Handle changes of config."""
        self.editor.set_line_endings(cfg.config.line_endings)
コード例 #4
0
class MainWindow(BaseWindow):
    def __init__(self):
        super().__init__()
        self.qObject = QObject()
        self.initConsole()
        self.initUI()
        self.show()

    def initConsole(self):
        self.consoleWindow = ButtomWindow()

    def initUI(self):
        self.resize(int(Utils.getWindowWidth() * 0.8),
                    int(Utils.getWindowHeight() * 0.8))
        self.initMenuBar()
        self.initLayout()
        super().initWindow()

    def initMenuBar(self):
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu('File')
        disConnectAction = QAction(IconTool.buildQIcon('disconnect.png'),
                                   '&Disconnect', self)
        disConnectAction.setShortcut('Ctrl+D')
        disConnectAction.setShortcutContext(Qt.ApplicationShortcut)
        disConnectAction.triggered.connect(self.restartProgram)
        settingAction = QAction(IconTool.buildQIcon('setting.png'),
                                '&Setting...', self)
        settingAction.setShortcut('Ctrl+Shift+S')
        settingAction.setShortcutContext(Qt.ApplicationShortcut)
        settingAction.triggered.connect(lambda: STCLogger().d('setting'))
        clearCacheAction = QAction(IconTool.buildQIcon('clearCache.png'),
                                   '&ClearCache', self)
        clearCacheAction.setShortcut('Ctrl+Alt+C')
        clearCacheAction.setShortcutContext(Qt.ApplicationShortcut)
        clearCacheAction.triggered.connect(self.clearCache)
        clearSearchHistoryAction = QAction(
            IconTool.buildQIcon('clearCache.png'), '&ClearSearchHistory', self)
        clearSearchHistoryAction.triggered.connect(self.clearSearchHistory)
        settingLogPathAction = QAction(IconTool.buildQIcon('path.png'),
                                       '&LogPath', self)
        settingLogPathAction.triggered.connect(self.setLogPath)
        fileMenu.addAction(disConnectAction)
        fileMenu.addAction(clearCacheAction)
        fileMenu.addAction(settingLogPathAction)
        fileMenu.addAction(clearSearchHistoryAction)
        # fileMenu.addAction(settingAction)
        # fileMenu.addAction(showLogAction)

        # settingAction.triggered.connect(self.openSettingWindow)

        editMenu = menuBar.addMenu('Edit')
        findAction = QAction(IconTool.buildQIcon('find.png'), '&Find', self)
        findAction.setShortcut('Ctrl+F')
        findAction.triggered.connect(self.findActionClick)
        editMenu.addAction(findAction)

        focusItemAction = QAction(IconTool.buildQIcon('focus.png'),
                                  '&Focus Item', self)
        focusItemAction.triggered.connect(self.focusChooseItem)
        editMenu.addAction(focusItemAction)
        self.chooseItemType = ''
        self.chooseItemId = ''

        settingMenu = menuBar.addMenu('Setting')
        autoLoginAction = QAction(IconTool.buildQIcon('setting.png'),
                                  '&Auto Login', self)
        autoLoginAction.setShortcutContext(Qt.ApplicationShortcut)
        autoLoginAction.triggered.connect(self.setAutoState)
        settingMenu.addAction(autoLoginAction)

        helpMenu = menuBar.addMenu('Help')
        aboutAction = QAction(IconTool.buildQIcon('about.png'), '&About', self)
        aboutAction.triggered.connect(self.openAboutWindow)
        helpMenu.addAction(aboutAction)

    def closeEvent(self, e):
        self.settings.setValue('searchHistory', self.searchHistory)
        e.accept()

    def openAboutWindow(self):
        print('open about')
        self.aboutWindow = AboutWindow()
        self.aboutWindow.show()

    def setAutoState(self):
        reply = QMessageBox.question(self, "提示", "是否取消自动登录功能",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            Utils.setAutoLoginState(False)

    def restartProgram(self):
        from XulDebugTool.ui.ConnectWindow import ConnectWindow  # 不应该在这里导入,但是放在前面会有问题
        try:
            hasAbout = object.__getattribute__(self, "aboutWindow")
        except:
            hasAbout = None
        if hasAbout is not None:
            self.aboutWindow.close()
        print("新建连接页面")
        self.con = ConnectWindow()
        self.close()

    # def openSettingWindow(self):
    #     self.tableInfoModel = SettingWindow()
    #     self.tableInfoModel.show()

    def initLayout(self):
        # ----------------------------left layout---------------------------- #
        self.treeModel = QStandardItemModel()
        self.pageItem = QStandardItem(ROOT_ITEM_PAGE)
        self.pageItem.type = ITEM_TYPE_PAGE_ROOT
        self.buildPageItem()
        self.userobjectItem = QStandardItem(ROOT_ITEM_USER_OBJECT)
        self.userobjectItem.type = ITEM_TYPE_USER_OBJECT_ROOT
        self.buildUserObjectItem()
        self.providerRequestItem = QStandardItem(ROOT_ITEM_PROVIDER_REQUESTS)
        self.providerRequestItem.type = ITEM_TYPE_PROVIDER_REQUESTS_ROOT
        self.pluginItem = QStandardItem(ROOT_ITEM_PLUGIN)
        self.pluginItem.type = ITEM_TYPE_PLUGIN_ROOT
        self.treeModel.appendColumn([
            self.pageItem, self.userobjectItem, self.providerRequestItem,
            self.pluginItem
        ])
        self.treeModel.setHeaderData(0, Qt.Horizontal, 'Model')

        self.treeView = QTreeView()
        self.treeView.setModel(self.treeModel)
        self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(self.openContextMenu)
        self.treeView.doubleClicked.connect(self.onTreeItemDoubleClicked)
        self.treeView.clicked.connect(self.getDebugData)

        leftContainer = QWidget()
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 6, 0)  # left, top, right, bottom
        layout.addWidget(self.treeView)
        leftContainer.setLayout(layout)

        # ----------------------------middle layout---------------------------- #
        middleContainer = QWidget()

        # search shall start not before the user completed typing
        # filter_delay = DelayedExecutionTimer(self)
        # new_column.search_bar.textEdited[str].connect(filter_delay.trigger)
        # filter_delay.triggered[str].connect(self.search)

        self.tabBar = QTabBar()
        self.tabBar.setUsesScrollButtons(False)
        self.tabBar.setDrawBase(False)
        # self.tabBar.addTab('tab1')
        # self.tabBar.addTab('tab2')

        self.pathBar = QWidget()
        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignLeft)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)
        self.pathBar.setLayout(layout)

        self.searchHolder = QWidget()
        layout = QHBoxLayout()
        layout.addWidget(self.tabBar)
        layout.addWidget(self.pathBar)
        layout.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Expanding))
        self.searchHolder.setLayout(layout)
        self.searchHolder.layout().setContentsMargins(6, 6, 6, 0)

        self.tabContentWidget = QWidget()
        self.browser = QWebEngineView()
        self.browser.setZoomFactor(1.3)
        self.channel = QWebChannel()
        self.webObject = WebShareObject()
        self.channel.registerObject('bridge', self.webObject)
        self.browser.page().setWebChannel(self.channel)
        self.webObject.jsCallback.connect(lambda value: self.addUpdate(value))

        qwebchannel_js = QFile(':/qtwebchannel/qwebchannel.js')
        if not qwebchannel_js.open(QIODevice.ReadOnly):
            raise SystemExit('Failed to load qwebchannel.js with error: %s' %
                             qwebchannel_js.errorString())
        qwebchannel_js = bytes(qwebchannel_js.readAll()).decode('utf-8')

        script = QWebEngineScript()
        script.setSourceCode(qwebchannel_js)
        script.setInjectionPoint(QWebEngineScript.DocumentCreation)
        script.setName('qtwebchannel.js')
        script.setWorldId(QWebEngineScript.MainWorld)
        script.setRunsOnSubFrames(True)
        self.browser.page().scripts().insert(script)

        Utils.scriptCreator(os.path.join('..', 'resources', 'js', 'event.js'),
                            'event.js', self.browser.page())
        self.browser.page().setWebChannel(self.channel)

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.initQCheckBoxUI())
        layout.addWidget(self.initSearchView())
        layout.addWidget(self.browser)
        self.tabContentWidget.setLayout(layout)
        self.searchWidget.hide()

        middleContainer.stackedWidget = QStackedWidget()
        self.url = XulDebugServerHelper.HOST + 'list-pages'
        self.showXulDebugData(self.url)
        middleContainer.stackedWidget.addWidget(self.tabContentWidget)
        middleContainer.stackedWidget.addWidget(QLabel('tab2 content'))

        self.tabBar.currentChanged.connect(
            lambda: middleContainer.stackedWidget.setCurrentIndex(
                self.tabBar.currentIndex()))

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.searchHolder)
        layout.addWidget(middleContainer.stackedWidget)
        middleContainer.setLayout(layout)

        # ----------------------------right layout---------------------------- #
        self.rightSiderClickInfo = 'Property'

        self.rightSiderTabWidget = QTabWidget()
        self.rightSiderTabBar = QTabBar()
        self.rightSiderTabWidget.setTabBar(self.rightSiderTabBar)
        self.rightSiderTabWidget.setTabPosition(QTabWidget.East)
        self.favoriteTreeView = FavoriteTreeView(self)

        # self.propertyEditor = PropertyEditor(['Key', 'Value'])
        self.inputWidget = UpdateProperty()
        self.rightSiderTabWidget.addTab(self.inputWidget,
                                        IconTool.buildQIcon('property.png'),
                                        'Property')

        self.rightSiderTabWidget.setStyleSheet(
            ('QTab::tab{height:60px;width:32px;color:black;padding:0px}'
             'QTabBar::tab:selected{background:lightgray}'))

        # self.rightSiderTabWidget.addTab(self.propertyEditor,IconTool.buildQIcon('property.png'),'property')
        self.rightSiderTabWidget.addTab(self.favoriteTreeView,
                                        IconTool.buildQIcon('favorites.png'),
                                        'Favorites')
        self.rightSiderTabBar.tabBarClicked.connect(self.rightSiderClick)

        # ----------------------------entire layout---------------------------- #

        self.contentSplitter = QSplitter(Qt.Horizontal)
        self.contentSplitter.setHandleWidth(0)  # thing to grab the splitter

        self.contentSplitter.addWidget(leftContainer)
        self.contentSplitter.addWidget(middleContainer)
        self.contentSplitter.addWidget(self.rightSiderTabWidget)
        self.contentSplitter.setStretchFactor(0, 0)
        self.contentSplitter.setStretchFactor(1, 6)
        self.contentSplitter.setStretchFactor(2, 6)

        self.mainSplitter = QSplitter(Qt.Vertical)
        self.mainSplitter.setHandleWidth(0)

        self.mainSplitter.addWidget(self.contentSplitter)
        self.mainSplitter.addWidget(self.consoleWindow)
        self.mainSplitter.setStretchFactor(1, 0)
        self.mainSplitter.setStretchFactor(2, 1)
        self.setCentralWidget(self.mainSplitter)
        # 默认隐藏掉复选框
        self.groupBox.setHidden(True)

    def addUpdate(self, value=None):
        self.inputWidget.initData(self.pageId, value)
        self.inputWidget.updateItemUI()
        dict = json.loads(value)
        if dict['action'] == "click":
            self.chooseItemId = dict['Id']
            self.chooseItemType = Utils.findNodeById(dict['Id'],
                                                     dict['xml']).tag
        elif dict['action'] == "load":
            self.browser.load(QUrl(dict['url']))
        else:
            pass

    def focusChooseItem(self):
        if self.chooseItemType in ('area', 'item'):
            XulDebugServerHelper.focusChooseItemUrl(self.chooseItemId)

    def initQCheckBoxUI(self):
        self.groupBox = QGroupBox()
        self.skipPropCheckBox = QCheckBox(SKIP_PROP, self)
        self.skipPropCheckBox.setChecked(False)
        self.skipPropCheckBox.stateChanged.connect(
            lambda: self.clickCheckBox(self.skipPropCheckBox, SKIP_PROP))

        self.withChildrenCheckBox = QCheckBox(WITH_CHILDREN, self)
        self.withChildrenCheckBox.setChecked(False)
        self.withChildrenCheckBox.stateChanged.connect(
            lambda: self.clickCheckBox(self.withChildrenCheckBox, WITH_CHILDREN
                                       ))

        self.withBindingDataCheckBox = QCheckBox(WITH_BINDING_DATA, self)
        self.withBindingDataCheckBox.setChecked(False)
        self.withBindingDataCheckBox.stateChanged.connect(
            lambda: self.clickCheckBox(self.withBindingDataCheckBox,
                                       WITH_BINDING_DATA))

        self.withPositionCheckBox = QCheckBox(WITH_POSITION, self)
        self.withPositionCheckBox.setChecked(False)
        self.withPositionCheckBox.stateChanged.connect(
            lambda: self.clickCheckBox(self.withPositionCheckBox, WITH_POSITION
                                       ))

        self.withSelectorCheckBox = QCheckBox(WITH_SELECTOR, self)
        self.withSelectorCheckBox.setChecked(False)
        self.withSelectorCheckBox.stateChanged.connect(
            lambda: self.clickCheckBox(self.withSelectorCheckBox, WITH_SELECTOR
                                       ))

        checkGrouplayout = QHBoxLayout()
        checkGrouplayout.addWidget(self.skipPropCheckBox)
        checkGrouplayout.addWidget(self.withChildrenCheckBox)
        checkGrouplayout.addWidget(self.withBindingDataCheckBox)
        checkGrouplayout.addWidget(self.withPositionCheckBox)
        checkGrouplayout.addWidget(self.withSelectorCheckBox)
        self.groupBox.setLayout(checkGrouplayout)
        return self.groupBox

    def initSearchView(self):
        self.settings = QSettings('XulDebugTool')
        self.searchHistory = self.settings.value('searchHistory', [])

        self.searchWidget = QWidget()
        self.searchWidget.setStyleSheet(
            ".QWidget{border:1px solid rgb(220, 220, 220)}")
        searchPageLayout = QHBoxLayout()
        self.searchLineEdit = QLineEdit()

        self.searchIcon = QAction(self)
        self.searchIcon.setIcon(IconTool.buildQIcon('find_h.png'))

        self.searchMenu = QMenu(self)
        for text in self.searchHistory:
            self.action = QAction(
                text,
                self,
                triggered=lambda: self.searchLineEdit.setText(text))
            self.searchMenu.addAction(self.action)
        self.searchIcon.setMenu(self.searchMenu)

        self.searchDelIcon = QAction(self)
        self.searchDelIcon.setIcon(IconTool.buildQIcon('del.png'))

        self.searchLineEdit.addAction(self.searchIcon,
                                      QLineEdit.LeadingPosition)
        self.searchLineEdit.addAction(self.searchDelIcon,
                                      QLineEdit.TrailingPosition)
        self.searchDelIcon.setVisible(False)
        self.searchLineEdit.setStyleSheet(
            "border:2px groove gray;border-radius:10px;padding:2px 4px")

        searchPageLayout.addWidget(self.searchLineEdit)
        self.searchLineEdit.textChanged.connect(self.searchPage)
        self.searchLineEdit.editingFinished.connect(self.saveSearchHistory)
        self.searchDelIcon.triggered.connect(self.searchDelClick)
        self.previousBtn = QPushButton()
        self.previousBtn.setStyleSheet("background:transparent;")
        self.previousBtn.setIcon(IconTool.buildQIcon('up.png'))
        self.previousBtn.setFixedSize(15, 20)
        searchPageLayout.addWidget(self.previousBtn)
        self.previousBtn.clicked.connect(
            lambda: self.previousBtnClick(self.searchLineEdit.text()))
        self.nextBtn = QPushButton()
        self.nextBtn.setIcon(IconTool.buildQIcon('down.png'))
        self.nextBtn.setStyleSheet("background:transparent;")
        self.nextBtn.setFixedSize(15, 20)
        self.nextBtn.clicked.connect(
            lambda: self.nextBtnClick(self.searchLineEdit.text()))
        searchPageLayout.addWidget(self.nextBtn)
        self.matchCase = QCheckBox("Match Case")
        self.matchCase.setChecked(False)
        self.matchCase.stateChanged.connect(self.matchCaseChange)
        searchPageLayout.addWidget(self.matchCase)

        self.matchTips = QLabel()
        self.matchTips.setFixedWidth(100)
        self.searchClose = QPushButton("×")
        self.searchClose.setFixedWidth(10)
        self.searchClose.setStyleSheet("background:transparent;")
        self.searchClose.clicked.connect(self.searchCloseClick)
        searchPageLayout.addWidget(self.matchTips)
        searchPageLayout.addWidget(self.searchClose)
        self.searchWidget.setLayout(searchPageLayout)
        return self.searchWidget

    def clickCheckBox(self, checkBox, name):
        if checkBox.isChecked():
            STCLogger().i('select ' + name)
            self.selectCheckBoxInfo(name)
        else:
            STCLogger().i('cancel ' + name)
            self.cancelCheckBoxInfo(name)

    def selectCheckBoxInfo(self, str):
        if None != self.url:
            checkedStr = str + '=' + 'true'
            unCheckedStr = str + '=' + 'false'
            if self.url.find('?') == -1:
                self.url += '?'
                self.url += checkedStr
            else:
                if self.url.find(str) == -1:
                    self.url += '&'
                    self.url += checkedStr
                elif self.url.find(unCheckedStr) != -1:
                    self.url = self.url.replace(unCheckedStr, checkedStr)
            self.showXulDebugData(self.url)

    def cancelCheckBoxInfo(self, str):
        if None != self.url:
            checkedStr = str + '=' + 'true'
            if self.url.find(checkedStr) >= -1:
                split = self.url.split(checkedStr)
                self.url = ''.join(split)
                self.url = self.url.replace('&&', '&')
                self.url = self.url.replace('?&', '?')
                if self.url.endswith('?'):
                    self.url = self.url[:-1]
                if self.url.endswith('&'):
                    self.url = self.url[:-1]
                self.showXulDebugData(self.url)

    def rightSiderClick(self, index):
        # 两次单击同一个tabBar时显示隐藏内容区域
        if self.rightSiderTabBar.tabText(index) == self.rightSiderClickInfo:
            if self.rightSiderTabWidget.width() == Utils.getItemHeight():
                self.rightSiderTabWidget.setMaximumWidth(
                    Utils.getWindowWidth())
                self.rightSiderTabWidget.setMinimumWidth(Utils.getItemHeight())
            else:
                self.rightSiderTabWidget.setFixedWidth(Utils.getItemHeight())
        else:
            if self.rightSiderTabWidget.width() == Utils.getItemHeight():
                self.rightSiderTabWidget.setMaximumWidth(
                    Utils.getWindowWidth())
                self.rightSiderTabWidget.setMinimumWidth(Utils.getItemHeight())
        self.rightSiderClickInfo = self.rightSiderTabBar.tabText(index)

    def clearCache(self):
        r = XulDebugServerHelper.clearAllCaches()
        if r.status == 200:
            self.statusBar().showMessage('cache cleanup success')
        else:
            self.statusBar().showMessage('cache cleanup failed')

    def setLogPath(self):
        file_path = QFileDialog.getSaveFileName(self, 'save file',
                                                ConfigHelper.LOGCATPATH,
                                                "Txt files(*.txt)")
        if len(file_path[0]) > 0:
            ConfigurationDB.saveConfiguration(ConfigHelper.KEY_LOGCATPATH,
                                              file_path[0])

    @pyqtSlot(QPoint)
    def openContextMenu(self, point):
        index = self.treeView.indexAt(point)
        if not index.isValid():
            return
        item = self.treeModel.itemFromIndex(index)
        menu = QMenu()

        if item.type == ITEM_TYPE_PROVIDER:
            queryAction = QAction(
                IconTool.buildQIcon('data.png'),
                '&Query Data...',
                self,
                triggered=lambda: self.showQueryDialog(item.data))
            queryAction.setShortcut('Alt+Q')
            menu.addAction(queryAction)

        copyAction = QAction(
            IconTool.buildQIcon('copy.png'),
            '&Copy',
            self,
            triggered=lambda: pyperclip.copy('%s' % index.data()))
        copyAction.setShortcut(QKeySequence.Copy)
        menu.addAction(copyAction)
        menu.exec_(self.treeView.viewport().mapToGlobal(point))

    @pyqtSlot(QModelIndex)
    def getDebugData(self, index):
        item = self.treeModel.itemFromIndex(index)

        if item.type == ITEM_TYPE_PAGE_ROOT:  # 树第一层,page节点
            self.buildPageItem()
            self.url = XulDebugServerHelper.HOST + 'list-pages'
            self.showXulDebugData(self.url)
        elif item.type == ITEM_TYPE_USER_OBJECT_ROOT:  # 树第一层,userObject节点
            self.buildUserObjectItem()
            self.url = XulDebugServerHelper.HOST + 'list-user-objects'
            self.showXulDebugData(self.url)
        elif item.type == ITEM_TYPE_PROVIDER_REQUESTS_ROOT:  # 树第一层,Provider Request节点
            self.url = XulDebugServerHelper.HOST + 'list-provider-requests'
            self.showXulDebugData(self.url)
        elif item.type == ITEM_TYPE_PLUGIN_ROOT:  # 树第一层,plugin节点
            pass
        elif item.type == ITEM_TYPE_PAGE:  # 树第二层,page下的子节点
            pageId = item.id
            self.url = XulDebugServerHelper.HOST + 'get-layout/' + pageId
            self.showXulDebugData(self.url)
        elif item.type == ITEM_TYPE_USER_OBJECT:  # 树第二层,userObject下的子节点
            objectId = item.id
            self.url = XulDebugServerHelper.HOST + 'get-user-object/' + objectId
            self.showXulDebugData(self.url)
        elif item.type == ITEM_TYPE_PROVIDER:  # 树第三层,userObject下的DataService下的子节点
            pass

        self.groupBox.setHidden(item.type != ITEM_TYPE_PAGE)
        # self.fillPropertyEditor(item.data)

    @pyqtSlot(QModelIndex)
    def onTreeItemDoubleClicked(self, index):
        item = self.treeModel.itemFromIndex(index)
        if item.type == ITEM_TYPE_PROVIDER:
            self.showQueryDialog(item.data)

    def buildPageItem(self):
        self.pageItem.removeRows(0, self.pageItem.rowCount())
        r = XulDebugServerHelper.listPages()
        if r:
            pagesNodes = Utils.xml2json(r.data, 'pages')
            if pagesNodes == '':
                return
            # 如果只有一个page,转化出来的json不是数据.分开处理
            if isinstance(pagesNodes['page'], list):
                for i, page in enumerate(pagesNodes['page']):
                    # 把page解析了以后放page节点下
                    row = QStandardItem(page['@pageId'])
                    row.id = self.pageId = page['@id']
                    row.data = page
                    row.type = ITEM_TYPE_PAGE
                    self.pageItem.appendRow(row)
            else:
                page = pagesNodes['page']
                row = QStandardItem(page['@pageId'])
                row.id = self.pageId = page['@id']
                row.data = page
                row.type = ITEM_TYPE_PAGE
                self.pageItem.appendRow(row)
            if self.pageItem.rowCount() > 0:
                self.pageItem.setText(
                    '%s(%s)' % (ROOT_ITEM_PAGE, self.pageItem.rowCount()))

    def buildUserObjectItem(self):
        self.userobjectItem.removeRows(0, self.userobjectItem.rowCount())
        r = XulDebugServerHelper.listUserObject()
        if r:
            userObjectNodes = Utils.xml2json(r.data, 'objects')
            # 如果只有一个userObject,转化出来的json不是数据.分开处理
            if userObjectNodes and isinstance(userObjectNodes['object'], list):
                for i, o in enumerate(userObjectNodes['object']):
                    # 把userObject加到User-Object节点下
                    row = QStandardItem(o['@name'])
                    row.id = o['@id']
                    row.data = o
                    row.type = ITEM_TYPE_USER_OBJECT
                    self.userobjectItem.appendRow(row)
                    # 如果是DataServcie, 填充所有的Provider到该节点下
                    if o['@name'] == CHILD_ITEM_DATA_SERVICE:
                        r = XulDebugServerHelper.getUserObject(o['@id'])
                        if r:
                            dataServiceNodes = Utils.xml2json(r.data, 'object')
                            if isinstance(
                                    dataServiceNodes['object']['provider'],
                                    list):
                                for j, provider in enumerate(
                                        dataServiceNodes['object']
                                    ['provider']):
                                    dsRow = QStandardItem(
                                        provider['ds']['@providerClass'])
                                    dsRow.id = provider['@name']
                                    dsRow.data = provider
                                    dsRow.type = ITEM_TYPE_PROVIDER
                                    row.appendRow(dsRow)
                            else:
                                provider = dataServiceNodes['object'][
                                    'provider']
                                dsRow = QStandardItem(
                                    provider['ds']['@providerClass'])
                                dsRow.id = provider['@name']
                                dsRow.data = provider
                                dsRow.type = ITEM_TYPE_PROVIDER
                                row.appendRow(dsRow)
                            # 对Provider按升序排序
                            row.sortChildren(0)
                    if row.rowCount() > 0:
                        row.setText('%s(%s)' % (row.text(), row.rowCount()))
            else:
                # 没有只有一个userObject的情况, 暂不处理
                pass
        if self.userobjectItem.rowCount() > 0:
            self.userobjectItem.setText(
                '%s(%s)' %
                (ROOT_ITEM_USER_OBJECT, self.userobjectItem.rowCount()))

    def showXulDebugData(self, url):
        STCLogger().i('request url:' + url)
        self.browser.load(QUrl(url))
        self.statusBar().showMessage(url)

    def convertProperty(self, k, v):
        """递归的将多层属性字典转成单层的."""
        if isinstance(v, dict):
            for subk, subv in v.items():
                self.convertProperty(subk, subv)
        else:
            setattr(self.qObject, k, v)

    def showQueryDialog(self, data):
        STCLogger().i('show query dialog: ', data)
        self.dialog = DataQueryDialog(data)
        self.dialog.finishSignal.connect(self.onGetQueryUrl)
        self.dialog.show()

    def onGetQueryUrl(self, url):
        STCLogger().i('request url:' + url)
        self.favoriteTreeView.updateTree()
        self.browser.load(QUrl(url))
        self.statusBar().showMessage(url)

    def findActionClick(self):
        self.searchWidget.show()
        self.searchLineEdit.setFocus()
        self.searchLineEdit.setText(self.browser.selectedText())

    def searchPage(self, text):
        if not text.strip():
            self.searchDelIcon.setVisible(False)
        else:
            self.searchDelIcon.setVisible(True)
        check = self.matchCase.isChecked()
        if check:
            self.browser.findText(text, QWebEnginePage.FindFlags(2),
                                  lambda result: self.changeMatchTip(result))
        else:
            self.browser.findText(text, QWebEnginePage.FindFlags(0),
                                  lambda result: self.changeMatchTip(result))

    def saveSearchHistory(self):
        text = self.searchLineEdit.text()
        if text != '' and text not in self.searchHistory:
            self.action = QAction(
                text,
                self,
                triggered=lambda: self.searchLineEdit.setText(text))
            self.searchMenu.addAction(self.action)
            self.searchHistory.append(text)

    def clearSearchHistory(self):
        self.searchHistory = []

    def previousBtnClick(self, text):
        check = self.matchCase.isChecked()
        if check:
            self.browser.findText(
                text,
                QWebEnginePage.FindFlags(1) | QWebEnginePage.FindFlags(2),
                lambda result: self.changeMatchTip(result))
        else:
            self.browser.findText(text, QWebEnginePage.FindFlags(1),
                                  lambda result: self.changeMatchTip(result))

    def nextBtnClick(self, text):
        check = self.matchCase.isChecked()
        if check:
            self.browser.findText(text, QWebEnginePage.FindFlags(2),
                                  lambda result: self.changeMatchTip(result))
        else:
            self.browser.findText(text, QWebEnginePage.FindFlags(0),
                                  lambda result: self.changeMatchTip(result))

    def matchCaseChange(self):
        self.browser.findText("")
        self.searchPage(self.searchLineEdit.text())

    def changeMatchTip(self, result):
        if result:
            self.matchTips.setText("Find matches")
        else:
            self.matchTips.setText("No matches")

    def searchDelClick(self):
        self.searchLineEdit.setText("")
        self.browser.findText("")
        self.matchTips.setText("")

    def searchCloseClick(self):
        self.searchLineEdit.setText("")
        self.browser.findText("")
        self.matchTips.setText("")
        self.searchWidget.hide()