summaryrefslogtreecommitdiff
path: root/python/examples
diff options
context:
space:
mode:
Diffstat (limited to 'python/examples')
-rw-r--r--python/examples/bin_info.py8
-rw-r--r--python/examples/mappedview.py21
-rw-r--r--python/examples/nds.py13
-rw-r--r--python/examples/nes.py42
-rw-r--r--python/examples/nsf.py24
-rw-r--r--python/examples/snippets/__init__.py444
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")