Files
propertree/ProperTree.command
T

393 lines
15 KiB
Bash
Executable File

#!/usr/bin/env python
import sys, os, binascii, base64
try:
import Tkinter as tk
import ttk
import tkFileDialog as fd
import tkMessageBox as mb
except:
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import filedialog as fd
from tkinter import messagebox as mb
from Scripts import *
class ProperTree:
def __init__(self, plists = []):
# Create the new tk object
self.tk = tk.Tk()
self.tk.title("Convert Values")
self.tk.minsize(width=640,height=130)
self.tk.resizable(False, False)
# self.tk.columnconfigure(2,weight=1)
# Build the Hex <--> Base64 converter
f_label = tk.Label(self.tk, text="From:")
f_label.grid(row=0,column=0)
t_label = tk.Label(self.tk, text="To:")
t_label.grid(row=1,column=0)
# Setup the from/to option menus
f_title = tk.StringVar(self.tk)
t_title = tk.StringVar(self.tk)
f_title.set("Base64")
t_title.set("Hex")
f_option = tk.OptionMenu(self.tk, f_title, "Ascii", "Base64", "Hex", command=self.change_from_type)
t_option = tk.OptionMenu(self.tk, t_title, "Ascii", "Base64", "Hex", command=self.change_to_type)
self.from_type = "Base64"
self.to_type = "Hex"
f_option.grid(row=0,column=1,sticky="we")
t_option.grid(row=1,column=1,sticky="we")
self.f_text = tk.Entry(self.tk,width=80)
self.f_text.delete(0,tk.END)
self.f_text.insert(0,"")
self.f_text.grid(row=0,column=2,sticky="we",padx=10,pady=10)
self.t_text = tk.Entry(self.tk,width=80)
self.t_text.configure(state='normal')
self.t_text.delete(0,tk.END)
self.t_text.insert(0,"")
self.t_text.configure(state='readonly')
self.t_text.grid(row=1,column=2,sticky="we",padx=10,pady=10)
self.c_button = tk.Button(self.tk, text="Convert", command=self.convert_values)
self.c_button.grid(row=2,column=2,sticky="e",padx=10,pady=10)
self.tk.bind("<Return>", self.convert_values)
self.tk.bind("<KP_Enter>", self.convert_values)
self.clipboard = None
# Setup the menu-related keybinds - and change the app name if needed
key="Control"
sign = "Ctr+"
if str(sys.platform) == "darwin":
# Remap the quit function to our own
self.tk.createcommand('::tk::mac::Quit', self.quit)
# Import the needed modules to change the bundle name and force focus
try:
from Foundation import NSBundle
from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps
app = NSRunningApplication.runningApplicationWithProcessIdentifier_(os.getpid())
app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)
bundle = NSBundle.mainBundle()
if bundle:
info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
if info and info['CFBundleName'] == 'Python':
info['CFBundleName'] = "ProperTree"
except:
pass
key="Command"
sign=key+"+"
self.tk.protocol("WM_DELETE_WINDOW", self.close_window)
# Close initial window
self.close_window(None,False)
# Setup the top level menu
file_menu = tk.Menu(self.tk)
main_menu = tk.Menu(self.tk)
main_menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="New ({}N)".format(sign), command=self.new_plist)
file_menu.add_command(label="Open ({}O)".format(sign), command=self.open_plist)
file_menu.add_command(label="Save ({}S)".format(sign), command=self.save_plist)
file_menu.add_command(label="Save As ({}Shift+S)".format(sign), command=self.save_plist_as)
file_menu.add_command(label="Duplicate ({}D)".format(sign), command=self.duplicate_plist)
file_menu.add_separator()
file_menu.add_command(label="OC Snapshot ({}R)".format(sign), command=self.oc_snapshot)
file_menu.add_separator()
file_menu.add_command(label="Convert Window ({}T)".format(sign), command=self.show_convert)
file_menu.add_command(label="Strip Comments ({}M)".format(sign), command=self.strip_comments)
file_menu.add_separator()
file_menu.add_command(label="View Data As Hex", command=lambda:self.change_data_display("hex"))
file_menu.add_command(label="View Data As Base64", command=lambda:self.change_data_display("base64"))
if not str(sys.platform) == "darwin":
file_menu.add_separator()
file_menu.add_command(label="Quit ({}Q)".format(sign), command=self.quit)
self.tk.config(menu=main_menu)
# Set bindings
self.tk.bind_all("<{}-n>".format(key), self.new_plist)
self.tk.bind_all("<{}-o>".format(key), self.open_plist)
self.tk.bind_all("<{}-s>".format(key), self.save_plist)
self.tk.bind_all("<{}-S>".format(key), self.save_plist_as)
self.tk.bind_all("<{}-d>".format(key), self.duplicate_plist)
self.tk.bind_all("<{}-c>".format(key), self.copy_selection)
self.tk.bind_all("<{}-v>".format(key), self.paste_selection)
self.tk.bind_all("<{}-t>".format(key), self.show_convert)
self.tk.bind_all("<{}-z>".format(key), self.undo)
self.tk.bind_all("<{}-Z>".format(key), self.redo)
self.tk.bind_all("<{}-m>".format(key), self.strip_comments)
self.tk.bind_all("<{}-r>".format(key), self.oc_snapshot)
if not str(sys.platform) == "darwin":
# Rewrite the default Command-Q command
self.tk.bind_all("<{}-q>".format(key), self.quit)
if isinstance(plists, list) and len(plists):
self.start_window = None
# Iterate the passed plists and open them
for p in plists:
self.open_plist_with_path(None,p,None)
else:
# create a fresh plist to start
self.start_window = self.new_plist()
# Start our run loop
tk.mainloop()
def change_data_display(self, new_data = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.change_data_display(new_data)
def oc_snapshot(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.oc_snapshot(event)
def close_window(self, event = None, check_close = True):
# Remove the default window that comes from it
if str(sys.platform) == "darwin":
self.tk.iconify()
else:
self.tk.withdraw()
if check_close:
windows = self.stackorder(self.tk)
if not len(windows):
# Quit if all windows are closed
self.quit()
def strip_comments(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.strip_comments(event)
def change_to_type(self, value):
self.to_type = value
self.convert_values()
def change_from_type(self, value):
self.from_type = value
def show_convert(self, event = None):
self.tk.deiconify()
def convert_values(self, event = None):
from_value = self.f_text.get()
if not len(from_value):
# Empty - nothing to convert
return
# Pre-check for hex potential issues
if self.from_type.lower() == "hex":
if from_value.lower().startswith("0x"):
from_value = from_value[2:]
from_value = from_value.replace(" ","")
if [x for x in from_value if x.lower() not in "0123456789abcdef"]:
self.tk.bell()
mb.showerror("Invalid Hex Data","Invalid character in passed hex data.",parent=self.tk)
return
try:
# Handle the from data
if sys.version_info >= (3,0):
# Convert to bytes
from_value = from_value.encode("utf-8")
if self.from_type.lower() == "base64":
from_value = base64.b64decode(from_value)
elif self.from_type.lower() == "hex":
from_value = binascii.unhexlify(from_value)
# Let's get the data converted
to_value = from_value
if self.to_type.lower() == "base64":
to_value = base64.b64encode(from_value)
elif self.to_type.lower() == "hex":
to_value = binascii.hexlify(from_value)
if sys.version_info >= (3,0):
# Convert to bytes
to_value = to_value.decode("utf-8")
if self.to_type.lower() == "hex":
# Capitalize it, and pad with spaces
to_value = "{}".format(" ".join((to_value[0+i:8+i] for i in range(0, len(to_value), 8))).upper())
# Set the text box
self.t_text.configure(state='normal')
self.t_text.delete(0,tk.END)
self.t_text.insert(0,to_value)
self.t_text.configure(state='readonly')
except Exception as e:
self.tk.bell()
mb.showerror("Conversion Error",str(e),parent=self.tk)
### ###
# Save/Load Plist Functions #
### ###
def copy_selection(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
node = window._tree.focus()
if node == "":
# Nothing to copy
return
self.clipboard = window.nodes_to_values(node,{})
def paste_selection(self, event = None):
if self.clipboard == None:
return
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.paste_selection(self.clipboard)
def duplicate_plist(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
plist_data = window.nodes_to_values()
plistwindow.PlistWindow(self, self.tk).open_plist(None,plist_data)
def save_plist(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.save_plist(event)
def save_plist_as(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.save_plist_as(event)
def undo(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.reundo(event)
def redo(self, event = None):
windows = self.stackorder(self.tk)
if not len(windows):
# Nothing to save
return
window = windows[-1] # Get the last item (most recent)
if window == self.tk:
return
window.reundo(event,False)
def new_plist(self, event = None):
# Creates a new plistwindow object
window = plistwindow.PlistWindow(self, self.tk)
window.focus_force()
window.update()
return window
def open_plist(self, event=None):
# Prompt the user to open a plist, attempt to load it, and if successful,
# set its path as our current_plist value
current_window = None
windows = self.stackorder(self.tk)
if len(windows) == 1 and windows[0] == self.start_window and windows[0].edited == False and windows[0].current_plist == None:
# Fresh window - replace the contents
current_window = windows[0]
path = fd.askopenfilename(title = "Select config.plist",filetypes=[("Plist files", "*.plist")],parent=current_window)
if not len(path):
# User cancelled - bail
return None
# Verify that no other window has that file selected already
for window in windows:
if window == self.tk:
continue
if window.current_plist == path:
# found one - just make this focus instead
window.focus_force()
window.update()
window.bell()
mb.showerror("File Already Open", "{} is already open here.".format(path), parent=window)
return
self.open_plist_with_path(event,path,current_window)
def open_plist_with_path(self, event = None, path = None, current_window = None):
if path == None:
# Uh... wut?
return
# Let's try to load the plist
try:
with open(path,"rb") as f:
plist_data = plist.load(f)
except Exception as e:
# Had an issue, throw up a display box
# print("{}\a".format(str(e)))
self.tk.bell()
mb.showerror("An Error Occurred While Opening {}".format(os.path.basename(path)), str(e),parent=current_window)
return None
else:
# Opened it correctly - let's load it, and set our values
if current_window:
current_window.open_plist(path,plist_data)
else:
# Need to create one first
plistwindow.PlistWindow(self, self.tk).open_plist(path,plist_data)
return True
def stackorder(self, root):
"""return a list of root and toplevel windows in stacking order (topmost is last)"""
c = root.children
s = root.tk.eval('wm stackorder {}'.format(root))
L = [x.lstrip('.') for x in s.split()]
return [(c[x] if x else root) for x in L]
def quit(self, event=None):
# Check if we need to save first, then quit if we didn't cancel
for window in self.stackorder(self.tk)[::-1]:
if window == self.tk:
continue
if window.check_save() == None:
# User cancelled or we failed to save, bail
return
window.destroy()
# Actually quit the tkinter session
self.tk.destroy()
if __name__ == '__main__':
plists = []
if len(sys.argv) > 1:
plists = sys.argv[1:]
p = ProperTree(plists)