pism

[fork] customized build of PISM, the parallel ice sheet model (tillflux branch)
git clone git://src.adamsgaard.dk/pism # fast
git clone https://src.adamsgaard.dk/pism.git # slow
Log | Files | Refs | README | LICENSE Back to index

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()