Update Picocrypt.py
This commit is contained in:
parent
3ac5947363
commit
40e64bda9c
|
@ -73,15 +73,16 @@ keepNotice = "Keep decrypted output even if it's corrupted or modified"
|
||||||
eraseNotice = "Securely erase and delete original file"
|
eraseNotice = "Securely erase and delete original file"
|
||||||
erasingNotice = "Securely erasing original file(s)..."
|
erasingNotice = "Securely erasing original file(s)..."
|
||||||
overwriteNotice = "Output file already exists. Would you like to overwrite it?"
|
overwriteNotice = "Output file already exists. Would you like to overwrite it?"
|
||||||
|
cancelNotice = "Picocrypt is still working. Are you sure?"
|
||||||
rsNotice = "Prevent corruption using Reed-Solomon"
|
rsNotice = "Prevent corruption using Reed-Solomon"
|
||||||
rscNotice = "Creating Reed-Solomon tables..."
|
rscNotice = "Creating Reed-Solomon tables..."
|
||||||
unknownErrorNotice = "Unknown error occured. Please try again."
|
unknownErrorNotice = "Unknown error occured. Please try again."
|
||||||
|
|
||||||
# Create root Tk
|
# Create root Tk
|
||||||
tk = TkinterDnD.Tk()
|
tk = TkinterDnD.Tk()
|
||||||
tk.geometry("480x480")
|
tk.geometry("480x470")
|
||||||
tk.title("Picocrypt")
|
tk.title("Picocrypt")
|
||||||
tk.configure(background="#f5f6f7")
|
tk.configure(background="#ffffff")
|
||||||
tk.resizable(0,0)
|
tk.resizable(0,0)
|
||||||
|
|
||||||
# Try setting window icon if included with Picocrypt
|
# Try setting window icon if included with Picocrypt
|
||||||
|
@ -93,12 +94,13 @@ except:
|
||||||
|
|
||||||
# Some styling
|
# Some styling
|
||||||
s = tkinter.ttk.Style()
|
s = tkinter.ttk.Style()
|
||||||
s.configure("TCheckbutton",background="#f5f6f7")
|
s.configure("TCheckbutton",background="#ffffff")
|
||||||
|
|
||||||
# Event when user selects an input file
|
# Event when user selects an input file
|
||||||
def inputSelected(draggedFile):
|
def inputSelected(draggedFile):
|
||||||
global inputFile,working,headerRsc,allFiles
|
global inputFile,working,headerRsc,allFiles
|
||||||
global draggedFolderPaths,files
|
global draggedFolderPaths,files
|
||||||
|
resetUI()
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
status.config(cursor="")
|
status.config(cursor="")
|
||||||
status.bind("<Button-1>",lambda e:None)
|
status.bind("<Button-1>",lambda e:None)
|
||||||
|
@ -180,6 +182,8 @@ def inputSelected(draggedFile):
|
||||||
adArea.delete("1.0",tkinter.END)
|
adArea.delete("1.0",tkinter.END)
|
||||||
adArea.insert("1.0",ad)
|
adArea.insert("1.0",ad)
|
||||||
adArea["state"] = "disabled"
|
adArea["state"] = "disabled"
|
||||||
|
|
||||||
|
# Update UI
|
||||||
adLabelString.set("File metadata (read only):")
|
adLabelString.set("File metadata (read only):")
|
||||||
keepBtn["state"] = "normal"
|
keepBtn["state"] = "normal"
|
||||||
eraseBtn["state"] = "disabled"
|
eraseBtn["state"] = "disabled"
|
||||||
|
@ -200,19 +204,37 @@ def inputSelected(draggedFile):
|
||||||
cpasswordInput["state"] = "normal"
|
cpasswordInput["state"] = "normal"
|
||||||
cpasswordInput.delete(0,"end")
|
cpasswordInput.delete(0,"end")
|
||||||
cpasswordString.set("Confirm password:")
|
cpasswordString.set("Confirm password:")
|
||||||
|
cpasswordLabel["state"] = "normal"
|
||||||
|
adLabel["state"] = "normal"
|
||||||
|
|
||||||
|
nFiles = len(files)
|
||||||
|
nFolders = len(draggedFolderPaths)
|
||||||
|
|
||||||
# Show selected file(s)
|
# Show selected file(s)
|
||||||
if allFiles or files:
|
if (allFiles or files) and not draggedFolderPaths:
|
||||||
inputString.set(f"{len(allFiles) or len(files)}"+
|
inputString.set(f"{nFiles} files selected (will encrypt).")
|
||||||
" file(s) selected (will encrypt).")
|
elif draggedFolderPaths and not files:
|
||||||
|
inputString.set(f"{nFolders} folder{'s' if nFolders!=1 else ''} selected (will encrypt).")
|
||||||
|
elif draggedFolderPaths and (allFiles or files):
|
||||||
|
inputString.set(
|
||||||
|
f"{nFiles} file{'s' if nFiles!=1 else ''} and "+
|
||||||
|
f"{nFolders} folder{'s' if nFolders!=1 else ''} selected (will encrypt)."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
inputString.set(inputFile.split("/")[-1]+suffix)
|
inputString.set(inputFile.split("/")[-1]+suffix)
|
||||||
|
'''if allFiles or files:
|
||||||
|
inputString.set(f"{len(allFiles) or len(files)}"+
|
||||||
|
" files selected (will encrypt).")
|
||||||
|
else:
|
||||||
|
inputString.set(inputFile.split("/")[-1]+suffix)'''
|
||||||
|
|
||||||
# Enable password box, etc.
|
# Enable password box, etc.
|
||||||
passwordInput["state"] = "normal"
|
passwordInput["state"] = "normal"
|
||||||
passwordInput.delete(0,"end")
|
passwordInput.delete(0,"end")
|
||||||
|
passwordLabel["state"] = "normal"
|
||||||
startBtn["state"] = "normal"
|
startBtn["state"] = "normal"
|
||||||
statusString.set("Ready.")
|
statusString.set("Ready.")
|
||||||
|
status["state"] = "enabled"
|
||||||
progress["value"] = 0
|
progress["value"] = 0
|
||||||
|
|
||||||
# File decode error
|
# File decode error
|
||||||
|
@ -222,7 +244,7 @@ def inputSelected(draggedFile):
|
||||||
|
|
||||||
# No file selected, do nothing
|
# No file selected, do nothing
|
||||||
except:
|
except:
|
||||||
inputString.set("Please drag and drop files here.")
|
inputString.set("Drag and drop file(s) and folder(s) into this window.")
|
||||||
resetUI()
|
resetUI()
|
||||||
|
|
||||||
# Focus the dummy button to remove ugly borders
|
# Focus the dummy button to remove ugly borders
|
||||||
|
@ -230,21 +252,40 @@ def inputSelected(draggedFile):
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
working = False
|
working = False
|
||||||
|
|
||||||
|
def clearInputs():
|
||||||
|
dummy.focus()
|
||||||
|
resetUI()
|
||||||
|
|
||||||
# Allow drag and drop
|
# Allow drag and drop
|
||||||
def onDrop(e):
|
def onDrop(e):
|
||||||
|
global working
|
||||||
|
if not working:
|
||||||
inputSelected(e.data)
|
inputSelected(e.data)
|
||||||
tk.drop_target_register(DND_FILES)
|
tk.drop_target_register(DND_FILES)
|
||||||
tk.dnd_bind("<<Drop>>",onDrop)
|
tk.dnd_bind("<<Drop>>",onDrop)
|
||||||
|
|
||||||
# Label that displays selected input file
|
# Label that displays selected input file
|
||||||
inputString = tkinter.StringVar(tk)
|
inputString = tkinter.StringVar(tk)
|
||||||
inputString.set("Please drag and drop files here.")
|
inputString.set("Drag and drop file(s) and folder(s) into this window.")
|
||||||
selectedInput = tkinter.ttk.Label(
|
selectedInput = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=inputString
|
textvariable=inputString
|
||||||
)
|
)
|
||||||
selectedInput.config(background="#f5f6f7")
|
selectedInput.config(background="#ffffff")
|
||||||
selectedInput.place(x=17,y=20)
|
selectedInput.place(x=17,y=16)
|
||||||
|
|
||||||
|
# Clear input files
|
||||||
|
clearInput = tkinter.ttk.Button(
|
||||||
|
tk,
|
||||||
|
text="Clear",
|
||||||
|
command=clearInputs
|
||||||
|
)
|
||||||
|
clearInput.place(x=421,y=14,width=40,height=24)
|
||||||
|
|
||||||
|
separator = tkinter.ttk.Separator(
|
||||||
|
tk
|
||||||
|
)
|
||||||
|
separator.place(x=20,y=36,width=440)
|
||||||
|
|
||||||
# Label that prompts user to enter a password
|
# Label that prompts user to enter a password
|
||||||
passwordString = tkinter.StringVar(tk)
|
passwordString = tkinter.StringVar(tk)
|
||||||
|
@ -253,8 +294,9 @@ passwordLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=passwordString
|
textvariable=passwordString
|
||||||
)
|
)
|
||||||
passwordLabel.place(x=17,y=56)
|
passwordLabel.place(x=17,y=46)
|
||||||
passwordLabel.config(background="#f5f6f7")
|
passwordLabel.config(background="#ffffff")
|
||||||
|
passwordLabel["state"] = "disabled"
|
||||||
|
|
||||||
# A frame to make password input fill width
|
# A frame to make password input fill width
|
||||||
passwordFrame = tkinter.Frame(
|
passwordFrame = tkinter.Frame(
|
||||||
|
@ -262,7 +304,7 @@ passwordFrame = tkinter.Frame(
|
||||||
width=440,
|
width=440,
|
||||||
height=22
|
height=22
|
||||||
)
|
)
|
||||||
passwordFrame.place(x=20,y=76)
|
passwordFrame.place(x=20,y=66)
|
||||||
passwordFrame.columnconfigure(0,weight=10)
|
passwordFrame.columnconfigure(0,weight=10)
|
||||||
passwordFrame.grid_propagate(False)
|
passwordFrame.grid_propagate(False)
|
||||||
# Password input box
|
# Password input box
|
||||||
|
@ -279,8 +321,9 @@ cpasswordLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=cpasswordString
|
textvariable=cpasswordString
|
||||||
)
|
)
|
||||||
cpasswordLabel.place(x=17,y=106)
|
cpasswordLabel.place(x=17,y=96)
|
||||||
cpasswordLabel.config(background="#f5f6f7")
|
cpasswordLabel.config(background="#ffffff")
|
||||||
|
cpasswordLabel["state"] = "disabled"
|
||||||
|
|
||||||
# A frame to make confirm password input fill width
|
# A frame to make confirm password input fill width
|
||||||
cpasswordFrame = tkinter.Frame(
|
cpasswordFrame = tkinter.Frame(
|
||||||
|
@ -288,7 +331,7 @@ cpasswordFrame = tkinter.Frame(
|
||||||
width=440,
|
width=440,
|
||||||
height=22
|
height=22
|
||||||
)
|
)
|
||||||
cpasswordFrame.place(x=20,y=126)
|
cpasswordFrame.place(x=20,y=116)
|
||||||
cpasswordFrame.columnconfigure(0,weight=10)
|
cpasswordFrame.columnconfigure(0,weight=10)
|
||||||
cpasswordFrame.grid_propagate(False)
|
cpasswordFrame.grid_propagate(False)
|
||||||
# Confirm password input box
|
# Confirm password input box
|
||||||
|
@ -329,7 +372,7 @@ def start():
|
||||||
# Check if file already exists (getsize() throws error if file not found)
|
# Check if file already exists (getsize() throws error if file not found)
|
||||||
try:
|
try:
|
||||||
getsize(outputFile)
|
getsize(outputFile)
|
||||||
force = messagebox.askyesno("Warning",overwriteNotice)
|
force = messagebox.askyesno("Confirmation",overwriteNotice)
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
if force!=1:
|
if force!=1:
|
||||||
return
|
return
|
||||||
|
@ -654,43 +697,36 @@ def start():
|
||||||
data = cipher.decrypt(piece)
|
data = cipher.decrypt(piece)
|
||||||
|
|
||||||
# Calculate speed, ETA, etc.
|
# Calculate speed, ETA, etc.
|
||||||
first = False
|
|
||||||
elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001
|
elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001
|
||||||
sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001
|
sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001
|
||||||
previousTime = datetime.now()
|
previousTime = datetime.now()
|
||||||
# Prevent divison by zero
|
|
||||||
if not elapsed:
|
|
||||||
elapsed = 0.1**6
|
|
||||||
percent = done*100/total
|
percent = done*100/total
|
||||||
progress["value"] = percent
|
progress["value"] = percent
|
||||||
rPercent = round(percent)
|
|
||||||
speed = (done/sinceStart)/10**6
|
speed = (done/sinceStart)/10**6 or 0.0001
|
||||||
# Prevent divison by zero
|
|
||||||
if not speed:
|
|
||||||
first = True
|
|
||||||
speed = 0.1**6
|
|
||||||
rSpeed = str(round(speed,2))
|
|
||||||
# Right-pad with zeros to large prevent layout shifts
|
|
||||||
while len(rSpeed.split(".")[1])!=2:
|
|
||||||
rSpeed += "0"
|
|
||||||
eta = round((total-done)/(speed*10**6))
|
eta = round((total-done)/(speed*10**6))
|
||||||
|
|
||||||
# Seconds to minutes if seconds more than 59
|
# Seconds to minutes if seconds more than 59
|
||||||
if eta>=60:
|
if eta>=60:
|
||||||
|
# Set blank ETA if just starting
|
||||||
|
if sinceStart<0.5:
|
||||||
|
eta = "..."
|
||||||
|
else:
|
||||||
eta = f"{eta//60}m {eta%60}"
|
eta = f"{eta//60}m {eta%60}"
|
||||||
if isinstance(eta,int) or isinstance(eta,float):
|
if isinstance(eta,int) or isinstance(eta,float):
|
||||||
if eta<0:
|
if eta<0:
|
||||||
eta = 0
|
eta = 0
|
||||||
# If it's the first round and no data/predictions yet...
|
|
||||||
if first:
|
|
||||||
statusString.set("...% at ... MB/s (ETA: ...s)")
|
|
||||||
else:
|
|
||||||
# Update status
|
# Update status
|
||||||
info = f"{rPercent}% at {rSpeed} MB/s (ETA: {eta}s)"
|
info = f"{percent:.0f}% at {speed:.2f} MB/s (ETA: {eta}s)"
|
||||||
|
|
||||||
if reedsolo and mode=="decrypt" and reedsoloFixedCount:
|
if reedsolo and mode=="decrypt" and reedsoloFixedCount:
|
||||||
eng = "s" if reedsoloFixedCount!=1 else ""
|
tmp = "s" if reedsoloFixedCount!=1 else ""
|
||||||
info += f", fixed {reedsoloFixedCount} corrupted byte{eng}"
|
info += f", fixed {reedsoloFixedCount} corrupted byte{tmp}"
|
||||||
if reedsolo and mode=="decrypt" and reedsoloErrorCount:
|
if reedsolo and mode=="decrypt" and reedsoloErrorCount:
|
||||||
info += f", {reedsoloErrorCount} MB unrecoverable"
|
info += f", {reedsoloErrorCount} MB unrecoverable"
|
||||||
|
|
||||||
statusString.set(info)
|
statusString.set(info)
|
||||||
|
|
||||||
# Increase done and write to output
|
# Increase done and write to output
|
||||||
|
@ -750,6 +786,7 @@ def start():
|
||||||
|
|
||||||
# Reset variables and UI states
|
# Reset variables and UI states
|
||||||
resetUI()
|
resetUI()
|
||||||
|
status["state"] = "normal"
|
||||||
inputFile = ""
|
inputFile = ""
|
||||||
outputFile = ""
|
outputFile = ""
|
||||||
password = ""
|
password = ""
|
||||||
|
@ -853,16 +890,20 @@ def resetUI():
|
||||||
adArea["state"] = "normal"
|
adArea["state"] = "normal"
|
||||||
adArea.delete("1.0",tkinter.END)
|
adArea.delete("1.0",tkinter.END)
|
||||||
adArea["state"] = "disabled"
|
adArea["state"] = "disabled"
|
||||||
|
adLabel["state"] = "disabled"
|
||||||
startBtn["state"] = "disabled"
|
startBtn["state"] = "disabled"
|
||||||
passwordInput["state"] = "normal"
|
passwordInput["state"] = "normal"
|
||||||
passwordInput.delete(0,"end")
|
passwordInput.delete(0,"end")
|
||||||
passwordInput["state"] = "disabled"
|
passwordInput["state"] = "disabled"
|
||||||
|
passwordLabel["state"] = "disabled"
|
||||||
cpasswordInput["state"] = "normal"
|
cpasswordInput["state"] = "normal"
|
||||||
cpasswordInput.delete(0,"end")
|
cpasswordInput.delete(0,"end")
|
||||||
cpasswordInput["state"] = "disabled"
|
cpasswordInput["state"] = "disabled"
|
||||||
cpasswordString.set("Confirm password:")
|
cpasswordString.set("Confirm password:")
|
||||||
|
cpasswordLabel["state"] = "disabled"
|
||||||
|
status["state"] = "disabled"
|
||||||
progress["value"] = 0
|
progress["value"] = 0
|
||||||
inputString.set("Please drag and drop files here.")
|
inputString.set("Drag and drop file(s) and folder(s) into this window.")
|
||||||
keepBtn["state"] = "normal"
|
keepBtn["state"] = "normal"
|
||||||
keep.set(0)
|
keep.set(0)
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
|
@ -891,8 +932,9 @@ adLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=adLabelString
|
textvariable=adLabelString
|
||||||
)
|
)
|
||||||
adLabel.place(x=17,y=158)
|
adLabel.place(x=17,y=148)
|
||||||
adLabel.config(background="#f5f6f7")
|
adLabel.config(background="#ffffff")
|
||||||
|
adLabel["state"] = "disabled"
|
||||||
|
|
||||||
# Frame so metadata text box can fill width
|
# Frame so metadata text box can fill width
|
||||||
adFrame = tkinter.Frame(
|
adFrame = tkinter.Frame(
|
||||||
|
@ -900,7 +942,7 @@ adFrame = tkinter.Frame(
|
||||||
width=440,
|
width=440,
|
||||||
height=100
|
height=100
|
||||||
)
|
)
|
||||||
adFrame.place(x=20,y=178)
|
adFrame.place(x=20,y=168)
|
||||||
adFrame.columnconfigure(0,weight=10)
|
adFrame.columnconfigure(0,weight=10)
|
||||||
adFrame.grid_propagate(False)
|
adFrame.grid_propagate(False)
|
||||||
|
|
||||||
|
@ -923,7 +965,7 @@ keepBtn = tkinter.ttk.Checkbutton(
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
command=lambda:dummy.focus()
|
command=lambda:dummy.focus()
|
||||||
)
|
)
|
||||||
keepBtn.place(x=18,y=290)
|
keepBtn.place(x=18,y=280)
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Check box for securely erasing original file
|
# Check box for securely erasing original file
|
||||||
|
@ -936,7 +978,7 @@ eraseBtn = tkinter.ttk.Checkbutton(
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
command=lambda:dummy.focus()
|
command=lambda:dummy.focus()
|
||||||
)
|
)
|
||||||
eraseBtn.place(x=18,y=310)
|
eraseBtn.place(x=18,y=300)
|
||||||
eraseBtn["state"] = "disabled"
|
eraseBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Check box for Reed Solomon
|
# Check box for Reed Solomon
|
||||||
|
@ -949,7 +991,7 @@ rsBtn = tkinter.ttk.Checkbutton(
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
command=lambda:dummy.focus()
|
command=lambda:dummy.focus()
|
||||||
)
|
)
|
||||||
rsBtn.place(x=18,y=330)
|
rsBtn.place(x=18,y=320)
|
||||||
rsBtn["state"] = "disabled"
|
rsBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Frame so start button can fill width
|
# Frame so start button can fill width
|
||||||
|
@ -958,7 +1000,7 @@ startFrame = tkinter.Frame(
|
||||||
width=442,
|
width=442,
|
||||||
height=25
|
height=25
|
||||||
)
|
)
|
||||||
startFrame.place(x=19,y=360)
|
startFrame.place(x=19,y=350)
|
||||||
startFrame.columnconfigure(0,weight=10)
|
startFrame.columnconfigure(0,weight=10)
|
||||||
startFrame.grid_propagate(False)
|
startFrame.grid_propagate(False)
|
||||||
# Start button
|
# Start button
|
||||||
|
@ -977,7 +1019,7 @@ progress = tkinter.ttk.Progressbar(
|
||||||
length=440,
|
length=440,
|
||||||
mode="determinate"
|
mode="determinate"
|
||||||
)
|
)
|
||||||
progress.place(x=20,y=388)
|
progress.place(x=20,y=378)
|
||||||
|
|
||||||
# Status label
|
# Status label
|
||||||
statusString = tkinter.StringVar(tk)
|
statusString = tkinter.StringVar(tk)
|
||||||
|
@ -986,8 +1028,9 @@ status = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=statusString
|
textvariable=statusString
|
||||||
)
|
)
|
||||||
status.place(x=17,y=416)
|
status.place(x=17,y=406)
|
||||||
status.config(background="#f5f6f7")
|
status.config(background="#ffffff")
|
||||||
|
status["state"] = "disabled"
|
||||||
|
|
||||||
# Credits :)
|
# Credits :)
|
||||||
hint = "Created by Evan Su. Click for details and source."
|
hint = "Created by Evan Su. Click for details and source."
|
||||||
|
@ -999,8 +1042,8 @@ credits = tkinter.ttk.Label(
|
||||||
cursor="hand2"
|
cursor="hand2"
|
||||||
)
|
)
|
||||||
credits["state"] = "disabled"
|
credits["state"] = "disabled"
|
||||||
credits.config(background="#f5f6f7")
|
credits.config(background="#ffffff")
|
||||||
credits.place(x=17,y=446)
|
credits.place(x=17,y=436)
|
||||||
source = "https://github.com/HACKERALERT/Picocrypt"
|
source = "https://github.com/HACKERALERT/Picocrypt"
|
||||||
credits.bind("<Button-1>",lambda e:webbrowser.open(source))
|
credits.bind("<Button-1>",lambda e:webbrowser.open(source))
|
||||||
|
|
||||||
|
@ -1012,8 +1055,8 @@ version = tkinter.ttk.Label(
|
||||||
textvariable=versionString
|
textvariable=versionString
|
||||||
)
|
)
|
||||||
version["state"] = "disabled"
|
version["state"] = "disabled"
|
||||||
version.config(background="#f5f6f7")
|
version.config(background="#ffffff")
|
||||||
version.place(x=430,y=446)
|
version.place(x=430,y=436)
|
||||||
|
|
||||||
# Dummy button to remove focus from other buttons
|
# Dummy button to remove focus from other buttons
|
||||||
# and prevent ugly border highlighting
|
# and prevent ugly border highlighting
|
||||||
|
@ -1033,8 +1076,13 @@ def prepare():
|
||||||
|
|
||||||
# Close window only if not encrypting or decrypting
|
# Close window only if not encrypting or decrypting
|
||||||
def onClose():
|
def onClose():
|
||||||
|
global outputFile
|
||||||
if not working:
|
if not working:
|
||||||
tk.destroy()
|
tk.destroy()
|
||||||
|
else:
|
||||||
|
force = messagebox.askyesno("Confirmation",cancelNotice)
|
||||||
|
if force:
|
||||||
|
tk.destroy()
|
||||||
|
|
||||||
# Main application loop
|
# Main application loop
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
|
|
Loading…
Reference in New Issue