diff options
Diffstat (limited to 'python/examples')
| -rw-r--r-- | python/examples/bin_info.py | 8 | ||||
| -rw-r--r-- | python/examples/mappedview.py | 21 | ||||
| -rw-r--r-- | python/examples/nds.py | 13 | ||||
| -rw-r--r-- | python/examples/nes.py | 42 | ||||
| -rw-r--r-- | python/examples/nsf.py | 24 | ||||
| -rw-r--r-- | python/examples/snippets/__init__.py | 444 |
6 files changed, 497 insertions, 55 deletions
diff --git a/python/examples/bin_info.py b/python/examples/bin_info.py index 1af59a31..850a97b8 100644 --- a/python/examples/bin_info.py +++ b/python/examples/bin_info.py @@ -28,9 +28,6 @@ import binaryninja.interaction as interaction from binaryninja.plugin import PluginCommand from binaryninja import Settings -# 2-3 compatibility -from binaryninja import range - def get_bininfo(bv): if bv is None: @@ -54,8 +51,9 @@ def get_bininfo(bv): contents += "| Start | Name |\n" contents += "|------:|:-------|\n" - for i in range(min(10, len(bv.functions))): - contents += "| 0x%x | %s |\n" % (bv.functions[i].start, bv.functions[i].symbol.full_name) + functions = list(bv.functions) + for i in range(min(10, len(functions))): + contents += "| 0x%x | %s |\n" % (functions[i].start, functions[i].symbol.full_name) contents += "### First 10 Strings ###\n" contents += "| Start | Length | String |\n" diff --git a/python/examples/mappedview.py b/python/examples/mappedview.py index 6d060235..bf7f2d11 100644 --- a/python/examples/mappedview.py +++ b/python/examples/mappedview.py @@ -43,8 +43,8 @@ class MappedView(BinaryView): def __init__(self, data): BinaryView.__init__(self, parent_view = data, file_metadata = data.file) - @classmethod - def is_valid_for_data(cls, data): + @staticmethod + def is_valid_for_data(data): # Insert code that looks for a magic identifier and return True if this BinaryViewType can handle/parse the binary return True @@ -64,12 +64,14 @@ class MappedView(BinaryView): # Optionally, perform light-weight parsing of the 'Raw' BinaryView to extract required information for load settings generation. # This allows finer control of the settings provided as well as their default values. # For example, the view.relocatable property could be used to control the read-only attribute of "loader.imageBase" - view = cls.registered_view_type.parse(data) - + registered_view = cls.registered_view_type + assert registered_view is not None + view = registered_view.parse(data) + assert view is not None # Populate settings container with default load settings # Note: `get_default_load_settings_for_data` automatically tries to parse the input if the `data` BinaryViewType name does not match the # cls BinaryViewType name. In this case a parsed view is already being passed. - load_settings = cls.registered_view_type.get_default_load_settings_for_data(view) + load_settings = registered_view.get_default_load_settings_for_data(view) # Specify default load settings that can be overridden (from the UI). overrides = ["loader.architecture", "loader.platform", "loader.entryPointOffset", "loader.imageBase", "loader.segments", "loader.sections"] @@ -113,8 +115,9 @@ class MappedView(BinaryView): load_settings = self.get_load_settings(self.name) if load_settings is None: if self.parse_only is True: - self.arch = Architecture['x86'] - self.platform = Architecture['x86'].standalone_platform + self.arch = Architecture['x86'] # type: ignore + self.platform = Architecture['x86'].standalone_platform # type: ignore + assert self.parent_view is not None self.add_auto_segment(0, len(self.parent_view), 0, len(self.parent_view), SegmentFlag.SegmentReadable) return True else: @@ -123,8 +126,8 @@ class MappedView(BinaryView): load_settings = self.__class__.get_load_settings_for_data(self.parent_view) arch = load_settings.get_string("loader.architecture", self) - self.arch = Architecture[arch] - self.platform = Architecture[arch].standalone_platform + self.arch = Architecture[arch] # type: ignore + self.platform = Architecture[arch].standalone_platform # type: ignore self.load_address = load_settings.get_integer("loader.imageBase", self) self.add_auto_segment(self.load_address, len(self.parent_view), 0, len(self.parent_view), SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) if load_settings.contains("loader.entryPointOffset"): diff --git a/python/examples/nds.py b/python/examples/nds.py index 4cf7c24d..18001d50 100644 --- a/python/examples/nds.py +++ b/python/examples/nds.py @@ -27,9 +27,6 @@ from binaryninja.log import log_error import struct import traceback -# 2-3 compatibility -from binaryninja import range - def crc16(data): crc = 0xffff @@ -48,8 +45,8 @@ class DSView(BinaryView): BinaryView.__init__(self, file_metadata = data.file, parent_view = data) self.raw = data - @classmethod - def is_valid_for_data(self, data): + @staticmethod + def is_valid_for_data(data): hdr = data.read(0, 0x160) if len(hdr) < 0x160: return False @@ -60,7 +57,7 @@ class DSView(BinaryView): return True def init_common(self): - self.platform = Architecture["armv7"].standalone_platform + self.platform = Architecture["armv7"].standalone_platform # type: ignore self.hdr = self.raw.read(0, 0x160) def init_arm9(self): @@ -72,7 +69,7 @@ class DSView(BinaryView): self.arm9_size = struct.unpack("<L", self.hdr[0x2C:0x30])[0] self.add_auto_segment(self.arm9_load_addr, self.arm9_size, self.arm9_offset, self.arm9_size, SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) - self.add_entry_point(Architecture['armv7'].standalone_platform, self.arm_entry_addr) + self.add_entry_point(Architecture['armv7'].standalone_platform, self.arm_entry_addr) # type: ignore return True except: log_error(traceback.format_exc()) @@ -87,7 +84,7 @@ class DSView(BinaryView): self.arm7_size = struct.unpack("<L", self.hdr[0x3C:0x40])[0] self.add_auto_segment(self.arm7_load_addr, self.arm7_size, self.arm7_offset, self.arm7_size, SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) - self.add_entry_point(Architecture['armv7'].standalone_platform, self.arm_entry_addr) + self.add_entry_point(Architecture['armv7'].standalone_platform, self.arm_entry_addr) # type: ignore return True except: log_error(traceback.format_exc()) diff --git a/python/examples/nes.py b/python/examples/nes.py index ba9a2965..013528a1 100644 --- a/python/examples/nes.py +++ b/python/examples/nes.py @@ -21,19 +21,17 @@ import struct import traceback import os +from typing import Callable, List, Any -from binaryninja.architecture import Architecture +from binaryninja.architecture import Architecture, InstructionInfo, RegisterInfo, RegisterName from binaryninja.lowlevelil import LowLevelILLabel, LLIL_TEMP -from binaryninja.function import RegisterInfo, InstructionInfo, InstructionTextToken +from binaryninja.function import InstructionTextToken from binaryninja.binaryview import BinaryView from binaryninja.types import Symbol from binaryninja.log import log_error from binaryninja.enums import (BranchType, InstructionTextTokenType, LowLevelILOperation, LowLevelILFlagCondition, FlagRole, SegmentFlag, SymbolType) -# 2-3 compatibility -from binaryninja import range - InstructionNames = [ "brk", "ora", None, None, None, "ora", "asl", None, # 0x00 @@ -145,14 +143,14 @@ OperandLengths = [ 1, # IND_Y_DEST 1, # REL 1, # ZERO - 1, # ZREO_DEST + 1, # ZERO_DEST 1, # ZERO_X 1, # ZERO_X_DEST 1, # ZERO_Y 1 # ZERO_Y_DEST ] -OperandTokens = [ +OperandTokens:List[Callable[[int], List[InstructionTextToken]]] = [ lambda value: [], # NONE lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value)], # ABS lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value)], # ABS_DEST @@ -248,7 +246,7 @@ OperandIL = [ def cond_branch(il, cond, dest): t = None if il[dest].operation == LowLevelILOperation.LLIL_CONST: - t = il.get_label_for_address(Architecture['6502'], il[dest].constant) + t = il.get_label_for_address(Architecture['6502'], il[dest].constant) # type: ignore if t is None: t = LowLevelILLabel() indirect = True @@ -266,7 +264,7 @@ def cond_branch(il, cond, dest): def jump(il, dest): label = None if il[dest].operation == LowLevelILOperation.LLIL_CONST: - label = il.get_label_for_address(Architecture['6502'], il[dest].constant) + label = il.get_label_for_address(Architecture['6502'], il[dest].constant) # type: ignore if label is None: il.append(il.jump(dest)) else: @@ -374,10 +372,10 @@ class M6502(Architecture): instr_alignment = 1 max_instr_length = 3 regs = { - "a": RegisterInfo("a", 1), - "x": RegisterInfo("x", 1), - "y": RegisterInfo("y", 1), - "s": RegisterInfo("s", 1) + "a": RegisterInfo(RegisterName("a"), 1), + "x": RegisterInfo(RegisterName("x"), 1), + "y": RegisterInfo(RegisterName("y"), 1), + "s": RegisterInfo(RegisterName("s"), 1) } stack_pointer = "s" flags = ["c", "z", "i", "d", "b", "v", "s"] @@ -451,7 +449,7 @@ class M6502(Architecture): def get_instruction_text(self, data, addr): instr, operand, length, value = self.decode_instruction(data, addr) - if instr is None: + if instr is None or operand is None or length is None or value is None: return None tokens = [] @@ -461,7 +459,7 @@ class M6502(Architecture): def get_instruction_low_level_il(self, data, addr, il): instr, operand, length, value = self.decode_instruction(data, addr) - if instr is None: + if instr is None or operand is None or length is None or value is None: return None operand = OperandIL[operand](il, value) @@ -518,26 +516,27 @@ class M6502(Architecture): def skip_and_return_value(self, data, addr, value): if (data[0:1] != b"\x20") or (len(data) != 3): return None - return b"\xa9" + chr(value & 0xff) + b"\xea" + return b"\xa9" + (value & 0xff).to_bytes(1, "little") + b"\xea" class NESView(BinaryView): name = "NES" long_name = "NES ROM" - + bank = None def __init__(self, data): BinaryView.__init__(self, parent_view = data, file_metadata = data.file) - self.platform = Architecture['6502'].standalone_platform + self.platform = Architecture['6502'].standalone_platform # type: ignore @classmethod - def is_valid_for_data(self, data): + def is_valid_for_data(cls, data): hdr = data.read(0, 16) if len(hdr) < 16: return False if hdr[0:4] != b"NES\x1a": return False rom_banks = struct.unpack("B", hdr[4:5])[0] - if rom_banks < (self.bank + 1): + assert cls.bank is not None + if rom_banks < (cls.bank + 1): return False return True @@ -558,6 +557,7 @@ class NESView(BinaryView): self.add_auto_segment(0, 0x8000, 0, 0, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable) # Add ROM mappings + assert self.__class__.bank is not None self.add_auto_segment(0x8000, 0x4000, self.rom_offset + (self.__class__.bank * 0x4000), 0x4000, SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) self.add_auto_segment(0xc000, 0x4000, self.rom_offset + self.rom_length - 0x4000, 0x4000, @@ -632,7 +632,7 @@ class NESView(BinaryView): return True def perform_get_entry_point(self): - return struct.unpack("<H", str(self.perform_read(0xfffc, 2)))[0] + return struct.unpack("<H", self.perform_read(0xfffc, 2))[0] banks = [] diff --git a/python/examples/nsf.py b/python/examples/nsf.py index 959dc060..45500cba 100644 --- a/python/examples/nsf.py +++ b/python/examples/nsf.py @@ -39,10 +39,10 @@ class NSFView(BinaryView): def __init__(self, data): BinaryView.__init__(self, parent_view=data, file_metadata=data.file) - self.platform = Architecture["6502"].standalone_platform + self.platform = Architecture["6502"].standalone_platform # type: ignore - @classmethod - def is_valid_for_data(self, data): + @staticmethod + def is_valid_for_data(data): hdr = data.read(0, 128) if len(hdr) < 128: return False @@ -57,25 +57,25 @@ class NSFView(BinaryView): def init(self): try: hdr = self.parent_view.read(0, 128) - self.version = struct.unpack("B", hdr[5])[0] - self.song_count = struct.unpack("B", hdr[6])[0] - self.starting_song = struct.unpack("B", hdr[7])[0] + self.version = int(hdr[5]) + self.song_count = int(hdr[6]) + self.starting_song = int(hdr[7]) self.load_address = struct.unpack("<H", hdr[8:10])[0] self.init_address = struct.unpack("<H", hdr[10:12])[0] self.play_address = struct.unpack("<H", hdr[12:14])[0] - self.song_name = hdr[15].split('\0')[0] - self.artist_name = hdr[46].split('\0')[0] - self.copyright_name = hdr[78].split('\0')[0] + self.song_name = int(hdr[15]) + self.artist_name = int(hdr[46]) + self.copyright_name = int(hdr[78]) self.play_speed_ntsc = struct.unpack("<H", hdr[110:112])[0] self.bank_switching = hdr[112:120] self.play_speed_pal = struct.unpack("<H", hdr[120:122])[0] - self.pal_ntsc_bits = struct.unpack("B", hdr[122])[0] + self.pal_ntsc_bits = int(hdr[122]) self.pal = True if (self.pal_ntsc_bits & 1) == 1 else False self.ntsc = not self.pal if self.pal_ntsc_bits & 2 == 2: self.pal = True self.ntsc = True - self.extra_sound_bits = struct.unpack("B", hdr[123])[0] + self.extra_sound_bits = int(hdr[123]) if self.bank_switching == "\0" * 8: # no bank switching @@ -139,7 +139,7 @@ class NSFView(BinaryView): return True def perform_get_entry_point(self): - return struct.unpack("<H", str(self.perform_read(0x0a, 2)))[0] + return struct.unpack("<H", self.perform_read(0x0a, 2))[0] NSFView.register() diff --git a/python/examples/snippets/__init__.py b/python/examples/snippets/__init__.py new file mode 100644 index 00000000..0f551a06 --- /dev/null +++ b/python/examples/snippets/__init__.py @@ -0,0 +1,444 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import sys +import os +import re +import codecs +from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication, QTextEdit, QWidget, + QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter, + QInputDialog, QMessageBox, QHeaderView, QMenu, QAction, QKeySequenceEdit, + QPlainTextEdit) +from PySide2.QtCore import (QDir, QObject, Qt, QFileInfo, QItemSelectionModel, QSettings, QUrl) +from PySide2.QtGui import (QFont, QFontMetrics, QDesktopServices, QKeySequence, QIcon) +from binaryninja import user_plugin_path +from binaryninja.plugin import PluginCommand, MainThreadActionHandler +from binaryninja.mainthread import execute_on_main_thread +from binaryninja.log import (log_error, log_debug) +from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, DockHandler, + getThemeColor, ThemeColor) +import numbers +from .QCodeEditor import QCodeEditor, PythonHighlighter + +snippetPath = os.path.realpath(os.path.join(user_plugin_path(), "..", "snippets")) +try: + if not os.path.exists(snippetPath): + os.mkdir(snippetPath) +except IOError: + log_error("Unable to create %s" % snippetPath) + + +def includeWalk(dir, includeExt): + filePaths = [] + for (root, dirs, files) in os.walk(dir): + for f in files: + if os.path.splitext(f)[1] in includeExt: + filePaths.append(os.path.join(root, f)) + return filePaths + + +def loadSnippetFromFile(snippetPath): + try: + snippetText = codecs.open(snippetPath, 'r', "utf-8").readlines() + except: + return ("", "", "") + if (len(snippetText) < 3): + return ("", "", "") + else: + qKeySequence = QKeySequence(snippetText[1].strip()[1:]) + if qKeySequence.isEmpty(): + qKeySequence = None + return (snippetText[0].strip()[1:], + qKeySequence, + ''.join(snippetText[2:]) + ) + + +def actionFromSnippet(snippetName, snippetDescription): + if not snippetDescription: + shortName = os.path.basename(snippetName) + if shortName.endswith('.py'): + shortName = shortName[:-3] + return "Snippets\\" + shortName + else: + return "Snippets\\" + snippetDescription + + +def executeSnippet(code, context): + snippetGlobals = {} + if context.binaryView == None: + dock = DockHandler.getActiveDockHandler() + if not dock: + log_error("Snippet triggered with no context and no dock handler. This should not happen. Please report reproduction steps if possible.") + return + viewFrame = dock.getViewFrame() + if not viewFrame: + log_error("Snippet triggered with no context and no view frame. Snippets require at least one open binary.") + return + viewInterface = viewFrame.getCurrentViewInterface() + context.binaryView = viewInterface.getData() + snippetGlobals['current_view'] = context.binaryView + snippetGlobals['bv'] = context.binaryView + if not context.function: + if not context.lowLevelILFunction: + if not context.mediumLevelILFunction: + snippetGlobals['current_mlil'] = None + snippetGlobals['current_function'] = None + snippetGlobals['current_llil'] = None + else: + snippetGlobals['current_mlil'] = context.mediumLevelILFunction + snippetGlobals['current_function'] = context.mediumLevelILFunction.source_function + snippetGlobals['current_llil'] = context.mediumLevelILFunction.source_function.llil + else: + snippetGlobals['current_llil'] = context.lowLevelILFunction + snippetGlobals['current_function'] = context.lowLevelILFunction.source_function + snippetGlobals['current_mlil'] = context.lowLevelILFunction.source_function.mlil + else: + snippetGlobals['current_function'] = context.function + snippetGlobals['current_mlil'] = context.function.mlil + snippetGlobals['current_llil'] = context.function.llil + snippetGlobals['current_token'] = context.function.llil + + if context.function is not None: + snippetGlobals['current_basic_block'] = context.function.get_basic_block_at(context.address) + else: + snippetGlobals['current_basic_block'] = None + snippetGlobals['current_address'] = context.address + snippetGlobals['here'] = context.address + if context.address is not None and isinstance(context.length, int): + snippetGlobals['current_selection'] = (context.address, context.address+context.length) + else: + snippetGlobals['current_selection'] = None + snippetGlobals['uicontext'] = context + + exec("from binaryninja import *", snippetGlobals) + exec(code, snippetGlobals) + if snippetGlobals['here'] != context.address: + context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['here']) + if snippetGlobals['current_address'] != context.address: + context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['current_address']) + + +def makeSnippetFunction(code): + return lambda context: executeSnippet(code, context) + +class Snippets(QDialog): + + def __init__(self, context, parent=None): + super(Snippets, self).__init__(parent) + # Create widgets + self.setWindowModality(Qt.ApplicationModal) + self.title = QLabel(self.tr("Snippet Editor")) + self.saveButton = QPushButton(self.tr("&Save")) + self.saveButton.setShortcut(QKeySequence(self.tr("Ctrl+S"))) + self.runButton = QPushButton(self.tr("&Run")) + self.runButton.setShortcut(QKeySequence(self.tr("Ctrl+R"))) + self.closeButton = QPushButton(self.tr("Close")) + self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) + self.setWindowTitle(self.title.text()) + #self.newFolderButton = QPushButton("New Folder") + self.browseButton = QPushButton("Browse Snippets") + self.browseButton.setIcon(QIcon.fromTheme("edit-undo")) + self.deleteSnippetButton = QPushButton("Delete") + self.newSnippetButton = QPushButton("New Snippet") + self.edit = QCodeEditor(HIGHLIGHT_CURRENT_LINE=False, SyntaxHighlighter=PythonHighlighter) + self.edit.setPlaceholderText("python code") + self.resetting = False + self.columns = 3 + self.context = context + + self.keySequenceEdit = QKeySequenceEdit(self) + self.currentHotkey = QKeySequence() + self.currentHotkeyLabel = QLabel("") + self.currentFileLabel = QLabel() + self.currentFile = "" + self.snippetDescription = QLineEdit() + self.snippetDescription.setPlaceholderText("optional description") + + #Set Editbox Size + font = getMonospaceFont(self) + self.edit.setFont(font) + font = QFontMetrics(font) + self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API + + #Files + self.files = QFileSystemModel() + self.files.setRootPath(snippetPath) + self.files.setNameFilters(["*.py"]) + + #Tree + self.tree = QTreeView() + self.tree.setModel(self.files) + self.tree.setSortingEnabled(True) + self.tree.hideColumn(2) + self.tree.sortByColumn(0, Qt.AscendingOrder) + self.tree.setRootIndex(self.files.index(snippetPath)) + for x in range(self.columns): + #self.tree.resizeColumnToContents(x) + self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) + treeLayout = QVBoxLayout() + treeLayout.addWidget(self.tree) + treeButtons = QHBoxLayout() + #treeButtons.addWidget(self.newFolderButton) + treeButtons.addWidget(self.browseButton) + treeButtons.addWidget(self.newSnippetButton) + treeButtons.addWidget(self.deleteSnippetButton) + treeLayout.addLayout(treeButtons) + treeWidget = QWidget() + treeWidget.setLayout(treeLayout) + + # Create layout and add widgets + buttons = QHBoxLayout() + buttons.addWidget(self.clearHotkeyButton) + buttons.addWidget(self.keySequenceEdit) + buttons.addWidget(self.currentHotkeyLabel) + buttons.addWidget(self.closeButton) + buttons.addWidget(self.runButton) + buttons.addWidget(self.saveButton) + + description = QHBoxLayout() + description.addWidget(QLabel(self.tr("Description: "))) + description.addWidget(self.snippetDescription) + + vlayoutWidget = QWidget() + vlayout = QVBoxLayout() + vlayout.addLayout(description) + vlayout.addWidget(self.edit) + vlayout.addLayout(buttons) + vlayoutWidget.setLayout(vlayout) + + hsplitter = QSplitter() + hsplitter.addWidget(treeWidget) + hsplitter.addWidget(vlayoutWidget) + + hlayout = QHBoxLayout() + hlayout.addWidget(hsplitter) + + self.showNormal() #Fixes bug that maximized windows are "stuck" + #Because you can't trust QT to do the right thing here + if (sys.platform == "darwin"): + self.settings = QSettings("Vector35", "Snippet Editor") + else: + self.settings = QSettings("Vector 35", "Snippet Editor") + if self.settings.contains("ui/snippeteditor/geometry"): + self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) + else: + self.edit.setMinimumWidth(80 * font.averageCharWidth()) + self.edit.setMinimumHeight(30 * font.lineSpacing()) + + # Set dialog layout + self.setLayout(hlayout) + + # Add signals + self.saveButton.clicked.connect(self.save) + self.closeButton.clicked.connect(self.close) + self.runButton.clicked.connect(self.run) + self.clearHotkeyButton.clicked.connect(self.clearHotkey) + self.tree.selectionModel().selectionChanged.connect(self.selectFile) + self.newSnippetButton.clicked.connect(self.newFileDialog) + self.deleteSnippetButton.clicked.connect(self.deleteSnippet) + #self.newFolderButton.clicked.connect(self.newFolder) + self.browseButton.clicked.connect(self.browseSnippets) + + if self.settings.contains("ui/snippeteditor/selected"): + selectedName = self.settings.value("ui/snippeteditor/selected") + self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + if self.tree.selectionModel().hasSelection(): + self.selectFile(self.tree.selectionModel().selection(), None) + self.edit.setFocus() + cursor = self.edit.textCursor() + cursor.setPosition(self.edit.document().characterCount()-1) + self.edit.setTextCursor(cursor) + else: + self.readOnly(True) + else: + self.readOnly(True) + + + @staticmethod + def registerAllSnippets(): + for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())): + if action == "Snippets\\Snippet Editor...": + continue + UIActionHandler.globalActions().unbindAction(action) + Menu.mainMenu("Tools").removeAction(action) + UIAction.unregisterAction(action) + + for snippet in includeWalk(snippetPath, ".py"): + snippetKeys = None + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet) + actionText = actionFromSnippet(snippet, snippetDescription) + if snippetCode: + if snippetKeys == None: + UIAction.registerAction(actionText) + else: + UIAction.registerAction(actionText, snippetKeys) + UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode))) + Menu.mainMenu("Tools").addAction(actionText, "Snippets") + + def clearSelection(self): + self.keySequenceEdit.clear() + self.currentHotkey = QKeySequence() + self.currentHotkeyLabel.setText("") + self.currentFileLabel.setText("") + self.snippetDescription.setText("") + self.edit.clear() + self.tree.clearSelection() + self.currentFile = "" + + def reject(self): + self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) + + if self.snippetChanged(): + question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?")) + if question != QMessageBox.StandardButton.Yes: + return + self.accept() + + def browseSnippets(self): + url = QUrl.fromLocalFile(snippetPath) + QDesktopServices.openUrl(url); + + def newFolder(self): + (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) + if ok and folderName: + index = self.tree.selectionModel().currentIndex() + selection = self.files.filePath(index) + if QFileInfo(selection).isDir(): + QDir(selection).mkdir(folderName) + else: + QDir(snippetPath).mkdir(folderName) + + def selectFile(self, new, old): + if (self.resetting): + self.resetting = False + return + if len(new.indexes()) == 0: + self.clearSelection() + self.currentFile = "" + self.readOnly(True) + return + newSelection = self.files.filePath(new.indexes()[0]) + self.settings.setValue("ui/snippeteditor/selected", newSelection) + if QFileInfo(newSelection).isDir(): + self.readOnly(True) + self.clearSelection() + self.currentFile = "" + return + + if old and old.length() > 0: + oldSelection = self.files.filePath(old.indexes()[0]) + if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): + question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?")) + if question != QMessageBox.StandardButton.Yes: + self.resetting = True + self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + return False + + self.currentFile = newSelection + self.loadSnippet() + + def loadSnippet(self): + self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) + self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") + self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence("")) + self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") + self.readOnly(False) + + def newFileDialog(self): + (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: ")) + if ok and snippetName: + if not snippetName.endswith(".py"): + snippetName += ".py" + index = self.tree.selectionModel().currentIndex() + selection = self.files.filePath(index) + if QFileInfo(selection).isDir(): + path = os.path.join(selection, snippetName) + else: + path = os.path.join(snippetPath, snippetName) + self.readOnly(False) + open(path, "w").close() + self.tree.setCurrentIndex(self.files.index(path)) + log_debug("Snippet %s created." % snippetName) + + def readOnly(self, flag): + self.keySequenceEdit.setEnabled(not flag) + self.snippetDescription.setReadOnly(flag) + self.edit.setReadOnly(flag) + if flag: + self.snippetDescription.setDisabled(True) + self.edit.setDisabled(True) + else: + self.snippetDescription.setEnabled(True) + self.edit.setEnabled(True) + + def deleteSnippet(self): + selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row + snippetName = self.files.fileName(selection) + question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) + if (question == QMessageBox.StandardButton.Yes): + log_debug("Deleting snippet %s." % snippetName) + self.clearSelection() + self.files.remove(selection) + self.registerAllSnippets() + + def snippetChanged(self): + if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): + return False + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) + if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty(): + return True + if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString(): + return True + return self.edit.toPlainText() != snippetCode or \ + self.snippetDescription.text() != snippetDescription + + def save(self): + log_debug("Saving snippet %s" % self.currentFile) + outputSnippet = codecs.open(self.currentFile, "w", "utf-8") + outputSnippet.write("#" + self.snippetDescription.text() + "\n") + outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") + outputSnippet.write(self.edit.toPlainText()) + outputSnippet.close() + self.registerAllSnippets() + + def run(self): + if self.context == None: + log_warn("Cannot run snippets outside of the UI at this time.") + return + if self.snippetChanged(): + question = QMessageBox.question(self, self.tr("Confirm"), self.tr("You have unsaved changes, must save first. Save?")) + if (question == QMessageBox.StandardButton.No): + return + else: + self.save() + actionText = actionFromSnippet(self.currentFile, self.snippetDescription.text()) + UIActionHandler.globalActions().executeAction(actionText, self.context) + + log_debug("Saving snippet %s" % self.currentFile) + outputSnippet = codecs.open(self.currentFile, "w", "utf-8") + outputSnippet.write("#" + self.snippetDescription.text() + "\n") + outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") + outputSnippet.write(self.edit.toPlainText()) + outputSnippet.close() + self.registerAllSnippets() + + def clearHotkey(self): + self.keySequenceEdit.clear() + + +def launchPlugin(context): + snippets = Snippets(context) + snippets.exec_() + + +if __name__ == '__main__': + app = QApplication(sys.argv) + snippets = Snippets(None) + snippets.show() + sys.exit(app.exec_()) +else: + Snippets.registerAllSnippets() + UIAction.registerAction("Snippets\\Snippet Editor...") + UIActionHandler.globalActions().bindAction("Snippets\\Snippet Editor...", UIAction(launchPlugin)) + Menu.mainMenu("Tools").addAction("Snippets\\Snippet Editor...", "Snippet") |
