def _on_show_detail(self): dlg = QTextEdit(self) dlg.setReadOnly(True) geo = QApplication.desktop().screenGeometry() dlg.setGeometry(geo.width() / 2, geo.height() / 2, geo.width() / 4, geo.height() / 4) dlg.setWindowTitle('Error Detail Information') dlg.setText(self.property('history')) dlg.setWindowFlags(Qt.Dialog) dlg.show()
class PythonCompleter(QCompleter, object): def __init__(self): super(PythonCompleter, self).__init__() self._info = None self._file_path = None self._model_strings = list() self._reset_list = True self._string_model = QStringListModel(self._model_strings, self) self._refresh_completer = True self._sub_activated = False self._last_imports = None self._last_lines = None self._last_path = None self._current_defined_imports = None self._last_path_and_part = None self._current_sub_functions = None self._last_column = 0 self.setCompletionMode(self.PopupCompletion) self.setCaseSensitivity(Qt.CaseInsensitive) self.setModel(self._string_model) self.setWrapAround(False) self.activated.connect(self._on_insert_completion) def setWidget(self, widget): super(PythonCompleter, self).setWidget(widget) self.setParent(widget) def keyPressEvent(self): return def show_info_popup(self, info=None): self._info = QTextEdit() self._info.setEnabled(False) self._info.setWindowFlags(Qt.Popup) self._info.show() def get_imports(self, paths=None): imports = self._get_available_modules(paths=paths) imports.sort() return imports def get_sub_imports(self, path): """ Returns namespace in a module :param path: str :return: str """ defined = code.get_defined(path) defined.sort() return defined def clear_completer_list(self): self._string_model.setStringList([]) def text_under_cursor(self): cursor = self.widget().textCursor() cursor.select(cursor.LineUnderCursor) return cursor.selectedText() def set_filepath(self, file_path): if not file_path: return self._file_path = file_path def handle_text(self, text): """ Parses a single line of text :param text: str :return: bool """ if not text: return False cursor = self.widget().textCursor() column = cursor.columnNumber() - 1 if column < self._last_column: self._last_column = column return False self._last_column = column if column == 1: return False text = str(text) passed = self.handle_from_import(text, column) if passed: return True passed = self.handle_sub_import(text, column) if passed: return True passed = self.handle_import_load(text, cursor) if passed: return True return False def handle_import(self, text): m = re.search(r'(from|import)(?:\s+?)(\w*)', text) if m: # TODO: Find available modules in Python path pass def handle_sub_import(self, text, column): m = re.search(r'(from|import)(?:\s+?)(\w*.?\w*)\.(\w*)$', text) if m: if column < m.end(2): return False from_module = m.group(2) module_path = code.get_package_path_from_name(from_module) last_part = m.group(3) if module_path: defined = self.get_imports(module_path) self._string_model.setStringList(defined) self.setCompletionPrefix(last_part) self.popup().setCurrentIndex(self.completionModel().index( 0, 0)) return True return False def handle_import_load(self, text, cursor): column = cursor.columnNumber() - 1 text = text[:cursor.columnNumber()] m = re.search(r'\s*([a-zA-Z0-9._]+)\.([a-zA-Z0-9_]*)$', text) block_number = cursor.blockNumber() line_number = block_number + 1 all_text = self.widget().toPlainText() scope_text = all_text[:(cursor.position() - 1)] if m and m.group(2): scope_text = all_text[:(cursor.position() - len(m.group(2)) + 1)] if not m: return False assignment = m.group(1) if column < m.end(1): return False sub_m = re.search(r'(from|import)\s+(%s)' % assignment, text) if sub_m: return False path = None sub_part = None target = None text = self.widget().toPlainText() lines = fileio.get_text_lines(text) # Search for assignments assign_map = code.get_ast_assignment(scope_text, line_number - 1, assignment) if assign_map: if assignment in assign_map: target = assign_map[assignment] else: split_assignment = assignment.split('.') inc = 1 while assignment not in assign_map: sub_assignment = string.join(split_assignment[:(inc * -1)], '.') if sub_assignment in assign_map: target = assign_map[sub_assignment] break inc += 1 if inc > (len(split_assignment) - 1): break sub_part = string.join(split_assignment[inc:], '.') module_name = m.group(1) if target and len(target) == 2: if target[0] == 'import': module_name = target[1] if not target[0] == 'import': module_name = target[0] sub_part = target[1] # import from module if module_name: imports = None if lines == self.last_lines: imports = self.last_imports if not imports: imports = code.get_line_imports(lines) self._last_imports = imports self._last_lines = lines if module_name in imports: path = imports[module_name] if module_name not in imports: split_assignment = module_name.split('.') last_part = split_assignment[-1] if last_part in imports: path = imports[last_part] if path and not sub_part: test_text = '' defined = None if path == self.last_path: defined = self.current_defined_imports if len(m.groups()) > 0: test_text = m.group(2) if not defined: defined = self.get_imports(path) if defined: self._current_defined_imports = defined else: defined = self.get_sub_imports(path) custom_defined = self.custom_import_load( assign_map, module_name) if custom_defined: defined = custom_defined if defined: if test_text and test_text[0].islower(): defined.sort(key=str.swapcase) self._string_model.setStringList(defined) self.setCompletionPrefix(test_text) self.setCaseSensitivity(Qt.CaseInsensitive) self.popup().setCurrentIndex(self.completionModel().index( 0, 0)) return True # import from a class of a module if path and sub_part: sub_functions = None if self.last_path_and_part: if path == self.last_path_and_part[ 0] and sub_part == self.last_path_and_part[1]: sub_functions = self.current_sub_functions if not sub_functions: sub_functions = code.get_ast_class_sub_functions( path, sub_part) if sub_functions: self._current_sub_functions = sub_functions self._last_path_and_part = [path, sub_part] if not sub_functions: return False test_text = '' if len(m.groups()) > 0: test_text = m.group(2) if test_text and test_text[0].islower(): sub_functions.sort(key=str.swapcase) self._string_model.setStringList(sub_functions) self.setCompletionPrefix(test_text) self.setCaseSensitivity(Qt.CaseInsensitive) self.popup().setCurrentIndex(self.completionModel().index( 0, 0)) return True module_name = m.group(1) if module_name: custom_defined = self.custom_import_load(assign_map, module_name) test_text = '' if len(m.groups()) > 0: test_text = m.group(2) if test_text and test_text[0].islower(): custom_defined.sort(key=str.swapcase) self._string_model.setStringList(custom_defined) self.setCompletionPrefix(test_text) self.setCaseSensitivity(Qt.CaseInsensitive) self.popup().setCurrentIndex(self.completionModel().index(0, 0)) return True return False def handle_from_import(self, text, column): m = re.search( r'(from)(?:\s+?)(\w*.?\w*)(?:\s+?)(import)(?:\s+?)(\w+)?$', text) if m: if column < m.end(3): return False from_module = m.group(2) module_path = code.get_package_path_from_name(from_module) last_part = m.group(4) if not last_part: last_part = '' if module_path: defined = self.get_imports(module_path) self._string_model.setStringList(defined) self.setCompletionPrefix(last_part) self.popup().setCurrentIndex(self.completionModel().index( 0, 0)) return True return False def custom_import_load(self, assign_map, moduel_name): return def _get_available_modules(self, paths=None): imports = list() if not paths: paths = sys.path if paths: paths = python.force_list(paths) for path in paths: fix_path = path_utils.normalize_path(path) stuff_in_folder = folder_utils.get_files_and_folders(fix_path) for file_or_folder in stuff_in_folder: folder_path = path_utils.join_path(fix_path, file_or_folder) files = folder_utils.get_files_with_extension('py', folder_path, full_path=False) if '__init__.py' in files: imports.append(str(file_or_folder)) python_files = folder_utils.get_files_with_extension( 'py', fix_path, full_path=False) for python_file in python_files: if python_file.startswith('__'): continue python_file_name = python_file.split('.')[0] imports.append(str(python_file_name)) if imports: imports = list(set(imports)) return imports def _on_insert_completion(self, completion_string): widget = self.widget() cursor = widget.textCursor() if completion_string == self.completionPrefix(): return extra = len(self.completionPrefix()) cursor.movePosition(QTextCursor.Left, cursor.KeepAnchor, extra) cursor.removeSelectedText() cursor.insertText(completion_string) widget.setTextCursor(cursor)
class MainWindow(QMainWindow): def __init__(self, url): super().__init__() self.setAttribute(Qt.WA_DeleteOnClose, True) self.progress = 0 f = QFile() f.setFileName(":/jquery.min.js") f.open(QIODevice.ReadOnly) self.jQuery = f.readAll().data().decode() self.jQuery += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" f.close() self.view = QWebEngineView(self) self.view.load(url) self.view.loadFinished.connect(self.adjustLocation) self.view.titleChanged.connect(self.adjustTitle) self.view.loadProgress.connect(self.setProgress) self.view.loadFinished.connect(self.finishLoading) self.locationEdit = QLineEdit(self) self.locationEdit.setSizePolicy( QSizePolicy.Expanding, self.locationEdit.sizePolicy().verticalPolicy()) self.locationEdit.returnPressed.connect(self.changeLocation) toolBar = self.addToolBar(self.tr("Navigation")) toolBar.addAction(self.view.pageAction(QWebEnginePage.Back)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Forward)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Reload)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Stop)) toolBar.addWidget(self.locationEdit) viewMenu = self.menuBar().addMenu(self.tr("&View")) viewSourceAction = QAction(self.tr("Page Source"), self) viewSourceAction.triggered.connect(self.viewSource) viewMenu.addAction(viewSourceAction) effectMenu = self.menuBar().addMenu(self.tr("&Effect")) effectMenu.addAction(self.tr("Highlight all links"), self.highlightAllLinks) self.rotateAction = QAction(self) self.rotateAction.setIcon(self.style().standardIcon( QStyle.SP_FileDialogDetailedView)) self.rotateAction.setCheckable(True) self.rotateAction.setText(self.tr("Turn images upside down")) self.rotateAction.toggled.connect(self.rotateImages) effectMenu.addAction(self.rotateAction) toolsMenu = self.menuBar().addMenu(self.tr("&Tools")) toolsMenu.addAction(self.tr("Remove GIF images"), self.removeGifImages) toolsMenu.addAction(self.tr("Remove all inline frames"), self.removeInlineFrames) toolsMenu.addAction(self.tr("Remove all object elements"), self.removeObjectElements) toolsMenu.addAction(self.tr("Remove all embedded elements"), self.removeEmbeddedElements) self.setCentralWidget(self.view) @Slot() def adjustLocation(self): self.locationEdit.setText(self.view.url().toString()) @Slot() def changeLocation(self): url = QUrl.fromUserInput(self.locationEdit.text()) self.view.load(url) self.view.setFocus() @Slot() def adjustTitle(self): if self.progress <= 0 or self.progress >= 100: self.setWindowTitle(self.view.title()) else: self.setWindowTitle("%s (%2d)" % (self.view.title(), self.progress)) @Slot(int) def setProgress(self, p): self.progress = p self.adjustTitle() @Slot() def finishLoading(self): self.progress = 100 self.adjustTitle() self.view.page().runJavaScript(self.jQuery) self.rotateImages(self.rotateAction.isChecked()) @Slot() def viewSource(self): self.textEdit = QTextEdit() self.textEdit.setAttribute(Qt.WA_DeleteOnClose) self.textEdit.adjustSize() self.textEdit.move(self.geometry().center() - self.textEdit.rect().center()) self.textEdit.show() self.view.page().toHtml(self.textEdit.setPlainText) @Slot() def highlightAllLinks(self): code = "qt.jQuery('a').each( function () { qt.jQuery(this).css('background-color', 'yellow') } )" self.view.page().runJavaScript(code) @Slot(bool) def rotateImages(self, invert): code = "" if invert: code = "qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(180deg)') } )" # noqa: E501 else: code = "qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(0deg)') } )" # noqa: E501 self.view.page().runJavaScript(code) @Slot() def removeGifImages(self): code = "qt.jQuery('[src*=gif]').remove()" self.view.page().runJavaScript(code) @Slot() def removeInlineFrames(self): code = "qt.jQuery('iframe').remove()" self.view.page().runJavaScript(code) @Slot() def removeObjectElements(self): code = "qt.jQuery('object').remove()" self.view.page().runJavaScript(code) @Slot() def removeEmbeddedElements(self): code = "qt.jQuery('embed').remove()" self.view.page().runJavaScript(code)