#!/usr/bin/env python3
#
# Known bugs:
# - Skip offset and Invert skip aren't grayed out when Field skip is set back to 0
# - solution: trace_add()
# - TAB navigation is flawed
# TODO: gray out Stroke color with Stroke width reset to 0
# TODO: more importantly: reactivate widgets on non-zero value after previous 0
# TODO: add a few warnings such as about unknown type < user typo
# TODO: tart up the help text (bold headers for example)
# TODO: bonus window help button: show BONUSTEXT
# TODO: (complicated) make changes in presets (addition, deletion) show immediately
###################################################################################################
# TODO: update this:
# Behavior on exit:
#
# Ok
# Save settings as presets?
# Yes save (if necessary) and exit
# (no changes) exit
# (changes)
# -------------------------------------------------------------------------------------------------
# Alt: Save prests button
# -------------------------------------------------------------------------------------------------
# Presets enter a name
# OK write presets and exit
# Cancel exit
# -------------------------------------------------------------------------------------------------
# Message: Saving settings as cf.timestamp
# OK save and exit
# Cancel exit
# -------------------------------------------------------------------------------------------------
# No exit
# Cancel stay in prog
#
# Cancel
# Exit RGBScript configuration?
# OK exit
# Cancel stay in prog
###################################################################################################
import tkinter as tk
from tkinter import messagebox
from tkinter import filedialog as fd
import os, sys
# Paths are in principle relative to the CWD.
CWD = os.getcwd()
# RGBScript will read this config if import of this GUI module succeeds.
# If it doesn't, rgbscript.cf is read instead.
CONFIGFILE = 'rgbscriptgui.cf'
# Location of presets files.
PRESETSDIR = 'rgbscript-presets'
# ==================================================================================================
# ==================================================================================================
# SPECIFIC SECTION
# GLOBALS
# MAIN SETTINGS
WINDOWSIZE = '734x528+40+40'
# Show frame borders?
FRAMEBORDERS = 0
# Object dictionaries.
# These are merely containers and can be safely used for both the main and subwindows.
# Set by main/subwindow code and by makewidgetframe(), config: setframeweights(), read by setupwidgets()
# : .
FRAMEDICT = {}
# Set by inittkvars(), read by manageconfigdict()
# :
TKVARDICT = {}
# Set by setupwidgets(), config: configwidgets()
# :
TKOBJDICT = {}
# For radiobuttons
RBOBJDICT = {}
# Configuration settings
# Set by initdefaultsdict(), passed to settkvars() by restoredefaults(), onpresetselect()
# Simple ': ' pairs retrieved from *CONFIGDICT
DEFAULTSDICT = {}
# Settings read from from the preset file. Set by readpresets()
RECONFIGDICT = {}
# Configuration dictionary for all settings: root plus subwindows.
# = pairs from this dict are written to the config file and presets files.
CONFIGDICT = {}
#
HELPTEXT = """RGBScript Configuration Help
Setting: Input: Description:
Color set defines the set of colors generated
Field settings
Fields integer number of fields to be printed; 0 means 'auto'
Field height integer field height; 0 means 'auto'
Color step integer step by which color values are in/decremented
Turnaround integer limits the number of color steps
Page settings
Margin integer width of the page margins
Show background adds a background layer below the color layer
Background color hex color code default is 0xffffff
Fix striping suppresses stripes in the output
Layout settings
Separation integer field separation in pixels; default is 0
Field skip integer number of fields to skip; default is 0
Skip offset integer with field skip: position to start showing fields from
Invert skip inverts the skipping: only the 'skipped' fields are shown
Color settings
Opacity float value between 0 and 1; default is 1 (no transparency)
Invert colors inverts the colors; does not affect the background color
RBG output turns R-G-B output into R-B-G output
Shuffle colors reshuffles the color list for every run
Print values prints a list of geberated color values
Rotate settings
huerotation integer color rotation in degrees; default is 30
rotatecolor hex color code color to rotate from
Start color color to start from; default is red
Misc
Color file loads the color set from a color file
Bonus code bonus code settings (new window)
"""
# --------------------------------------------------------------------------------------------------
BONUSTEXT = """Bonus code settings
Setting: Input: Description:
showmesquares print squares
showmerectangles print rectangles
showmecircles print circles
showmetriangles print triangles
objectsize integer object size; max size in combination with minobjectsize
minobjectsize integer min object size; 0 renders a fixed
objectstrokewidth integer stroke width in pixels
objectstrokecolor color name stroke color: HTML color name, no hex code (!)
objectopacity integer object opacity (1 means transparency at all)
spillover allow objects to partly exceed page borders?
"""
# Window layout
#
# -------------------------------------------------------------------------------------
# | 0 Color set |
# -------------------------------------------------------------------------------------
# | 1.0 Fields | 2.1 Page | 2.2 Layout |
# | | | |
# -------------------------------------------------------------------------------------
# | 2.0 Colors | 3.1 Rotate | 3.2 Start color |
# | | | |
# | -----------------------------------------------------------
# | [3] | 4.1 Misc |
# -------------------------------------------------------------------------------------
# | 4 Presets |
# -------------------------------------------------------------------------------------
# | 5 Help Ok Cancel |
# -------------------------------------------------------------------------------------
#
# --------------------------------------------------------------------------------------------------
#
# Frames
#
# (row numbers differ from those in the code because of inserted separator frames)
#
# NOTE: THE FOLLOWING IS A LIST AND NOT A DICTIONARY.
# The use of colons can be confusing.
# Frame definitions for grid(). The entire window layout is defined here.
# Syntax:
# :
# : ::
# : [.[.]]
# : h (h for horizontal)
# : v (v for vertical)
# Column is optional; default is 0, which means that 2 is the same as 2.0
# (the latter is probably clearer)
# Rowspan and columnspan are optional; default rowspan/columnspan is 1
# Optional rowspan and/or colspan may appear in an arbitrary order
# Example: 'frame_misc: frame_root: 6.1.h3',
# Will be written as: row=6, column=1, columnspan=3
ROOTFRAMESLIST = [
#
# Row 0
'frame_root: root: : 0.0',
'frame_sets: frame_root: Color sets: 0.0.h3',
'frame_sets_widgets: frame_sets: : 1.0.h3', # relative to row 0: frame_sets
# Row 1
'frame_field: frame_root: Field settings: 1.0',
'frame_field_widgets: frame_field: : 1.0',
'frame_page: frame_root: Page settings: 1.1',
'frame_page_widgets: frame_page: : 1.0',
'frame_layout: frame_root: Layout settings: 1.2',
'frame_layout_widgets: frame_layout: : 1.0',
# Rows 2/3
'frame_color: frame_root: Color settings: 2.0.v2',
'frame_color_widgets: frame_color: : 1.0',
'frame_rotate: frame_root: Rotate settings: 2.1',
'frame_rotate_widgets: frame_rotate: : 1.0',
'frame_start: frame_root: Start color: 2.2',
'frame_start_widgets: frame_start: : 1.0',
'frame_misc: frame_root: Misc: 3.1.h2',
'frame_misc_widgets: frame_misc: : 0.1',
'frame_colorfile: frame_misc_widgets: : 0.0',
'frame_bonus: frame_misc_widgets: : 0.1',
# Row 4
'frame_presets: frame_root: Presets: 4.0.h3',
'frame_presets_widgets: frame_presets: : 0.1.h3',
# Row 5
'frame_help: frame_root: : 5.0',
'frame_unused: frame_root: : 5.1',
'frame_ok: frame_root: : 5.2',
]
# Frame weights, a rather inexact property of grids
# : ':::
# : r (row), c (column)
# : row or columm number (0..n-1)
# Example: 'frame_root0': 'frame_root:r:0:1'
# Becomes: root.grid_configure(0, weight=1)
# There is little code gain in this dict, it just keeps things central
ROOTWEIGHTDICT = {
#
'root_row0': 'frame_root: r: 0: 1',
'root_row1': 'frame_root: r: 1: 1',
'root_row2': 'frame_root: r: 2: 1',
'root_row3': 'frame_root: r: 3: 1',
'root_row4': 'frame_root: r: 4: 1',
'root_row5': 'frame_root: r: 5: 1',
'root_row4_col0': 'frame_root: c: 0: 1',
'root_row5_col0': 'frame_ok: c: 0: 0',
'root_row5_col1': 'frame_ok: c: 1: 1',
'root_row5_col2': 'frame_ok: c: 0: 1',
}
# --------------------------------------------------------------------------------------------------
#
# Widgets
#
# A distinction is made between 'config' amd 'para' widgets. The latter are widgets like OK buttons,
# which play no role in the configuration being generated. Keeping the twi types separated isn't
# strictly necessary, but it may serve to avoid ambiguities. Here, in particular, it makes no sense
# to assign a default value to an OK or Cancel button for example.
#
# Names of widgets and their properties; these correspond directly with config var names
# : ';;;>;;[,, ..[; parent frame
# h (horizontal), v (vertical): how to arrange widgets within a frame
# the text to display on the widget
# widget type: cb (checkbutton), of (openfile), en (entry), rb (radiobutton)
# - button (bt) and menubutton (mb) never appear in ROOTCONFIGDICT
# variable type: i (int), f (float), b (bool), s {string)
# initial (default) value
# - can be a comma-separated list (with radiobutton)
# - in that case, first value is the default
# horizontal padding of the widget; default is 10
# All values except for bools are strings as far as this part of the code is concerned
# - only turnaround is in fact set dynamically: 0xff/colorstep
# - with the default colorstep of 0x33, this equals 5, which is good enough for this purpose
ROOTCONFIGDICT = {
#
'colorset': 'frame_sets_widgets; h; rb; PlainRGB,\
Hybrid,\
LightRGB,\
DarkMYC,\
DarkRGB,\
Web,\
Rotate; s; plainrgb,\
hybrid,\
lightrgb,\
darkmyc,\
darkrgb,\
web,\
rotate',
'fieldnum': 'frame_field_widgets; v; en; Fields:; i; 0',
'fieldheight': 'frame_field_widgets; v; en; Field height:; i; 0',
'colorstep': 'frame_field_widgets; v; en; Color step:; i; 0x33',
'turnaround': 'frame_field_widgets; v; en; Turnaround; i; 5',
'pagemargin': 'frame_page_widgets; v; en; Margin:; i; 0',
'showbackground': 'frame_page_widgets; v; cb; Background; b; no; 4',
'backgroundcolor': 'frame_page_widgets; v; en; Background color; i; 0xffffff',
'fixstriping': 'frame_page_widgets; v; cb; Fix striping; b; no; 4',
'fieldsep': 'frame_layout_widgets; v; en; Field separation; i; 0',
'fieldskip': 'frame_layout_widgets; v; en; Field skip; i; 0',
'skipoffset': 'frame_layout_widgets; v; en; Skip offset; i; 0',
'invertskip': 'frame_layout_widgets; v; cb; Invert skip; b; no; 4',
'fieldopacity': 'frame_color_widgets; v; en; Opacity; f; 1',
'invertcolors': 'frame_color_widgets; v; cb; Invert colors; b; no',
'rbgoutput': 'frame_color_widgets; v; cb; RBG output; b; no',
'shufflecolors': 'frame_color_widgets; v; cb; Shuffle colors; b; no',
'printvalues': 'frame_color_widgets; v; cb; Print values; b; no',
'huerotation': 'frame_rotate_widgets; v; en; Rotation; i; 30',
'rotatecolor': 'frame_rotate_widgets; v; en; Color:; s;',
'startcolor': 'frame_start_widgets; v; rb; Red,\
Green,\
Blue; s; red,\
green,\
blue',
'colorfile': 'frame_colorfile; h; fs; Color file; s;',
}
# Same format: widgets which do not correspond with config variables
# Usually buttons and menubuttons
# bt (button), mb (menubutton)
# Minor TODO: as above, but with orientation
ROOTPARADICT = {
#
'bonus': 'frame_bonus; h; bt; Bonus code; ; ; 0',
'presets': 'frame_presets_widgets; h; mb; Select presets; ; ; 20',
'savepresets': 'frame_presets_widgets; h; bt; Save presets; ; ; 20',
'defaultpresets': 'frame_presets_widgets; h; bt; Restore defaults; ; ; 20',
'deletepresets': 'frame_presets_widgets; h; bt; Delete presets; ; ; 20',
'help': 'frame_help; h; bt; Help; ; ; ',
'ok': 'frame_ok; h; bt; OK; ; ; 4.E',
'cancel': 'frame_ok; h; bt; Cancel; ; ; 4.E',
}
# Concatenate both dictionaries above into a new one
# This one is used for creating widgets, but not for handling RGBScript configuration
ROOTWIDGETSDICT = dict(ROOTCONFIGDICT)
ROOTWIDGETSDICT.update(ROOTPARADICT)
# --------------------------------------------------------------------------------------------------
# Widget dependencies
# Widgets to be disabled on startup
ROOTDISABLED = [
'huerotation',
'rotatecolor',
'backgroundcolor',
'skipoffset',
'invertskip',
]
###################################################################################################
###################################################################################################
# IN THE CASE OF CONFLICTING STATES PRESENT, ALL RADIO BUTTUNS MUST BE LISTED
###################################################################################################
###################################################################################################
# Widgets to be toggled, depending on the state of some other widget
# Parent widget: the widget on which other widgets
# Dependent widgets: comma-separated list of names
# : |-,...]
# : enable; -
# Row 0
'frame_bonuswindow: bonuswindow: : 0.0',
'frame_bwin_main: frame_bonuswindow: : 0.0',
'frame_bwin_shapes: frame_bwin_main: Shapes: 0.0',
'frame_bwin_shapes_widgets: frame_bwin_shapes: : 1.0',
'frame_bwin_properties: frame_bwin_main: Properties: 0.1',
'frame_bwin_properties_widgets: frame_bwin_properties: : 1.0',
'frame_bwin_ok: frame_bonuswindow: : 2.0',
]
# Weight dict for bonus window frames
BONUSWEIGHTDICT = {
#
'bwin_row0': 'frame_bonuswindow: r: 0: 1',
'bwin_row1': 'frame_bonuswindow: r: 1: 1',
'bwin_row2': 'frame_bonuswindow: r: 2: 1',
}
# --------------------------------------------------------------------------------------------------
#
# Widgets
#
# Widget dicts for bonus window
BONUSCONFIGDICT = {
#
# 'blah': 'frame_bwin_widgets; h; bt; Blah; ; ;',
'showmesquares': 'frame_bwin_shapes_widgets; v; cb; Squares; b; no;',
'showmerectangles': 'frame_bwin_shapes_widgets; v; cb; Rectangles; b; no;',
'showmecircles': 'frame_bwin_shapes_widgets; v; cb; Circles; b; no;',
'showmetriangles': 'frame_bwin_shapes_widgets; v; cb; Triangles; b; no;',
'objectsize': 'frame_bwin_properties_widgets; v; en; Size (max size); i; 10;',
'minobjectsize': 'frame_bwin_properties_widgets; v; en; Min size; i; 0;',
'objectstrokewidth': 'frame_bwin_properties_widgets; v; en; Stroke width; i; 0;',
'objectstrokecolor': 'frame_bwin_properties_widgets; v; en; Stroke color; s; white;',
'objectopacity': 'frame_bwin_properties_widgets; v; en; Opacity; f; 1;',
'spillover': 'frame_bwin_properties_widgets; v; cb; Spillover; b; no;',
}
BONUSPARADICT = {
#
'bwin_ok': 'frame_bwin_ok; h; bt; OK; b; no;',
}
BONUSWIDGETSDICT = dict(BONUSCONFIGDICT)
BONUSWIDGETSDICT.update(BONUSPARADICT)
# --------------------------------------------------------------------------------------------------
#
# Widget dependencies
#
# Widgets disabled on startup
BONUSDISABLED = [
'objectstrokecolor',
]
# Widget enable/disable dependencies
BONUSENABLEDICT = {
'objectstrokewidth': 'objectstrokecolor',
}
# ==================================================================================================
# ==================================================================================================
# ==================================================================================================
# SPECIFIC FUNCTIONS
# These apply to the configuration and operation of a specific root window
# This function binds specific actions to widgets
def wconfigspecifics(wname, wtype, var=None, enabledict=ROOTENABLEDICT, disabledlist=ROOTDISABLED):
global TKOBJDICT # apparently not needed
wobj = TKOBJDICT[wname]
if (wtype == 'en'):
if (wname == 'fieldskip'):
# Duplicate (configwidgets())
#wobj.configure(command=lambda: togglefswidgets(wname, enabledict=enabledict))
##############################################################################################
##############################################################################################
# TODO: won't do the trick
wobj.bind('', lambda event: togglefswidgets(wname, enabledict=enabledict))
##############################################################################################
##############################################################################################
elif (wtype == 'bt'):
if (wname == 'bonus'):
wobj.configure(command=onbonusbutton)
wobj.bind('', onbonusbutton)
elif (wname == 'savepresets'):
wobj.configure(command=savepresets)
wobj.bind('', savepresets)
elif (wname == 'defaultpresets'):
wobj.configure(command=restoredefaults)
wobj.bind('', restoredefaults)
elif (wname == 'deletepresets'):
wobj.configure(command=removepresetspaths)
wobj.bind('', removepresetspaths)
elif (wname == 'help'):
wobj.configure(command=onhelpbutton)
wobj.bind('', onhelpbutton)
elif (wname == 'ok'):
wobj.configure(command=onokbutton)
wobj.bind('', onokbutton)
elif (wname == 'cancel'):
wobj.configure(command=oncancelbutton)
wobj.bind('', oncancelbutton)
# **** Odd duckling: subwindow ************
elif (wname == 'bwin_ok'):
wobj.configure(command=onbonusokbutton)
wobj.bind('', onbonusokbutton)
# *****************************************
elif (wtype == 'fs'):
if (wname == 'colorfile'):
wobj.configure(command=selectcolorfile)
wobj.bind('', selectcolorfile)
elif (wtype == 'mb'):
if (wname == 'presets'):
setpresets(wname, wobj)
wobj.configure(takefocus=True) # it still won't show any signs of focus, but space bar / arrow keys work
if wname in disabledlist:
wobj.configure(state=tk.DISABLED)
return()
# Tkinter Open File Dialog https://www.pythontutorial.net/tkinter/tkinter-open-file-dialog/
# File selector for color file (e.g. colors.txt)
def selectcolorfile(event=None):
filetypes = (('Text files', '*.txt'), ('All files', '*.*'))
rgbfile = fd.askopenfilename(title='Color file', initialdir=CWD, filetypes=filetypes)
# MAYBE
#tk.messagebox.showinfo(title='Color file', message=filename)
return(rgbfile)
def onhelpbutton(event=None):
textwindow(1200, 1200, 'RGBScript Config Help', HELPTEXT)
return()
def byebye():
if messagebox.askokcancel("Exit", "Exit RGBScript configuration?"):
root.after(200, root.destroy)
# Handle OK: write config if changes were made
def onokbutton(event=None):
manageconfigdict()
writeconfig(CONFIGFILE)
root.after(200, root.destroy)
# Handle Cancel: close window
def oncancelbutton(event=None):
byebye()
# ==================================================================================================
# ==================================================================================================
# GENERAL SECTION
# ==================================================================================================
# FUNCTION DEFINITIONS
# --------------------------------------------------------------------------------------------------
# GENERAL/MISC
# Stuff that doesn't go elswhere
####################################################################################################
####################################################################################################
# TODO: bold headers
def textwindow(xsize, ysize, ttl, txt):
nw = tk.Toplevel(root)
nw.title(ttl)
nw.geometry("%sx%s" % (xsize, ysize))
#tk.Label(nw, text = '
' + txt + '
', wraplength=xsize, justify="left").pack()
textbox = tk.Text(nw)
textbox.configure(width=xsize, height=ysize, padx=100, pady=100, borderwidth=4, relief='groove', background='white')
textbox.insert('1.0', txt)
#text.insert(END, "Bye Bye.....") # append
textbox.pack()
tk.Button(nw, text="OK", command=nw.destroy).pack()
####################################################################################################
####################################################################################################
def logfatalerror(txt):
print('Fatal error: %s' % txt)
root.after(200, root.destroy)
def logerror(txt):
print('Error: %s' % txt)
return()
def logwarning(txt):
print('Warning: %s' % txt)
return()
def log(txt):
print('%s' % txt)
return()
def yesno(val):
if (val == 0): return('no')
return('yes')
def onezero(val):
if (val == 'no'): return(0)
return(1)
def firstvalue(val):
vlist = splitstr(val, ',')
first = vlist[0].strip()
return(first)
def hasindex(lst, index):
try:
lst[index]
return(True)
except IndexError:
return(False)
def splitstr(string, sep):
lst = string.split(sep)
return(lst)
# Strip all items of a list
def striplistitems(lst):
stripped = []
for x in lst:
y = x.strip()
stripped.append(y)
return(stripped)
def askconfirm(ttext, qtext):
rv = messagebox.askyesno(title=ttext, message=qtext)
return(rv)
def showmessage(ttext, mtext):
tk.messagebox.showinfo(title=ttext, message=mtext)
return()
def showwarning(ttext, wtext):
tk.messagebox.showwarning(title=ttext, message=wtext)
return()
def showerror(ttext, etext):
tk.messagebox.showwarning(title=ttext, message=etext)
return()
# --------------------------------------------------------------------------------------------------
# FRAMES
# Set up frames within a window.
# Not all args in these functions are necessarily used, but they can be useful for logging and debugging
# Make a widget frame. Very similar in structure to the print*() functions in TKINTER WIDGET OBJECTS.
# 'parent' is the parent frame name, 'pobj' is the parent frame object.
def makewidgetframe(frame, parent, pobj, row, col, rspan, cspan):
global FRAMEDICT # not necessary (??)
if (FRAMEBORDERS == 1):
FRAMEDICT[frame] = tk.Frame(pobj, bd=0, relief="sunken", highlightbackground="blue", highlightthickness=2)
else:
FRAMEDICT[frame] = tk.Frame(pobj, bd=0, relief="sunken")
FRAMEDICT[frame].grid(row=row, column=col, rowspan=rspan, columnspan=cspan, sticky="nsew", padx=2, pady=2)
return()
# A frentry is an entry from the frames list
# The frame name frname is the first item in list
def getframename(frentry):
frlist = splitstr(frentry, ':')
frlist = striplistitems(frlist)
frname = frlist[0]
return(frname)
# Get the first matching entry from the ROOTFRAMESLIST _list_
# Looks for ':'
def getframedef(frname, framelist=ROOTFRAMESLIST):
listentry = ''
for listentry in framelist:
#frdfn = frdfn.replace(' ', '').replace(' ', '')
if listentry.startswith('%s:' % frname):
break
return(listentry)
def buildframe(frname, parent='', framelist=ROOTFRAMESLIST):
#
frdef = getframedef(frname, framelist) # 'frame_blah: frame_root: Blah: 6.1.h2'
frlist = splitstr(frdef, ':')
frlist = striplistitems(frlist) # ['frame_blah','frame_root','Blah','6.1.h2']
# : [: ]
pname = frlist[1] # frame_root
frargs = frlist[3] # 6.1.h2
frlayout = frargs.split('.') # 6 1 h2
# Non-root parent should have already been created by this same routine
# Expects a root or subwindow object to be present in FRAMEDICT, placed there by main code
pobj = FRAMEDICT[pname]
# Row, column, span string
frcol = '0'
frspanstr = ', ' #
if hasindex(frlayout, 2):
frspanstr = frlayout[2].strip() # h2
if hasindex(frlayout, 1):
if ('h' in frlayout[1] or 'v' in frlayout[1]):
frspanstr = frlayout[1].strip() # h2
else:
frcol = frlayout[1].strip() # 1
frrow = frlayout[0] # 6
# Row span, column span
frrspan = 1
frcspan = 1
while ('h' in frspanstr or 'v' in frspanstr):
if (frspanstr.startswith('h')):
frcspan = frspanstr[1].strip()
frspanstr = frspanstr[2:]
if (frspanstr.startswith('v')):
frrspan = frspanstr[1].strip()
frspanstr = frspanstr[2:]
# if frname.startswith('frame_sep'):
# makeseparator(frname, parent, frrow, frcol, frrspan, frcspan)
# else:
# Create frame and add frame name as key to FRAMEDICT with object as value.
makewidgetframe(frname, pname, pobj, frrow, frcol, frrspan, frcspan)
return()
def setframetitle(frname, framelist=ROOTFRAMESLIST):
frdef = getframedef(frname, framelist=framelist)
frlist = splitstr(frdef, ':')
frlist = striplistitems(frlist)
frparent = frlist[1]
frtitle = frlist[2]
frfont = 'Arial 12 bold'
frxpad = 10
if frtitle: # not all frames have a title
printlabel(frname, frparent, FRAMEDICT[frname], 0, 0, frtitle, frfont, frxpad)
return()
# Weights, for what they're worth..
def setframeweights(weightdict=ROOTWEIGHTDICT):
for key in weightdict:
val = weightdict[key]
wtlist = splitstr(val, ':')
wtlist = striplistitems(wtlist)
# Parent frame name
frname = wtlist[0]
# Frame 'type': row or column
frtype = wtlist[1]
# Row or column number
frgrid = wtlist[2]
# Weight
frwt = wtlist[3]
if (frtype == 'r'):
FRAMEDICT[frname].grid_rowconfigure(frgrid, weight=frwt)
elif (frtype == 'c'):
FRAMEDICT[frname].grid_columnconfigure(frgrid, weight=frwt)
return()
# --------------------------------------------------------------------------------------------------
# WIDGETS
# Place widgets inside the frames created and initialize them with values.
# widget and variable names are identical. wname: widget context; var: variable context
def getwparent(wname, sdict=ROOTWIDGETSDICT):
wlist = splitstr(sdict[wname], ';')
return(wlist[0].strip())
def getwvect(wname, sdict=ROOTWIDGETSDICT):
wlist = splitstr(sdict[wname], ';')
return(wlist[1].strip())
def getwtype(wname, sdict=ROOTWIDGETSDICT):
wlist = splitstr(sdict[wname], ';')
return(wlist[2].strip())
def getwtext(wname, sdict=ROOTWIDGETSDICT):
wlist = splitstr(sdict[wname], ';')
return(wlist[3].strip())
def getvartype(var, sdict=ROOTWIDGETSDICT):
vlist = splitstr(sdict[var], ';')
return(vlist[4].strip())
def getvarval(var, sdict=ROOTWIDGETSDICT):
vlist = splitstr(sdict[var], ';')
return(vlist[5].strip())
def getwlayout(wname, sdict=ROOTWIDGETSDICT):
wlist = splitstr(sdict[wname], ';')
if hasindex(wlist, 6):
return(wlist[6].strip())
return()
# print*(): display a widget. 'frobj' is a frame object.
# Not all args are necessarily used, but they can be useful for logging and debugging
def printlabel(name, parent, frobj, row, col, text, font='TkTextFont', xpad=4, orientation='W'):
lb = tk.Label(frobj, text=text, font=font)
lb.grid(row=row, column=col, sticky=orientation, padx=xpad, pady=4)
return(lb)
def printcheckbutton(name, parent, frobj, row, col, text, xpad=4, orientation='W'):
cb = tk.Checkbutton(frobj, text=' %s' % text)
cb.grid(row=row, column=col, sticky=orientation, padx=xpad, pady=4)
return(cb)
def printentry(name, parent, frobj, row, col, text, font='TkTextFont', xpad=4, orientation='W'):
lb = tk.Label(frobj, text=text, font=font)
lb.grid(row=row, column=col, sticky=orientation, padx=xpad, pady=4)
en = tk.Entry(frobj, width=8)
en.grid(row=row, column=col+1, sticky=orientation, padx=xpad, pady=4)
return(en)
def printbutton(name, parent, frobj, row, col, text, xpad=4, orientation='W'):
bt = tk.Button(frobj, text=text)
bt.grid(row=row, column=col, sticky=orientation, padx=xpad, pady=4)
return(bt)
def printmenubutton(name, parent, frobj, row, col, text, xpad=4, orientation='W'):
mb = tk.Menubutton(frobj, text=text, relief='raised', width=14)
mb.grid(row=row, column=col, sticky=orientation, padx=xpad, pady=10)
mb.menu = tk.Menu(mb, tearoff=0)
mb["menu"] = mb.menu
return(mb)
def printradiobutton(name, parent, frobj, row, col, text, vect, xpad=4, orientation='W'):
textlist = splitstr(text, ',')
textlist = striplistitems(textlist)
rblist = []
count = 0
for txt in textlist:
rb = tk.Radiobutton(frobj)
rb.configure(text=txt)
rb.grid(row=row, column=col, sticky=orientation, padx=xpad, pady=4)
if (vect == 'v'):
row += 1
elif (vect == 'h'):
col += 1
rblist.append(rb)
count += 1
return(rblist)
def setupwidgets(widgetdict=ROOTWIDGETSDICT):
global TKOBJDICT, RBOBJDICT
wcount = 0
wfrobj = ''
for w in widgetdict:
# Get config var attributes from the widgets dict
wparent = getwparent(w, sdict=widgetdict)
wvect = getwvect(w, sdict=widgetdict)
wtype = getwtype(w, sdict=widgetdict)
wtext = getwtext(w, sdict=widgetdict)
wlayout = getwlayout(w, sdict=widgetdict)
wfont = 'TkTextFont'
oldwfrobj = wfrobj
wfrobj = FRAMEDICT[wparent]
if (oldwfrobj != wfrobj):
wcount = 0
if (wlayout):
wllist = splitstr(wlayout, '.')
wllist = striplistitems(wllist)
wxpad = wllist[0]
if hasindex(wllist, 1):
worientation = wllist[1]
else:
wxpad = 10
worientation = 'W'
# Rows and columns are relative to the frame
if (wvect == 'v'):
row, col = wcount, 0
elif (wvect == 'h'):
row, col = 0, wcount
if (wtype == 'lb'):
obj = printlabel(w, wparent, wfrobj, row, col, wtext, wfont, xpad=wxpad)
TKOBJDICT[w] = obj
obj = printlabel(w, wparent, wfrobj, row, col, wtext, wfont, xpad=wxpad)
TKOBJDICT[w] = obj
elif (wtype == 'en'):
obj = printentry(w, wparent, wfrobj, row, col, wtext, wfont, xpad=wxpad)
TKOBJDICT[w] = obj
elif (wtype == 'cb'):
obj = printcheckbutton(w, wparent, wfrobj, row, col, wtext, xpad=wxpad)
TKOBJDICT[w] = obj
elif (wtype == 'bt'):
obj = printbutton(w, wparent, wfrobj, row, col, wtext, orientation=worientation)
TKOBJDICT[w] = obj
elif (wtype == 'fs'):
obj = printbutton(w, wparent, wfrobj, row, col, wtext, xpad=10)
TKOBJDICT[w] = obj
elif (wtype == 'mb'):
obj = printmenubutton(w, wparent, wfrobj, row, col, wtext, xpad=20)
TKOBJDICT[w] = obj
elif (wtype == 'rb'):
objlist = printradiobutton(w, wparent, wfrobj, row, col, wtext, wvect, xpad=wxpad)
TKOBJDICT[w] = objlist
for i in range(len(objlist)):
subw = w + str(i)
RBOBJDICT[subw] = objlist[i]
wcount += 1
return()
# Create Tkinter Tntvar/Stringvar and store it in TKVARDICT.
def inittkvar(wname, val, vtype='str'):
global TKVARDICT # Not really needed apparently
if (vtype == 'str'):
objv = tk.StringVar(None, val)
TKVARDICT[wname] = objv
elif (vtype == 'int'):
objv = tk.IntVar(None, val)
TKVARDICT[wname] = objv
return(objv)
# Initoalize Tkinter objects.
def inittkvars(widgetdict=ROOTWIDGETSDICT):
for w in widgetdict:
# Get config var attributes from the widgets dict
wtype = getwtype(w, sdict=widgetdict)
vtype = getvartype(w, sdict=widgetdict)
vval = getvarval(w, sdict=widgetdict)
# Checkboxes write a boolean value to an IntVar (0|1)
if (vtype == 'b'):
vval = onezero(vval)
intv = inittkvar(w, vval, 'int')
else:
# Radiobutton: first value is the default
if (wtype == 'rb'):
vval = firstvalue(vval)
strv = inittkvar(w, vval)
return()
# Configure a Tkinter object.
def configobj(wname, vtype='str', disabledlist=ROOTDISABLED):
obj = TKOBJDICT[wname]
if (vtype == 'str'):
obj.configure(textvariable=TKVARDICT[wname])
elif (vtype == 'int'):
obj.configure(variable=TKVARDICT[wname])
if wname in disabledlist:
obj.configure(state=tk.DISABLED)
return()
# Configure widgets.
def configwidgets(cfaction='config', widgetdict=ROOTWIDGETSDICT, enabledict=ROOTENABLEDICT, disabledlist=ROOTDISABLED):
for w in widgetdict:
wtype = getwtype(w, sdict=widgetdict)
vval = getvarval(w, sdict=widgetdict)
# Process according to object type
if (wtype == 'lb'): # Label
configobj(w, disabledlist=disabledlist)
elif (wtype == 'en'): # Entry
configobj(w, disabledlist=disabledlist)
if w in enabledict:
obj = TKOBJDICT[w]
############################################################################################
############################################################################################
# TODO: will do the trick with TAB, but not a second time
obj.configure(validate='focusout', validatecommand=lambda x = w: togglefswidgets(x, enabledict=enabledict))
############################################################################################
############################################################################################
elif (wtype == 'cb'): # Checkbutton
configobj(w, 'int', disabledlist=disabledlist)
obj = TKOBJDICT[w]
obj.configure(command=lambda x = w: togglecbwidgets(x, enabledict=enabledict))
elif (wtype == 'rb'): # Radiobutton
objlist = TKOBJDICT[w]
# Radio button objects are indexed, e.g. RBOBJDICT[colorset0], i.e. one entry per button.
# The Tkinter variable is named after the widget, e.g. TKVARDICT[colorset], i.e. a single
# Tkinter var bound to all buttons.
for i in range(len(objlist)):
vlist = splitstr(vval, ',') # for radiobuttons value is a comma-separated list
vlist = striplistitems(vlist)
thisval = vlist[i]
subw = w + str(i) # entry in RBOBJDICT, e.g. colorset0, colorset1, ..
enablename = w + ':' + thisval # entry in *ENABLEDICT, e.g. colorset:plainrgb, colorset:hybrid, ..
obj = RBOBJDICT[subw]
obj.configure(variable=TKVARDICT[w], value=thisval, command=lambda x = enablename: togglerbwidgets(x, widgetdict=widgetdict, enabledict=enabledict))
else:
# Specifics (commands etc.) moved out of this function
# Button ('bt'), menubutton, ('mb'), fileselect ('fs') require non-standard treatment
wconfigspecifics(w, wtype, enabledict=enabledict, disabledlist=disabledlist)
return()
def gettkval(wname):
objv = TKVARDICT[wname].get()
return(objv)
# --------------------------------------------------------------------------------------------------
# WIDGET DEPENDENCIES
# What follows is a feast of double and triple negations: if widget A has value X, widgets B
# and C must either be enabled or disabled. If widget A does not have value X, the behavior
# is consequently inverted. It gets a bit complex with radio buttons.
# Enable a widget.
def enablewidget(wname):
obj = TKOBJDICT[wname]
obj.configure(state=tk.NORMAL)
return()
# Disable a widget.
def disablewidget(wname):
obj = TKOBJDICT[wname]
obj.configure(state=tk.DISABLED)
return()
# Decide whether to enable or disable a widget.
def togglewidgets(wdglist, action='enable'):
for w in wdglist:
invert = 0
if w.startswith('-'):
w = w[1:]
invert = 1
if (action == 'enable'):
if (invert):
disablewidget(w)
else:
enablewidget(w)
elif (action == 'disable'):
if (invert):
enablewidget(w)
else:
disablewidget(w)
return()
# Enable/disable widgets dependent on fieldskip [entry] value (applies to StringVars).
###################################################################################################
###################################################################################################
# TODO: VALUE IS READ ONLY ONCE ???????????????????????????????????????????????????????????????????
# https://www.codegrepper.com/code-examples/python/how+to+reset+entry+in+tkinter
# “how to reset entry in tkinter” Code Answer
# https://newbedev.com/how-to-reset-entry-in-tkinter-code-example
# how to reset entry in tkinter code example
#
# widget.delete(0, END)
#
# https://coderslegacy.com/python/tkinter-clear-entry/
# Tkinter – Clear Entry box
#
# myentry.delete(0, 'end') # requires a button
#
###################################################################################################
###################################################################################################
# TODO: respond on
###################################################################################################
###################################################################################################
# Enable/disable field skip wodgets
def togglefswidgets(wname, enabledict=ROOTENABLEDICT):
entry = enabledict[wname]
wdglist = splitstr(entry, ',')
wdglist = striplistitems(wdglist)
newvalue = TKVARDICT[wname].get()
# Alternative to validating input: disable for non-numeric input
# -------------------- ROUTINE-SPECIFIC ------------------------------
if (newvalue.isnumeric()):
if (int(newvalue) > 0):
# --------------------------------------------------------------------
togglewidgets(wdglist)
else:
togglewidgets(wdglist, 'disable')
TKVARDICT[wname].set(newvalue)
return()
###################################################################################################
###################################################################################################
# TODO: CONTAINS CONFIG SPECIFICS ('showbackground')
###################################################################################################
###################################################################################################
# Enable/disable widgets dependent on some checkbutton (applies to IntVars).
def togglecbwidgets(wname, enabledict=ROOTENABLEDICT):
if wname in enabledict:
entry = enabledict[wname]
wdglist = splitstr(entry, ',')
wdglist = striplistitems(wdglist)
if (wname == 'showbackground'):
newvalue = TKVARDICT[wname].get()
# -------------------- ROUTINE-SPECIFIC --------------------------
if (newvalue): # button is checked
# ----------------------------------------------------------------
togglewidgets(wdglist)
else:
togglewidgets(wdglist, 'disable')
return()
# Return the combined specs of all inactive buttons.
def togglerbinvert(wname, current, widgetdict=ROOTWIDGETSDICT, enabledict=ROOTENABLEDICT):
wstr = getvarval(wname, sdict=widgetdict) # comma-separated list of button names
# e.g. plainrgb,hybrid,lightrgb,darkmyc,darkrgb,web,rotate
vlist = splitstr(wstr, ',')
vlist = striplistitems(vlist)
vlist.remove(current) # delete current value from list, e.g. rotate
# e.g. plainrgb,hybrid,lightrgb,darkmyc,darkrgb,web
invertlist = []
# Read the specs for all values except the CURRENT
for v in vlist:
dictname = wname + ':' + v # e.g. colorset:plainrgb
if dictname in enabledict: # see if the value has a spec
entry = enabledict[dictname]
wdglist = splitstr(entry, ',')
wdglist = striplistitems(wdglist)
# This could be done in a single list assignment (??)
for w in wdglist:
inv = w
if not inv in invertlist: # no duplicates
invertlist.append(inv)
return(invertlist)
# Enable/disable widgets dependent on some radio list (applies to StringVar).
# This routine is oblivious to the PREVIOUS selection.
# This is a fairly simple procedure, but with a rather complex implementation.
# Essentially: 'untoggle' everything listed for a particular radio set, then
# toggle what is needed for the active button.
def togglerbwidgets(wstr, widgetdict=ROOTWIDGETSDICT, enabledict=ROOTENABLEDICT):
# Data for the current (newly pressed, active) button
wlist = splitstr(wstr, ':') # e.g. colorset:rotate
wname = wlist[0].strip() # colorset
wval = wlist[1].strip() # rotate
wdglist = []
# see if anything is specified for the active button, e.g. 'colorset:rotate'
if wstr in enabledict:
# get the list of toggles for the active button
activelist = enabledict[wstr] # E.g. 'huerotation, rotatecolor , -colorstep, -rbgoutput'
wdglist = splitstr(activelist, ',')
wdglist = striplistitems(wdglist) # E.g. ['huerotation', 'rotatecolor', '-colorstep', '-rbgoutput']
# List of ALL items listed for this widget in the enable dict.
# These are to be inverted for ALL BUT THE ACTIVE WIDGET.
# E.g. ['turnaround', 'huerotation', 'rotatecolor', '-colorstep', '-rbgoutput']
##################################################################################################
# THERE MAY BE CONFLICTING ITEMS IN THIS LIST, e.g. 'turnaround, -turnaroud, huerotation, ..'
##################################################################################################
invertlist = togglerbinvert(wname, wval, widgetdict=widgetdict)
# Negate all these states in a sweeping way. This basically turns everything off.
# E.g. EFFECTIVELY: ['-turnaround', '-huerotation', '-rotatecolor', 'colorstep', 'rbgoutput']
togglewidgets(invertlist, 'disable')
# Turn the specified states on again for the active button
# E.g. for rotate: ['huerotation, rotatecolor , -colorstep, -rbgoutput']
# Note that 'turnaround' is left disabled (!)
togglewidgets(wdglist)
return()
# --------------------------------------------------------------------------------------------------
# CONFIGURATION SETTINGS
# These functions form the main engine of the script; everything else is basically initialization
# - if a setting matches the default, no need to write it to the config file
# - in addition: if a subwindow is opened for a second time, initialize it with the previous values
# Store initial (default) ': ' pairs read from a config dict in the defaults dict
# DEFAULTSDICT exisis merely for convenience.
def initdefaultsdict(configdict=ROOTCONFIGDICT):
global DEFAULTSDICT
for var in configdict:
val = getvarval(var, sdict=configdict)
DEFAULTSDICT[var] = val
return()
# Reinitialze a subwindow if opened more than once. Note that configdict=ROOTCONFIGDICT is a mere
# placeholder, since the root window is never shown twice.
def reinitsubwindow(configdict=ROOTCONFIGDICT):
for var in configdict:
if var in CONFIGDICT:
val = CONFIGDICT[var]
# The unavoidable yes|no -> 1|0 conversion
vtype = getvartype(var, sdict=configdict)
if (vtype == 'b'): val = onezero(val)
TKVARDICT[var].set(val)
return()
# Record non-default config settings (changed by user). Only these settings will be written to the
# new config file. Other settings (defaults) can be considered 'commented out'.
def manageconfigdict(configdict=ROOTCONFIGDICT):
global CONFIGDICT
# Needed in order to let caller know changes were made
rv = 0
for var in configdict:
# String values are written python style, in single quotes.
# Numeric values are written without quotes.
vtype = getvartype(var, sdict=configdict)
if (vtype == 'i' or vtype == 'f'):
quote = ''
else:
quote = "'"
# COMPARE default value from DEFAULTSDICT with current value from TKVARDICT
# - if the values differ, store them in CONFIGDICT.
# - if a value has been reset to the default, remove the entry from CONFIGDICT
default = DEFAULTSDICT[var] # Same thing: vval = getvarval(var, sdict=configdict)
# Radio box 'types' have multiple possible values; first is default
if (',' in default): default = firstvalue(default)
# TKVARDICT contains Tkinter OBJECTS holding the CURRENT values.
# Need to convert value to str for comparison.
curval = str(TKVARDICT[var].get())
# Convert bools: 0|1 -> no|yes
if (vtype == 'b'): curval = yesno(int(curval))
# Add only the vars whose values differ from the defaults to config dict.
if (curval == default):
# Delete item if value matches the default.
if var in CONFIGDICT:
CONFIGDICT.pop(var)
else:
CONFIGDICT[var] = quote + curval + quote
rv = 1
return(rv)
# Build the output text and write it to the config file.
# This routine is also used for preset files.
def writeconfig(cpath):
printtext = ''
for var in CONFIGDICT:
printtext = printtext + '
' + var + ' = ' + CONFIGDICT[var]
try:
with open(cpath, mode='w') as f:
f.writelines(printtext + '
')
except OSError:
titletext='Error saving configuration'
warntext = 'Saving configuration to %s failed' % pfile
showwarning(titletext, warntext)
return()
# --------------------------------------------------------------------------------------------------
# PRESETS
# Load/save/delete presets.
# To avoid confusion:
# - a presets file is a preset file's basename here, which matches the name of the preset
# - a presets path is the file's absulute pathname
# Set a value to current in TKVARDICT. Basically the same as TKVARDICT[var].set(val) in caller.
# Therefore some extra overhead without much code gain, but consistent and clear.
def settkvar(wname, val):
global TKVARDICT # Not needed (???)
objv = TKVARDICT[wname].set(val)
return()
# Initialize Tkinter variables (IntVar/StringVar).
# These correspond with a widget's state.
def settkvars(configdict, widgetdict=ROOTWIDGETSDICT):
for w in configdict:
if w in widgetdict:
# Get config var attributes from the widgets dict
wtype = getwtype(w, sdict=widgetdict)
vtype = getvartype(w, sdict=widgetdict)
vval = configdict[w]
# Checkboxes write a boolean value to an IntVar (0|1)
if (vtype == 'b'): vval = onezero(vval)
if (wtype == 'rb'): vval = firstvalue(vval)
settkvar(w, vval)
return()
# Returns a list of preset files (rgbscript.cf.) for display the with Presets menu button.
def getpresetspaths():
import glob
if os.path.exists(PRESETSDIR):
pspaths = [] # list of files
# Must match filename like: rgbscript.cf.2022-06-06-13:44:23.920467
#for f in glob.glob('rgbscript.cf.[1-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9]*'):
for f in glob.glob(os.path.join(PRESETSDIR, '*')):
pspaths.append(f)
else:
titletext = 'Error loading presets'
warntext = 'No presets directory found'
showwarning(titletext, warntext)
return(pspaths)
# Retrieve the name part of a presets filename
def getpresetsname(pfile):
base = os.path.basename(pfile)
parts = splitstr(base, '.')
# Only interested in final part
for p in parts:
pname = p
return(pname)
## Returns a timestamp for the presets file to be written.
#def getpresetsfilename():
#
# from datetime import datetime
#
# dt = datetime.now() # e.g. 2022-06-06 13:44:23.920467
# dt = str(dt).replace(' ', '-') # e.g. 2022-06-06-13:44:23.920467 (also: what insanely complicated code)
# pfile = 'rgbscript.cf' + '.' + dt # rgbscript.cf.
#
# return(pfile)
# Create the presets ditectory if it doesn't exist.
def makepresetsdir(pdir):
if not os.path.exists(pdir):
try:
os.mkdir(pdir)
except OSError as e:
if e.errno != errno.EEXIST:
titletext='Error creating directory'
errtext = 'Creating directory %s failed' % pdir
showerror(titletext, warntext)
logfatalerror(errtext)
else:
if not os.path.isdir(pdir):
titletext='Error creating directory'
errtext = 'Non-directory %s exists' % pdir
showerror(titletext, warntext)
logfatalerror(errtext)
return()
# Save current configuration in the form of a presets file stored in the presets directory.
# A presets file is just a ' = ' config file with a special name.
def savepresets():
write = False
# No need to ask, user can hit Cancel (maybe in some future version).
#if asksavepresets():
if True:
rv = manageconfigdict()
if (rv):
answer = askpresetname()
if (answer != None):
# CWD/rgbscript-presets/
presetsfile = os.path.join(PRESETSDIR, answer)
if os.path.exists(PRESETSDIR):
if os.path.exists(presetsfile):
write = askconfirm('Presets exist', 'Overwrite %s?' % presetsfile)
else:
write = True
else:
titletext = 'Error saving presets'
warntext = 'No presets directory present'
showwarning(titletext, warntext)
else:
titletext = 'Error saving presets'
messagetext = 'Not saving defaults'
showmessage(titletext, messagetext)
if (write):
writeconfig(presetsfile)
return()
# Populate the Presets menu dynamically. Strange that Tkinter doesn't have some builtin routine for this,
# nor that most tutorials are very explicit about it. Figuring this out was a mjor pain.
def setpresets(wname, mnu):
presetspathlist = getpresetspaths()
menudict = {}
mkeys = []
# Add key-value pairs to dictionary
for i in range(len(presetspathlist)):
key = 'key'+str(i) # key0, key1, ..
########################################
# ??????????????????????????????????????
# Why does this not work ??
# mkeys.append[key]
# ??????????????????????????????????????
########################################
temp = {key: presetspathlist[i]}
menudict.update(temp)
mkeys = list(menudict.keys()) # ['key0', 'key1', ..]
# Add entries and values to the menu
for i in range(len(presetspathlist)):
# mkey = list(menudict.keys())[i]
mkey = mkeys[i]
fname = menudict.get(mkey)
# add_radiobutton() will display the previously selected menu option
# alt.: add_command ('anpnymous')
# getpresetsname() returns only the name (part of the file's basename after last '.')
lbl = getpresetsname(menudict['key'+str(i)])
mnu.menu.add_radiobutton(label=lbl, command=lambda x = fname: onpresetselect(x))
mnu.grid(row=0, column=0, sticky='W', padx=4, pady=4)
# Add a 'defaults' entry to the menu
mnu.menu.add_radiobutton(label='defaults', command=restoredefaults)
mnu.grid(row=0, column=0, sticky='W', padx=4, pady=4)
return()
# Parse a preset file.
def readpresets(fpath):
global RECONFIGDICT
RECONFIGDICT.clear()
# Read STRINGS from file
try:
with open(fpath) as fp:
lines = fp.readlines()
for line in lines:
line = line.rstrip()
if '=' in line:
entry = line.split('=')
var = entry[0].strip()
val = entry[1].strip()
val = val.replace("'", "")
val = val.replace('"', "")
RECONFIGDICT[var] = firstvalue(val)
except OSError:
titletext='Error loading presets'
warntext = 'Reading presets from %s failed' % presetsfile
showwarning(titletext, warntext)
return()
# Ask whether to save current config as presets.
def asksavepresets():
rv = messagebox.askyesno("Save settings", "Save settings as presets?")
return(rv)
# Ask user to enter a name for current config to be saved as presets.
def askpresetname():
from tkinter.simpledialog import askstring
name = askstring("Save presets", "Name for presets:")
return(name)
# Read the selected presets file.
def onpresetselect(presetspath):
readpresets(presetspath) # load settings from the preset file into RECONFIGDICT
###################################################################################################
###################################################################################################
# RESTORE WINDOW CONFIG
# Could be used for re-graying widgets ??
# TODO: def restoreconfig():
# Reconfiguration is done in a sweeping way: defaults plus values read from the preset file
settkvars(DEFAULTSDICT) # back to defaults
settkvars(RECONFIGDICT) # read settings from RECONFIGDICT
###################################################################################################
###################################################################################################
return()
# Ask whether to restore default settings.
def askrestoredefaults():
rv = messagebox.askyesnocancel("Restore defaults", "Discard current settings?")
return(rv)
# Restore default settings. What some programs would call a reset.
def restoredefaults():
if askrestoredefaults():
settkvars(DEFAULTSDICT)
return()
# Ask whether to delete presets files.
def askdeletepresets():
rv = messagebox.askyesnocancel("Delete presets", "Delete presets?")
return(rv)
# Delete presets files from the presets directory.
def removepresetspaths():
if askdeletepresets():
flist = getpresetspaths()
if flist:
for f in flist:
if os.path.isfile(f):
log('Deleting %s' % os.path.join(CWD, f))
try:
os.remove(f)
except OSError as e:
logerror("%s - %s." % (e.filename, e.strerror))
return()
# ************************************************************************************************************************************************************************************************************
# ************************************************************************************************************************************************************************************************************
# ************************************************************************************************************************************************************************************************************
# BONUS CODE
# Handle OK: write config if changes were made
def onbonusokbutton(event=None):
manageconfigdict(configdict=BONUSCONFIGDICT)
bonuswindow.after(200, bonuswindow.destroy)
#def wbonusconfig(wname, wtype, var=None):
# global TKOBJDICT # apparently not needed
# wobj = TKOBJDICT[wname]
## print(wobj)
# if (wtype == 'bt'):
# if (wname == 'bwin_ok'):
# wobj.configure(command=onbonusokbutton)
# wobj.bind('', onbonusokbutton)
bonuswindow = None
BWINSHOWN = 0
def onbonusbutton(event=None):
global BWINSHOWN, bonuswindow
# Window
bonuswindow = tk.Toplevel(root)
bonuswindow.title('Bonus code settings')
bonuswindow.geometry(BONUSWINDOWSIZE)
FRAMEDICT['bonuswindow'] = bonuswindow
bonuswindow.resizable(False, False)
# Defaults
initdefaultsdict(configdict=BONUSCONFIGDICT)
# Frames
for frdfn in BONUSFRAMESLIST:
frname = getframename(frdfn)
buildframe(frname, framelist=BONUSFRAMESLIST)
setframetitle(frname, framelist=BONUSFRAMESLIST)
setframeweights(weightdict=BONUSWEIGHTDICT)
# Widgets
setupwidgets(widgetdict=BONUSWIDGETSDICT)
inittkvars(widgetdict=BONUSWIDGETSDICT) # initialize tk vars (default settings)
configwidgets(widgetdict=BONUSWIDGETSDICT, enabledict=BONUSENABLEDICT, disabledlist=BONUSDISABLED) # bind tk vars to widgets
if (BWINSHOWN == 1):
reinitsubwindow(configdict=BONUSCONFIGDICT)
BWINSHOWN = 1
return()
# ************************************************************************************************************************************************************************************************************
# ************************************************************************************************************************************************************************************************************
# ************************************************************************************************************************************************************************************************************
# ==================================================================================================
# ==================================================================================================
# MAIN CODE
if not os.path.exists(PRESETSDIR):
makepresetsdir(PRESETSDIR)
# Window
root = tk.Tk()
root.title('RGBScript Configuration Editor')
root.geometry(WINDOWSIZE)
FRAMEDICT['root'] = root
#root.resizable(False, False)
# Defaults
###################################################################################################
###################################################################################################
#RECONFIGDICT.clear() # Just to make sure
###################################################################################################
###################################################################################################
initdefaultsdict()
# Frames
for frdfn in ROOTFRAMESLIST:
frname = getframename(frdfn)
buildframe(frname)
setframetitle(frname)
setframeweights()
# Widgets
setupwidgets()
inittkvars() # initialize tk vars (default settings)
configwidgets() # bind tk vars to widgets
# With this, the root window is set up and configured. From here configuration widgets
# and buttons take over.
###################################################################################################
###################################################################################################
#root.protocol("WM_DELETE_WINDOW", byebye)
###################################################################################################
###################################################################################################
root.mainloop()