pism_config_editor (10520B)
1 #!/usr/bin/env python3 2 3 data = {} 4 5 6 def text_height(width, value): 7 assert width > 0 8 9 if len(value) < width: 10 return 1 11 else: 12 return len(value) / width + 2 13 14 15 def load_config(filename): 16 global data 17 18 import json 19 20 with open(filename) as f: 21 data = json.load(f) 22 23 24 def is_special(key): 25 return key.endswith("_doc") or key.endswith("_type") or key.endswith("_units") or key.endswith("_option") or key.endswith("_choices") or key == "long_name" 26 27 28 def path(event): 29 return event.widget.focus().split(".") 30 31 32 def parameter_info(event): 33 d = data.copy() 34 p = path(event) 35 36 name = p[-1] 37 parent = p[:-1] 38 39 for k in parent: 40 d = d[k] 41 42 result = {} 43 44 result["name"] = ".".join(p) 45 result["value"] = d[name] 46 result["type"] = d[name + "_type"] 47 result["doc"] = d[name + "_doc"] 48 49 try: 50 result["option"] = "-" + d[name + "_option"] 51 except KeyError: 52 result["option"] = "-" + result["name"] 53 54 if result["type"] == "keyword": 55 result["choices"] = d[name + "_choices"] 56 57 if result["type"] not in ("keyword", "flag", "string"): 58 result["units"] = d[name + "_units"] 59 60 return result 61 62 63 def keyword_handler(info, window, first_row): 64 value = tk.StringVar() 65 value.set(info["value"]) 66 67 window.value = value 68 69 ttk.Label(window, text="Value:").grid(row=first_row, column=0, sticky=tk.W, padx=10) 70 71 j = first_row 72 for v in info["choices"].split(","): 73 ttk.Radiobutton(window, text=v, variable=value, value=v).grid(row=j, column=1, sticky=tk.W, padx=10) 74 j += 1 75 76 return j 77 78 79 def keyword_dialog(event): 80 generic_dialog(event, keyword_handler) 81 82 83 def generic_dialog(event, func): 84 85 info = parameter_info(event) 86 87 treeview = event.widget 88 item_id = treeview.focus() 89 90 root = treeview.master.master 91 92 window = tk.Toplevel(root) 93 window.wm_title(info["name"]) 94 95 frame = ttk.Frame(window) 96 frame.grid(padx=5, pady=5) 97 98 j = 0 99 100 ttk.Label(frame, text="Name:").grid(row=j, sticky=tk.W, padx=10) 101 ttk.Label(frame, text=info["name"]).grid(row=j, column=1, sticky=tk.W, padx=10) 102 j += 1 103 104 ttk.Label(frame, text="Command-line option:").grid(row=j, sticky=tk.W, padx=10) 105 ttk.Label(frame, text=info["option"]).grid(row=j, column=1, sticky=tk.W, padx=10) 106 j += 1 107 108 doc_text = info["doc"] 109 ttk.Label(frame, text="Description:").grid(row=j, column=0, sticky=tk.W+tk.N, padx=10) 110 doc = tk.Text(frame, width=60, height=text_height(60, doc_text), wrap="word") 111 doc.insert(tk.END, doc_text) 112 doc.config(state=tk.DISABLED) 113 doc.grid(row=j, column=1, sticky=tk.W, padx=10) 114 j += 1 115 116 # spacer 117 ttk.Frame(frame, height=5).grid(row=j, column=0, columnspan=2) 118 j += 1 119 120 j = func(info, frame, j) 121 122 # the footer contains "OK" and "Cancel" buttons 123 footer = ttk.Frame(frame) 124 footer.grid(row=j, column=0, columnspan=2) 125 126 def is_valid(T, value): 127 if T == "number": 128 try: 129 dummy = float(value) 130 return True 131 except: 132 return False 133 elif T == "integer": 134 try: 135 dummy = int(value) 136 return True 137 except: 138 return False 139 elif T == "flag": 140 return value in ("True", "False") 141 else: 142 return True # all strings are valid 143 144 def set_item(name, value): 145 print "setting %s to %s" % (name, value) 146 147 def update_treeview(tree, name, value): 148 print "setting %s to %s in the tree view" % (name, value) 149 150 def ok_handler(): 151 if info["type"] in ("keyword", "flag"): 152 value = frame.value.get() 153 else: 154 value = frame.value.get(1.0, "end").strip() 155 156 if is_valid(info["type"], value): 157 if value != str(info["value"]): 158 set_item(info["name"], value) 159 update_treeview(treeview, info["name"], value) 160 window.destroy() 161 else: 162 tkMessageBox.showerror("Invalid %s" % info["name"], 163 "'%s' is not a valid %s" % (value, info["type"]), 164 parent=window) 165 166 ttk.Button(footer, text="OK", width=20, 167 command=ok_handler).grid(pady=10, ipady=5, sticky=tk.W) 168 ttk.Button(footer, text="Cancel", width=20, 169 command=window.destroy).grid(row=0, column=1, pady=10, ipady=5, sticky=tk.W) 170 171 window.transient(root) 172 window.grab_set() 173 root.wait_window(window) 174 175 176 def scalar_handler(info, window, first_row, integer): 177 178 j = first_row 179 180 ttk.Label(window, text="Units:").grid(row=j, column=0, sticky=tk.W, padx=10) 181 ttk.Label(window, text=info["units"]).grid(row=j, column=1, sticky=tk.W, padx=10) 182 j += 1 183 184 ttk.Label(window, text="Value:").grid(row=j, column=0, sticky=tk.W+tk.N, padx=10) 185 text = tk.Text(window, width=60, height=1) 186 text.grid(row=j, column=1, sticky=tk.W, padx=10) 187 value = info["value"] 188 if integer: 189 text.insert("end", str(int(value))) 190 else: 191 text.insert("end", str(value)) 192 j += 1 193 194 window.value = text 195 196 return j 197 198 199 def scalar_dialog(event): 200 generic_dialog(event, lambda a, b, c: scalar_handler(a, b, c, False)) 201 202 203 def integer_dialog(event): 204 generic_dialog(event, lambda a, b, c: scalar_handler(a, b, c, True)) 205 206 207 def string_handler(info, window, first_row): 208 209 ttk.Label(window, text="Value:").grid(row=first_row, column=0, sticky=tk.W+tk.N, padx=10) 210 211 value = info["value"] 212 213 j = first_row 214 text = tk.Text(window, width=60, height=text_height(60, value)) 215 text.grid(row=j, column=1, sticky=tk.W, padx=10) 216 text.insert("end", value) 217 j += 1 218 219 window.value = text 220 221 return j 222 223 224 def string_dialog(event): 225 generic_dialog(event, string_handler) 226 227 228 def flag_handler(info, window, first_row): 229 230 value = tk.StringVar() 231 value.set(str(info["value"])) 232 233 ttk.Label(window, text="Value:").grid(row=first_row, column=0, sticky=tk.W, padx=10) 234 235 window.value = value 236 237 j = first_row 238 for v in ["True", "False"]: 239 ttk.Radiobutton(window, text=v, variable=value, value=v).grid(row=j, column=1, sticky=tk.W, padx=10) 240 j += 1 241 242 return j 243 244 245 def flag_dialog(event): 246 generic_dialog(event, flag_handler) 247 248 249 class App(object): 250 def __init__(self, master): 251 self.master = master 252 self.create_widgets(master) 253 254 def create_widgets(self, master): 255 frame = ttk.Frame(master, width=1000) 256 frame.pack(side="left", fill="both", expand=True, padx=10, pady=10) 257 258 tree = ttk.Treeview(frame) 259 tree.tag_bind('string', '<Double-Button-1>', string_dialog) 260 tree.tag_bind('keyword', '<Double-Button-1>', keyword_dialog) 261 tree.tag_bind('scalar', '<Double-Button-1>', scalar_dialog) 262 tree.tag_bind('integer', '<Double-Button-1>', integer_dialog) 263 tree.tag_bind('flag', '<Double-Button-1>', flag_dialog) 264 265 tree.pack(side="left", fill="both", expand=True) 266 tree["height"] = 40 267 268 tree['columns'] = ["value", "units", "description"] 269 270 tree.column('description', minwidth=500) 271 tree.column('units', minwidth=150, stretch=False) 272 tree.column('value', minwidth=150, stretch=False) 273 tree.column('#0', minwidth=200) 274 275 tree.heading("value", text="Value") 276 tree.heading("units", text="Units") 277 tree.heading("description", text="Description") 278 279 scrollbar = tk.Scrollbar(master, orient=tk.VERTICAL, command=tree.yview) 280 scrollbar.pack(expand=True, fill="y") 281 282 tree["yscrollcommand"] = scrollbar.set 283 284 self.load_config(master) 285 286 self.add_items("", tree, "", data) 287 288 def add_items(self, id, tree, root, config): 289 keys = config.keys() 290 keys.sort() 291 292 for k in keys: 293 if id == "": 294 parameter_name = k 295 else: 296 parameter_name = id + "." + k 297 if type(config[k]) == dict: 298 self.add_items(parameter_name, tree, 299 tree.insert(root, 'end', parameter_name, text=k, 300 values=("---", "---", "---")), 301 config[k]) 302 else: 303 if not is_special(k): 304 parameter_value = config[k] 305 parameter_type = config[k + "_type"] 306 if parameter_type == "integer": 307 parameter_value = int(parameter_value) 308 if parameter_type not in ("string", "keyword", "flag"): 309 parameter_units = config[k + "_units"] 310 else: 311 parameter_units = "---" 312 doc = config[k + "_doc"] 313 tree.insert(root, 'end', parameter_name, text=k, values=(parameter_value, parameter_units, doc), 314 tags=parameter_type) 315 316 def load_config(self, master): 317 from argparse import ArgumentParser 318 319 parser = ArgumentParser() 320 parser.description = "PISM Configuration file editor" 321 parser.add_argument("FILE", nargs=1) 322 options = parser.parse_args() 323 args = options.FILE 324 325 if len(args) > 0: 326 self.input_file = args[0] 327 else: 328 master.update() 329 self.input_file = tkFileDialog.askopenfilename(parent=master, 330 filetypes=["JSON .json"], 331 title='Choose an input file') 332 333 if len(self.input_file) == 0: 334 print "No input file selected. Exiting..." 335 import sys 336 sys.exit(0) 337 338 load_config(self.input_file) 339 340 341 def create_nesting(config, name): 342 p = name.split(".") 343 344 d = config 345 for j in p[:-1]: 346 if j not in d.keys(): 347 d[j] = {} 348 349 d = d[j] 350 351 352 def copy_parameter(source, destination, name): 353 create_nesting(destination, name) 354 355 p = name.split(".") 356 357 s = source 358 d = destination 359 for j in p[:-1]: 360 s = s[j] 361 d = d[j] 362 363 for k in s.keys(): 364 if k.startswith(p[-1] + "_"): 365 d[k] = s[k] 366 367 368 if __name__ == "__main__": 369 370 import Tkinter as tk 371 import tkFileDialog 372 import ttk 373 import tkMessageBox 374 375 root = tk.Tk() 376 root.geometry("%dx%d" % (1200, 700)) 377 root.wm_title("PISM configuration editor") 378 379 a = App(root) 380 381 root.lift() 382 root.mainloop()