def add(self, itemType, cnf = {}, **kw): # handle variable text only for items which have such parameters if itemType in [ # "radiobutton" - TODO: check it "cascade", "command", "checkbutton" # "separator" - does not have such parameters ]: for param in ["label", "accelerator"]: if param in cnf: var = cnf.pop(param) elif param in kw: var = kw.pop(param) else: var = "" if not isinstance(var, variables): var = StringVar(self, var) binding = MenuVarBinding(self, var, self.count, param) var.trace_variable("w", binding.on_var_changed) if cnf: cnf[param] = var.get() else: kw[param] = var.get() self.count = self.count + 1 Menu.add(self, itemType, cnf or kw)
class VarLabel(Label): def __init__(self, *args, **kw): # "textvariable" argument has same effect to VarLabel as "text" try: var = kw.pop("textvariable") except KeyError: pass else: if "text" in kw: raise RuntimeError('"text" and "textvariable" ' 'arguments cannot be passed together to a VarLabel' ) kw["text"] = var if "text" in kw: self.text_var = kw.pop("text") kw["text"] = self.text_var.get() else: self.text_var = StringVar("") Label.__init__(self, *args, **kw) self.text_var.trace_variable("w", self.on_var_changed) def on_var_changed(self, *args): Label.config(self, text = self.text_var.get())
class VarButton(Button): def __init__(self, *args, **kw): if "text" in kw: self.text_var = kw.pop("text") kw["text"] = self.text_var.get() else: self.text_var = StringVar("") Button.__init__(self, *args, **kw) self.text_var.trace_variable("w", self.on_var_changed) def on_var_changed(self, *args): Button.config(self, text=self.text_var.get())
class VarLabelFrame(LabelFrame): def __init__(self, *args, **kw): if "text" in kw: self.text_var = kw.pop("text") kw["text"] = self.text_var.get() else: self.text_var = StringVar("") LabelFrame.__init__(self, *args, **kw) self.text_var.trace_variable("w", self.on_var_changed) def on_var_changed(self, *args): Label.config(self, text=self.text_var.get())
def __init__(self, *args, **kw): text = kw.get("text", "") if isinstance(text, variables): kw["text"] = text.get() Checkbutton.__init__(self, *args, **kw) # `StringVar` requires its master to be initialized. if not isinstance(text, variables): text = StringVar(self, text) self.text_var = text text.trace_variable("w", self.on_var_changed)
def __init__(self, irq, *args, **kw): SettingsWidget.__init__(self, irq, *args, **kw) self.irq = irq for pfx, txt in [ ("src_", _("Source")), ("dst_", _("Destination")) ]: lf = VarLabelFrame(self, text = txt) lf.pack(fill = BOTH, expand = False) lf.columnconfigure(0, weight = 0) lf.columnconfigure(1, weight = 1) for row in xrange(0, 3): lf.rowconfigure(row, weight = 1) l = VarLabel(lf, text = _("Node")) l.grid(row = 0, column = 0, sticky = "NE") node_var = StringVar() node_var.trace_variable("w", self.on_node_text_changed) node_cb = Combobox(lf, textvariable = node_var, values = [], state = "readonly" ) node_cb.grid(row = 0, column = 1, sticky = "NEW") irq_idx_l = VarLabel(lf, text = _("GPIO index")) irq_idx_l.grid(row = 1, column = 0, sticky = "NE") irq_idx_var = StringVar() irq_idx_e = HKEntry(lf, textvariable = irq_idx_var) irq_idx_e.grid(row = 1, column = 1, sticky = "NEW") irq_name_l = VarLabel(lf, text = _("GPIO name")) irq_name_l.grid(row = 2, column = 0, sticky = "NE") irq_name_var = StringVar() irq_name_e = HKEntry(lf, textvariable = irq_name_var) irq_name_e.grid(row = 2, column = 1, sticky = "NEW") for v in ["lf", "node_var", "node_cb", "irq_idx_l", "irq_idx_e", "irq_idx_var", "irq_name_l", "irq_name_e", "irq_name_var"]: setattr(self, pfx + v, locals()[v]) self.__auto_var_base_cbs = None self.v_var_base.trace_variable("w", self.__on_var_base)
def __init__(self, qom_desc, *args, **kw): GUIFrame.__init__(self, *args, **kw) self.desc = qom_desc try: self.pht = self.winfo_toplevel().pht except AttributeError: self.pht = None # shapshot mode without PHT if self.pht is not None: self.pht.watch_changed(self.__on_changed__) sf = self.settings_fr = GUIFrame(self) sf.pack(fill = BOTH, expand = False) f = self.qomd_fr = GUIFrame(sf) f.pack(fill = BOTH, expand = False) f.columnconfigure(0, weight = 0) f.columnconfigure(1, weight = 1) have_pciid = False for row, (attr, info) in enumerate(qom_desc.__attribute_info__.items()): f.rowconfigure(row, weight = 0) l = VarLabel(f, text = info["short"]) l.grid(row = row, column = 0, sticky = "NES") try: _input = info["input"] except KeyError: # attribute is read-only v = StringVar() w = HKEntry(f, textvariable = v, state="readonly") else: if _input is str: v = StringVar() w = HKEntry(f, textvariable = v) elif _input is int: v = StringVar() w = HKEntry(f, textvariable = v) def validate(varname, junk, act, entry = w, var = v): validate_int(var, entry = entry) v.trace_variable("w", validate) elif _input is PCIId: have_pciid = True """ Value of PCI Id could be presented either by PCIId object or by a string. So the actual widget/variable pair will be assigned during refresh. """ v = None w = GUIFrame(f) w.grid() w.rowconfigure(0, weight = 1) w.columnconfigure(0, weight = 1) elif _input is bool: v = BooleanVar() w = Checkbutton(f, variable = v) else: raise RuntimeError("Input of QOM template attribute %s of" " type %s is not supported" % (attr, _input.__name__) ) w.grid(row = row, column = 1, sticky = "NEWS") setattr(self, "_var_" + attr, v) setattr(self, "_w_" + attr, w) btf = self.buttons_fr = GUIFrame(self) btf.pack(fill = BOTH, expand = False) btf.rowconfigure(0, weight = 0) btf.columnconfigure(0, weight = 1) btf.columnconfigure(1, weight = 0) bt_apply = VarButton(btf, text = _("Apply"), command = self.__on_apply__ ) bt_apply.grid(row = 0, column = 1, sticky = "NEWS") bt_revert = VarButton(btf, text = _("Refresh"), command = self.__on_refresh__ ) bt_revert.grid(row = 0, column = 0, sticky = "NES") self.after(0, self.__refresh__) self.bind("<Destroy>", self.__on_destroy__, "+") self.__have_pciid = have_pciid if have_pciid: self.qsig_watch("qvc_available", self.__on_qvc_available)
class QDCGUIWindow(GUITk, QDCGUISignalHelper): def __init__(self, project=None): GUITk.__init__(self, wait_msec=1) for signame in ["qvc_dirtied", "qvd_failed", "qvc_available"]: s = CoSignal() s.attach(self.signal_dispatcher) setattr(self, "sig_" + signame, s) self.title_suffix = _("Qemu device creator GUI") self.title_suffix.trace_variable("w", self.__on_title_suffix_write__) self.title_not_saved_asterisk = StringVar() self.title_not_saved_asterisk.trace_variable( "w", self.__on_title_suffix_write__) self.saved_operation = None self.var_title = StringVar() self.title(self.var_title) # Hot keys, accelerators self.hk = hotkeys = HotKey(self) hotkeys.add_bindings([ HotKeyBinding( self.invert_history_window, key_code=43, description=_("If editing history window is hidden then \ show it else hide it."), symbol="H"), HotKeyBinding(self.on_load, key_code=32, description=_("Load project from file."), symbol="O"), HotKeyBinding(self.on_new_project, key_code=57, description=_("Create new project."), symbol="N"), HotKeyBinding(self.on_add_description, key_code=40, description=_("Add description to the project"), symbol="D"), HotKeyBinding(self.on_set_qemu_build_path, key_code=56, description=_("Set Qemu build path for the project"), symbol="B"), HotKeyBinding( self.on_sel_tgt_qemu_version, key_code=28, description=_("Select target Qemu version for the project"), symbol="T"), HotKeyBinding(self.on_generate, key_code=42, description=_("Launch code generation"), symbol="G"), HotKeyBinding(self.on_delete, key_code=24, description=_("Shutdown the application."), symbol="Q"), HotKeyBinding(self.undo, key_code=52, description=_("Revert previous editing."), symbol="Z"), HotKeyBinding(self.redo, key_code=29, description=_("Make reverted editing again."), symbol="Y"), HotKeyBinding(self.on_save, key_code=39, description=_("Save project."), symbol="S"), HotKeyBinding(self.on_reload, key_code=27, description=_("Reload current project from file."), symbol="R") ]) # see `set_user_settings` self._user_settings = None # Menu bar menubar = VarMenu(self) self.filemenu = filemenu = VarMenu(menubar, tearoff=False) filemenu.add_command(label=_("Add description"), command=self.on_add_description, accelerator=hotkeys.get_keycode_string( self.on_add_description)) filemenu.add_command(label=_("Set Qemu build path"), command=self.on_set_qemu_build_path, accelerator=hotkeys.get_keycode_string( self.on_set_qemu_build_path)) filemenu.add_command(label=_("Select target Qemu version"), command=self.on_sel_tgt_qemu_version, accelerator=hotkeys.get_keycode_string( self.on_sel_tgt_qemu_version)) filemenu.add_command(label=_("Generate"), command=self.on_generate, accelerator=hotkeys.get_keycode_string( self.on_generate)) filemenu.add_separator() filemenu.add_command(label=_("New project"), command=self.on_new_project, accelerator=hotkeys.get_keycode_string( self.on_new_project)), filemenu.add_command(label=_("Save"), command=self.on_save, accelerator=hotkeys.get_keycode_string( self.on_save)), filemenu.add_command(label=_("Save project as..."), command=self.on_save_as) filemenu.add_command(label=_("Load"), command=self.on_load, accelerator=hotkeys.get_keycode_string( self.on_load)), self.reload_idx = filemenu.count filemenu.add_command(label=_("Reload"), command=self.on_reload, accelerator=hotkeys.get_keycode_string( self.on_reload)), self.recentmenu = recentmenu = VarMenu(filemenu, tearoff=False) filemenu.add_cascade( label=_("Recent projects"), menu=recentmenu, state=DISABLED # a user settings instance is required ) filemenu.add_separator() filemenu.add_command(label=_("Quit"), command=self.quit, accelerator=hotkeys.get_keycode_string( self.on_delete)) menubar.add_cascade(label=_("File"), menu=filemenu) self.editmenu = editmenu = VarMenu(menubar, tearoff=False) editmenu.add_command(label=_("Undo"), command=self.undo, accelerator=hotkeys.get_keycode_string(self.undo)) self.undo_idx = editmenu.count - 1 editmenu.add_command(label=_("Redo"), command=self.redo, accelerator=hotkeys.get_keycode_string(self.redo)) self.redo_idx = editmenu.count - 1 editmenu.add_separator() editmenu.add_command(label=_("Rebuild Cache"), command=self.rebuild_cache, accelerator=hotkeys.get_keycode_string( self.rebuild_cache)) editmenu.add_separator() v = self.var_history_window = BooleanVar() v.set(False) self.__on_var_history_window = v.trace_variable( "w", self.__on_var_history_window__) editmenu.add_checkbutton(label=_("Editing history window"), variable=v, accelerator=hotkeys.get_keycode_string( self.invert_history_window)) menubar.add_cascade(label=_("Edit"), menu=editmenu) self.optionsmenu = optionsmenu = VarMenu(menubar, tearoff=False) v = self.var_schedule_generation = BooleanVar() v.set(False) self.__on_var_schedule_generation = v.trace_variable( "w", self.__on_var_schedule_generation__) optionsmenu.add_checkbutton( label=_("Schedule generation after cache loading"), variable=v) v = self.var_gen_chunk_graphs = BooleanVar() v.set(False) self.__on_var_gen_chunk_graphs = v.trace_variable( "w", self.__on_var_gen_chunk_graphs__) optionsmenu.add_checkbutton(label=_("Generate chunk graphs"), variable=v) menubar.add_cascade(label=_("Options"), menu=optionsmenu) self.config(menu=menubar) # Widget layout self.grid() self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1) # Status bar self.grid_rowconfigure(1, weight=0) self.sb = sb = Statusbar(self) sb.grid(row=1, column=0, sticky="NEWS") # Target Qemu version in the status bar self._target_qemu = Variable(None) # This complicated scheme is required because the status must also # be updated on language change. @as_variable(self._target_qemu, _("No target"), _("Target Qemu: %s")) def var_target_qemu(target, no_target, target_qemu): if target is None: return no_target else: return target_qemu % target sb.left(var_target_qemu) # QEMU build path displaying self.var_qemu_build_path = StringVar() sb.left(self.var_qemu_build_path) # Task counters in status bar sb.right(_("Background tasks: ")) sb.repack(CoStatusView(sb), RIGHT) self.signal_dispatcher.watch_failed(self.__on_listener_failed) self.protocol("WM_DELETE_WINDOW", self.on_delete) self.set_project(GUIProject() if project is None else project) self.__update_title__() self.__check_saved_asterisk__() self.qsig_watch("qvc_available", self.__on_qvc_available) def set_user_settings(self, val): if self._user_settings is val: return self._user_settings = val self._update_recent_projects() self._update_options() def _update_recent_projects(self): settings = self._user_settings menu = self.recentmenu # first, clear recent menu last = menu.index(END) if last is not None: menu.delete(0, last) added = 0 if settings is not None: for f in reversed(list(settings.recent_projects)): # a simple check if not isfile(f): settings.recent_projects.discard(f) continue def do_load(file_name=f): self._do_load(file_name) menu.add_command(label=f, command=do_load) added += 1 self.filemenu.entryconfigure(self.filemenu.index( _("Recent projects").get()), state=NORMAL if added else DISABLED) def _update_options(self): settings = self._user_settings if settings is None: return self.var_schedule_generation.set(settings.schedule_generation) self.var_gen_chunk_graphs.set(settings.gen_chunk_graphs) def __on_listener_failed(self, e, tb): sys.stderr.write("Listener failed - %s" % "".join(format_exception(type(e), e, tb))) def __on_history_window_destroy__(self, *args, **kw): self.var_history_window.trace_vdelete("w", self.__on_var_history_window) self.var_history_window.set(False) self.__on_var_history_window = self.var_history_window.trace_variable( "w", self.__on_var_history_window__) def __on_var_history_window__(self, *args): if self.var_history_window.get(): self._history_window = HistoryWindow(self.pht, self) self._history_window.bind("<Destroy>", self.__on_history_window_destroy__, "+") else: try: self._history_window.destroy() except AttributeError: pass else: del self._history_window def invert_history_window(self): self.var_history_window.set(not self.var_history_window.get()) def __on_var_schedule_generation__(self, *args): settings = self._user_settings if settings is not None: settings.schedule_generation = self.var_schedule_generation.get() def __on_var_gen_chunk_graphs__(self, *args): settings = self._user_settings if settings is not None: settings.gen_chunk_graphs = self.var_gen_chunk_graphs.get() def __on_title_suffix_write__(self, *args, **kw): self.__update_title__() def __update_title__(self): try: title_prefix = str(self.current_file_name) except AttributeError: title_prefix = "[New project]" self.var_title.set(title_prefix + self.title_not_saved_asterisk.get() + " - " + self.title_suffix.get()) def check_undo_redo(self): can_do = self.pht.can_do() self.hk.set_enabled(self.redo, can_do) if can_do: self.editmenu.entryconfig(self.redo_idx, state=NORMAL) else: self.editmenu.entryconfig(self.redo_idx, state=DISABLED) can_undo = self.pht.can_undo() self.hk.set_enabled(self.undo, can_undo) if can_undo: self.editmenu.entryconfig(self.undo_idx, state=NORMAL) else: self.editmenu.entryconfig(self.undo_idx, state=DISABLED) def set_current_file_name(self, file_name=None): if file_name is None: try: del self.current_file_name except AttributeError: pass # already deleted else: self.filemenu.entryconfig(self.reload_idx, state=DISABLED) else: # TODO: watch the file in FS and auto ask user to reload self.current_file_name = file_name self.filemenu.entryconfig(self.reload_idx, state=NORMAL) self.__update_title__() def set_project(self, project): try: pht = self.pht except AttributeError: # Project was never been set pass else: pht.unwatch_changed(self.on_changed) try: self.pw.destroy() except AttributeError: # project widget was never been created pass # Close history window if self.var_history_window.get(): self.var_history_window.set(False) self.proj = project self.pht = GUIProjectHistoryTracker(self.proj, self.proj.history) DescNameWatcher(self.pht) self.pw = ProjectWidget(self.proj, self) self.pw.grid(column=0, row=0, sticky="NEWS") self.update_qemu_build_path(project.build_path) self.update_target_qemu() self.pht.watch_changed(self.on_changed) self.check_undo_redo() def __saved_asterisk__(self, saved=True): if saved: if self.title_not_saved_asterisk.get() != "": self.title_not_saved_asterisk.set("") else: if self.title_not_saved_asterisk.get() != "*": self.title_not_saved_asterisk.set("*") def __check_saved_asterisk__(self): if self.saved_operation == self.pht.pos: self.__saved_asterisk__(True) else: self.__saved_asterisk__(False) def on_changed(self, op, *args, **kw): self.check_undo_redo() self.__check_saved_asterisk__() if isinstance(op, GUIPOp_SetBuildPath): proj = self.proj if op.p is proj: self.update_qemu_build_path(proj.build_path) # Note that target Qemu version info will be update when QVC # will be ready. def undo(self): self.pht.undo_sequence() def redo(self): self.pht.do_sequence() def rebuild_cache(self): try: qvd = qvd_get(self.proj.build_path, version=self.proj.target_version) except BadBuildPath as e: showerror( title=_("Cache rebuilding is impossible").get(), message=(_("Selected Qemu build path is bad. Reason: %s") % (e.message)).get()) return # if 'reload_build_path_task' is not failed if hasattr(self.pw, 'reload_build_path_task'): # reload_build_path_task always run at program start that is why # it's in process when it's not in finished_tasks and not failed if self.pw.reload_build_path_task not in self.pw.tm.finished_tasks: ans = askyesno(self, title=_("Cache rebuilding"), message=_("Cache building is already \ in process. Do you want to start cache rebuilding?")) if not ans: return qvd.remove_cache() self.pw.reload_build_path() def on_delete(self): if self.title_not_saved_asterisk.get() == "*": resp = askyesnocancel( title=self.title_suffix.get(), message=_("Current project has unsaved changes." " Would you like to save it?" "\n\nNote that a backup is always saved with name" " project.py in current working directory.").get()) if resp is None: return if resp: self.on_save() if self.title_not_saved_asterisk.get() == "*": # user canceled saving during on_save return try: """ TODO: Note that it is possible to prevent window to close if a generation task is in process. But is it really needed? """ self.task_manager.remove(self._project_generation_task) except AttributeError: pass else: del self._project_generation_task self.signal_dispatcher.unwatch_failed(self.__on_listener_failed) self.quit() def on_add_description(self): d = AddDescriptionDialog(self.pht, self) def on_set_qemu_build_path(self): dir = askdirectory(self, title=_("Select Qemu build path")) if not dir: return self.pht.set_build_path(dir) def on_sel_tgt_qemu_version(self): try: qvd = qvd_get(self.proj.build_path) except: repo = None else: repo = qvd.repo new_target = GitVerSelDialog(self, repo).wait() if new_target is None: return self.pht.set_target(new_target) def on_generate(self): try: t = self._project_generation_task except AttributeError: pass else: if not t.finished: showerror(title=_("Generation is cancelled").get(), message=_("At least one generation task is already \ in process.").get()) return if not self.proj.build_path: showerror( title=_("Generation is impossible").get(), message=_("No Qemu build path is set for the project.").get()) return try: qvd = qvd_get(self.proj.build_path, version=self.proj.target_version) except BadBuildPath as e: showerror( title=_("Generation is impossible").get(), message=(_("Selected Qemu build path is bad. Reason: %s") % (e.message)).get()) return if not hasattr(self.pw, "reload_build_path_task"): showerror(title=_("Generation is impossible").get(), message=_("Qemu version cache loading failed.").get()) return if not qvd.qvc_is_ready and not self.var_schedule_generation.get(): showerror(title=_("Generation is cancelled").get(), message=_("Qemu version cache is not ready yet. Try \ later.").get()) return self._project_generation_task = ProjectGeneration( self.proj, qvd.src_path, self.sig_qvc_dirtied, self.pw.reload_build_path_task, self.var_gen_chunk_graphs.get()) self.task_manager.enqueue(self._project_generation_task) def load_project_from_file(self, file_name): loaded_variables = dict(qdt.__dict__) try: execfile(file_name, loaded_variables) except Exception as e: raise e else: qproj = None for v in loaded_variables.values(): if isinstance(v, GUIProject): break elif qproj is None and isinstance(v, QProject): qproj = v else: if qproj: v = GUIProject.from_qproject(qproj) else: raise Exception("No project object was loaded") self.set_project(v) self.set_current_file_name(file_name) self.saved_operation = self.pht.pos self.__check_saved_asterisk__() if self._user_settings: self._user_settings.account_project(file_name) self._update_recent_projects() def save_project_to_file(self, file_name): self.pw.refresh_layouts() project = self.proj # Ensure that all machine nodes are in corresponding lists for d in project.descriptions: if isinstance(d, MachineNode): d.link(handle_system_bus=False) pythonize(project, file_name) self.set_current_file_name(file_name) self.saved_operation = self.pht.pos self.__check_saved_asterisk__() def try_save_project_to_file(self, file_name): try: open(file_name, "wb").close() except IOError as e: if not e.errno == 13: # Do not remove read-only files try: remove(file_name) except: pass showerror(title=_("Cannot save project").get(), message=str(e)) return self.save_project_to_file(file_name) if self._user_settings: self._user_settings.account_project(file_name) self._update_recent_projects() def on_save_as(self): fname = asksaveas(self, [(_("QDC GUI Project defining script"), ".py")], title=_("Save project")) if not fname: return self.try_save_project_to_file(fname) def on_save(self): try: fname = self.current_file_name except AttributeError: self.on_save_as() else: self.try_save_project_to_file(fname) def check_unsaved(self): if self.title_not_saved_asterisk.get() == "*": return askyesno( self, title=self.title_suffix, message= _("Current project has unsaved changes. They will be lost. Continue?" )) else: return True def on_new_project(self): if not self.check_unsaved(): return self.set_project(GUIProject()) self.set_current_file_name() """ There is nothing to save in just created project. So declare that all changes are saved. """ self.saved_operation = self.pht.pos self.__check_saved_asterisk__() def on_load(self): if not self.check_unsaved(): return fname = askopen(self, [(_("QDC GUI Project defining script"), ".py")], title=_("Load project")) if fname: self._do_load(fname) def _do_load(self, fname): try: self.load_project_from_file(fname) except Exception as e: showerror(title=_("Project loading failed").get(), message=str(e)) def on_reload(self): try: fname = self.current_file_name except AttributeError: # No file is selected for the project yet return if not self.check_unsaved(): return self._do_load(fname) def update_qemu_build_path(self, bp): if bp is None: self.var_qemu_build_path.set( _("No QEMU build path selected").get()) else: self.var_qemu_build_path.set("QEMU: " + bp) def update_target_qemu(self): p, qvd = self.proj, QemuVersionDescription.current self._target_qemu.set(p and p.target_version or qvd and qvd.commit_sha) def __on_qvc_available(self): self.update_target_qemu()
class BusLineDesc(object): def __init__(self, device_settings_widget, idx): self.dsw = device_settings_widget self.idx = idx def gen_row(self): p = self.dsw.buses_lf p.rowconfigure(self.idx, weight = 1) self.v = StringVar() # When a bus is selected all Combobox values lists should be updated # to prevent selecting of this bus in another Combobox self._on_var_changed = self.v.trace_variable("w", self.on_var_changed) self.cb = Combobox(p, textvariable = self.v, state = "readonly" ) self.cb.grid(row = self.idx, column = 0, sticky = "NEWS") self._on_bus_selected = self.dsw.bind( DeviceSettingsWidget.EVENT_BUS_SELECTED, self.on_bus_selected, "+") def on_var_changed(self, *args): self.dsw.event_generate(DeviceSettingsWidget.EVENT_BUS_SELECTED) def update(self): try: cur_bus = self.dsw.dev.buses[self.idx] except IndexError: cur_bus = None bus_text = DeviceSettingsWidget.gen_node_link_text(cur_bus) self.v.set(bus_text) def delete(self): self.v.trace_vdelete("w", self._on_var_changed) self.dsw.unbind(DeviceSettingsWidget.EVENT_BUS_SELECTED, self._on_bus_selected) if "obs" in self.__dict__: self.v.trace_vdelete("w", self.obs) del self.obs self.cb.destroy() def update_values(self): sel_buses = self.dsw.get_selected_buses() values = [ DeviceSettingsWidget.gen_node_link_text(b) for b in ( [ b for b in self.dsw.mach.buses if (\ ( b.parent_device is None \ or b.parent_device == self.dsw.dev) and (not b.id in sel_buses)) ] + [ None ] ) ] self.cb.config(values = values) def on_bus_selected(self, event): self.update_values() def on_dsw_destroy(self, event): self.delete()
class DeviceSettingsWidget(SettingsWidget): EVENT_BUS_SELECTED = "<<DSWBusSelected>>" prop_type_name_map = { QOMPropertyTypeInteger: ("Integer", ), QOMPropertyTypeLink: ("Link", ), QOMPropertyTypeString: ("String", ), QOMPropertyTypeBoolean: ("Boolean", ) } prop_name_type_map = { "Integer": QOMPropertyTypeInteger, "Link": QOMPropertyTypeLink, "String": QOMPropertyTypeString, "Boolean": QOMPropertyTypeBoolean } def __init__(self, device, *args, **kw): SettingsWidget.__init__(self, device, *args, **kw) self.dev = device common_fr = GUIFrame(self) common_fr.pack(fill = BOTH, expand = False) common_fr.columnconfigure(0, weight = 0) common_fr.columnconfigure(1, weight = 1) common_fr.rowconfigure(0, weight = 0) l = VarLabel(common_fr, text = _("QOM type")) v = self.qom_type_var = StringVar() e = HKEntry(common_fr, textvariable = v) v.trace_variable("w", self.__on_qom_type_var_changed) l.grid(row = 0, column = 0, sticky = "W") e.grid(row = 0, column = 1, sticky = "EW") b = VarButton(common_fr, text = _("Select"), command = self.on_press_select_qom_type ) b.grid(row = 0, column = 2, sticky = "EW") # Check for device tree bp = self.mach.project.build_path if bp is None: b["state"] = "disabled" else: qvd = qvd_get(bp, version = self.mach.project.target_version) if qvd.qvc is None or qvd.qvc.device_tree is None: # TODO: watch "qvc_available" signal b["state"] = "disabled" # parent bus editing widgets l = VarLabel(common_fr, text = _("Parent bus")) self.bus_var = StringVar() self.bus_var.trace_variable("w", self.on_parent_bus_var_changed) self.bus_cb = Combobox( common_fr, textvariable = self.bus_var, state = "readonly" ) l.grid(row = 1, column = 0, sticky = "W") self.bus_cb.grid(row = 1, column = 1, sticky = "EW") common_fr.rowconfigure(1, weight = 0) self.buses_lf = lf = VarLabelFrame( common_fr, text = _("Child buses") ) lf.grid(row = 2, column = 0, columns = 3, sticky = "NEWS") self.rowconfigure(2, weight = 1) lf.columnconfigure(0, weight = 1) self.child_buses_rows = [] # pack properties inside a frame with scrolling outer_frame = VarLabelFrame(self, text = _("Properties")) outer_frame.pack(fill = BOTH, expand = True) self.props_lf = add_scrollbars(outer_frame, GUIFrame) self.props_lf.columnconfigure(0, weight = 1) self.props_lf.columnconfigure(1, weight = 0) self.props_lf.columnconfigure(2, weight = 1) self.props_lf.columnconfigure(3, weight = 0) self.prop2field = {} self.bt_add_prop = VarButton( self.props_lf, text = _("Add"), command = self.on_prop_add ) self.bt_add_prop.grid( column = 3, sticky = "NEWS" ) def __on_destroy__(self, *args): for bld in self.child_buses_rows: bld.delete() SettingsWidget.__on_destroy__(self, *args) def __on_qom_type_var_changed(self, *args): vvb = self.v_var_base vb = vvb.get() try: prev_qt = self.__prev_qom_type except AttributeError: # QOM type was not edited yet prev_qt = self.node.qom_type if vb == "dev" or vb == qom_type_to_var_base(prev_qt): """ If current variable name base is default or corresponds to previous QOM type name then auto suggest new variable name base with account of just entered QOM type. """ qt = self.qom_type_var.get() vvb.set(qom_type_to_var_base(qt)) self.__prev_qom_type = qt def on_parent_bus_var_changed(self, *args): self.event_generate(DeviceSettingsWidget.EVENT_BUS_SELECTED) def on_press_select_qom_type(self): DeviceTreeWidget(self) def gen_uniq_prop_name(self): for x in count(0, 1): name = "name-of-new-property-" + str(x) for prop in self.prop2field: if name == prop.prop_name: name = None break if name: return name def on_prop_add(self): p = QOMPropertyValue( QOMPropertyTypeLink, self.gen_uniq_prop_name(), None ) lpd = PropLineDesc(self, p) row = len(self.prop2field) lpd.gen_row(row) self.prop2field[p] = lpd # Move add button bottom self.bt_add_prop.grid(row = row + 1) def on_changed(self, op, *args, **kw): if isinstance(op, MOp_SetChildBus): self.event_generate(DeviceSettingsWidget.EVENT_BUS_SELECTED) if isinstance(op, MOp_SetDevParentBus): self.event_generate(DeviceSettingsWidget.EVENT_BUS_SELECTED) if isinstance(op, MachineNodeOperation): if op.writes_node() and self.dev.id == -1: self.destroy() elif op.node_id == self.dev.id: self.refresh() @staticmethod def gen_prop_type_optionmenu(parent, current = None): var = StringVar() keys = [] for ptn in DeviceSettingsWidget.prop_type_name_map.values(): keys.append(ptn[0]) om = OptionMenu(parent, var, *keys) if current: current = DeviceSettingsWidget.prop_type_name_map[current][0] else: current = next(itervalues(DeviceSettingsWidget.prop_type_name_map)) var.set(current) return om, var @staticmethod def gen_node_link_text(node): # TODO: localize? if node is None: return "-1: NULL" ret = "%s: " % node.id if isinstance(node, BusNode): ret = ret + "Bus, %s" % node.gen_child_name_for_bus() elif isinstance(node, IRQLine): ret = ret + "IRQ: %s" \ % DeviceSettingsWidget.gen_node_link_text(node.src[0]) \ + " -> %s" \ % DeviceSettingsWidget.gen_node_link_text(node.dst[0]) elif isinstance(node, IRQHub): ret = ret + "IRQ Hub" elif isinstance(node, DeviceNode): ret = ret + "Device, %s" % node.qom_type elif isinstance(node, MemoryNode): ret = ret + "Memory, %s" % node.name return ret def refresh(self): SettingsWidget.refresh(self) self.qom_type_var.set(self.dev.qom_type) for p, desc in self.prop2field.items(): desc.e_name.destroy() desc.om_type.destroy() desc.w_val.destroy() desc.bt_del.destroy() self.prop2field = {} # If self.dev.properties is empty the row variable will remain # undefined. row = -1 for row, p in enumerate(self.dev.properties): lpd = PropLineDesc(self, p) lpd.gen_row(row) # Do not use different QOMPropertyValue as the key for the # PropLineDesc of corresponding device-stored QOMPropertyValue # The QOMPropertyValue is used to apply deletion of device # property. self.prop2field[p] = lpd self.bt_add_prop.grid(row = row + 1) # refresh parent bus buses = [ DeviceSettingsWidget.gen_node_link_text(None) ] for n in self.mach.id2node.values(): if not isinstance(n, BusNode): continue buses.append(DeviceSettingsWidget.gen_node_link_text(n)) self.bus_cb.config(values = buses) self.bus_var.set( DeviceSettingsWidget.gen_node_link_text(self.dev.parent_bus) ) bus_row_count = len(self.child_buses_rows) bus_count = len(self.dev.buses) + 1 if bus_row_count < bus_count: if bus_row_count: bld = self.child_buses_rows[-1] bld.v.trace_vdelete("w", bld.obs) del bld.obs for idx in xrange(bus_row_count, bus_count): bld = BusLineDesc(self, idx) self.child_buses_rows.append(bld) bld.gen_row() bld.obs = bld.v.trace_variable("w", self.on_last_child_bus_changed) if bus_count < bus_row_count: for idx in xrange(bus_count, bus_row_count): bld = self.child_buses_rows.pop() bld.delete() bld = self.child_buses_rows[-1] bld.obs = bld.v.trace_variable("w", self.on_last_child_bus_changed) for bld in self.child_buses_rows: bld.update() def on_last_child_bus_changed(self, *args): bld = self.child_buses_rows[-1] bus = self.find_node_by_link_text(bld.v.get()) if not bus is None: # Selecting not NULL child bus means that a child bus was added. # Add new NULL bus string for consequent bus addition. bld.v.trace_vdelete("w", bld.obs) del bld.obs bld = BusLineDesc(self, len(self.child_buses_rows)) self.child_buses_rows.append(bld) bld.gen_row() bld.update() bld.obs = bld.v.trace_variable("w", self.on_last_child_bus_changed) def get_selected_child_buses(self): child_buses = [ bld.v.get() for bld in self.child_buses_rows ] ret = [ self.find_node_by_link_text(t) for t in child_buses if t ] return [ b.id for b in ret if not b is None ] def get_selected_buses(self): ret = self.get_selected_child_buses() parent_bus = self.find_node_by_link_text(self.bus_var.get()) if not parent_bus is None: parent_bus = parent_bus.id if not parent_bus in ret: ret.append(parent_bus) return ret def __apply_internal__(self): # apply parent bus new_bus_text = self.bus_var.get() new_bus = self.find_node_by_link_text(new_bus_text) if not self.dev.parent_bus == new_bus: self.mht.stage(MOp_SetDevParentBus, new_bus, self.dev.id) qom = self.qom_type_var.get() if not self.dev.qom_type == qom: self.mht.stage(MOp_SetDevQOMType, qom, self.dev.id) # Do property removing before addition to prevent conflicts of # property recreation. for p in self.dev.properties: if not p in self.prop2field: self.mht.stage(MOp_DelDevProp, p, self.dev.id) for p, desc in list(self.prop2field.items()): cur_name, cur_type, cur_val = desc.get_current_name(), \ desc.get_current_type(), desc.get_current_val() if p in self.dev.properties.values(): if cur_name != p.prop_name: # Name of property was changed. Recreate it. new_p = QOMPropertyValue(cur_type, cur_name, cur_val) self.mht.stage(MOp_DelDevProp, p, self.dev.id) self.mht.stage(MOp_AddDevProp, new_p, self.dev.id) del self.prop2field[p] self.prop2field[new_p] = desc desc.prop = new_p elif not ( p.prop_type == cur_type and p.prop_val == cur_val ): self.mht.stage( MOp_SetDevProp, cur_type, cur_val, p, self.dev.id ) else: # A completely new property. It was added using # the 'Add' button. new_p = QOMPropertyValue(cur_type, cur_name, cur_val) self.mht.stage(MOp_AddDevProp, new_p, self.dev.id) new_buses = self.get_selected_child_buses() # Changing of buses is made in two steps to allow reordering of buses # during single iteration. step2 = [] # The child bus list is reversed to remove buses from the end to to the # begin. After removing bus from middle consequent indexes becomes # incorrect. for i, bus in reversed([ x for x in enumerate(self.dev.buses) ]): try: new_bus = new_buses.pop(i) except IndexError: # remove i-th bus self.mht.stage( MOp_SetChildBus, self.dev.id, i, -1 ) else: if bus.id == new_bus: continue # change i-th bus (1-st step: remove) self.mht.stage( MOp_SetChildBus, self.dev.id, i, -1 ) # step 2 should be done in increasing index order step2.insert(0, (i, new_bus)) adding = [ x for x in zip(count(len(self.dev.buses)), new_buses) ] for i, new_bus in step2 + adding: # add i-th bus self.mht.stage( MOp_SetChildBus, self.dev.id, i, new_bus ) self.mht.set_sequence_description(_("Device configuration."))
class QDCGUIWindow(GUITk): def __init__(self, project=None): GUITk.__init__(self, wait_msec=1) for signame in ["qvc_dirtied", "qvd_failed", "qvc_available"]: s = CoSignal() s.attach(self.signal_dispatcher) setattr(self, "sig_" + signame, s) self.title_suffix = _("Qemu device creator GUI") self.title_suffix.trace_variable("w", self.__on_title_suffix_write__) self.title_not_saved_asterisk = StringVar() self.title_not_saved_asterisk.trace_variable( "w", self.__on_title_suffix_write__) self.saved_operation = None self.var_title = StringVar() self.title(self.var_title) # Hot keys, accelerators self.hk = hotkeys = HotKey(self) hotkeys.add_bindings([ HotKeyBinding( self.invert_history_window, key_code=43, # H description=_("If editing history window is hidden then \ show it else hide it.")), HotKeyBinding( self.on_load, key_code=32, # O description=_("Load project from file.")), HotKeyBinding( self.on_new_project, key_code=57, # N description=_("Create new project.")), HotKeyBinding( self.on_add_description, key_code=40, # D description=_("Add description to the project")), HotKeyBinding( self.on_set_qemu_build_path, key_code=56, # B description=_("Set Qemu build path for the project")), HotKeyBinding( self.on_generate, key_code=42, # G description=_("Launch code generation")), HotKeyBinding( self.on_delete, key_code=24, # Q description=_("Shutdown the application.")), HotKeyBinding( self.undo, key_code=52, # Z description=_("Revert previous editing.")), HotKeyBinding( self.redo, key_code=29, # Y description=_("Make reverted editing again.")), HotKeyBinding( self.on_save, key_code=39, # S description=_("Save project.")), HotKeyBinding( self.rebuild_cache, key_code=27, # R description=_("Rebuild Cache.")) ]) hotkeys.add_key_symbols({ 27: "R", 43: "H", 32: "O", 57: "N", 40: "D", 56: "B", 42: "G", 24: "Q", 52: "Z", 29: "Y", 39: "S" }) # Menu bar menubar = VarMenu(self) filemenu = VarMenu(menubar, tearoff=False) filemenu.add_command(label=_("Add description"), command=self.on_add_description, accelerator=hotkeys.get_keycode_string( self.on_add_description)) filemenu.add_command(label=_("Set Qemu build path"), command=self.on_set_qemu_build_path, accelerator=hotkeys.get_keycode_string( self.on_set_qemu_build_path)) filemenu.add_command(label=_("Generate"), command=self.on_generate, accelerator=hotkeys.get_keycode_string( self.on_generate)) filemenu.add_separator() filemenu.add_command(label=_("New project"), command=self.on_new_project, accelerator=hotkeys.get_keycode_string( self.on_new_project)), filemenu.add_command(label=_("Save"), command=self.on_save, accelerator=hotkeys.get_keycode_string( self.on_save)), filemenu.add_command(label=_("Save project as..."), command=self.on_save_as) filemenu.add_command(label=_("Load"), command=self.on_load, accelerator=hotkeys.get_keycode_string( self.on_load)), filemenu.add_separator() filemenu.add_command(label=_("Quit"), command=self.quit, accelerator=hotkeys.get_keycode_string( self.on_delete)) menubar.add_cascade(label=_("File"), menu=filemenu) self.editmenu = editmenu = VarMenu(menubar, tearoff=False) editmenu.add_command(label=_("Undo"), command=self.undo, accelerator=hotkeys.get_keycode_string(self.undo)) self.undo_idx = editmenu.count - 1 editmenu.add_command(label=_("Redo"), command=self.redo, accelerator=hotkeys.get_keycode_string(self.redo)) self.redo_idx = editmenu.count - 1 editmenu.add_separator() editmenu.add_command(label=_("Rebuild Cache"), command=self.rebuild_cache, accelerator=hotkeys.get_keycode_string( self.rebuild_cache)) editmenu.add_separator() v = self.var_history_window = BooleanVar() v.set(False) self.__on_var_history_window = v.trace_variable( "w", self.__on_var_history_window__) editmenu.add_checkbutton(label=_("Editing history window"), variable=v, accelerator=hotkeys.get_keycode_string( self.invert_history_window)) menubar.add_cascade(label=_("Edit"), menu=editmenu) self.config(menu=menubar) # Widget layout self.grid() self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1) # Status bar self.grid_rowconfigure(1, weight=0) self.sb = sb = Statusbar(self) sb.grid(row=1, column=0, sticky="NEWS") # QEMU build path displaying self.var_qemu_build_path = StringVar() sb.left(self.var_qemu_build_path) # Task counters in status bar self.var_tasks = vt = IntVar() self.var_callers = vc = IntVar() self.var_active_tasks = vat = IntVar() self.var_finished_tasks = vft = IntVar() sb.right(_("Background tasks: ")) sb.right(FormatVar(value="%u") % vt, fg="red") sb.right(FormatVar(value="%u") % vc, fg="orange") sb.right(FormatVar(value="%u") % vat) sb.right(FormatVar(value="%u") % vft, fg="grey") self.task_manager.watch_activated(self.__on_task_state_changed) self.task_manager.watch_finished(self.__on_task_state_changed) self.task_manager.watch_failed(self.__on_task_state_changed) self.task_manager.watch_removed(self.__on_task_state_changed) self.protocol("WM_DELETE_WINDOW", self.on_delete) self.set_project(GUIProject() if project is None else project) self.__update_title__() self.__check_saved_asterisk__() def __on_task_state_changed(self, task): for group in ["tasks", "callers", "active_tasks", "finished_tasks"]: var = getattr(self, "var_" + group) cur_val = len(getattr(self.task_manager, group)) if cur_val != var.get(): var.set(cur_val) def __on_history_window_destroy__(self, *args, **kw): self.var_history_window.trace_vdelete("w", self.__on_var_history_window) self.var_history_window.set(False) self.__on_var_history_window = self.var_history_window.trace_variable( "w", self.__on_var_history_window__) def __on_var_history_window__(self, *args): if self.var_history_window.get(): self._history_window = HistoryWindow(self.pht, self) self._history_window.bind("<Destroy>", self.__on_history_window_destroy__, "+") else: try: self._history_window.destroy() except AttributeError: pass else: del self._history_window def invert_history_window(self): self.var_history_window.set(not self.var_history_window.get()) def __on_title_suffix_write__(self, *args, **kw): self.__update_title__() def __update_title__(self): try: title_prefix = str(self.current_file_name) except AttributeError: title_prefix = "[New project]" self.var_title.set(title_prefix + self.title_not_saved_asterisk.get() + " - " + self.title_suffix.get()) def check_undo_redo(self): can_do = self.pht.can_do() self.hk.set_enabled(self.redo, can_do) if can_do: self.editmenu.entryconfig(self.redo_idx, state="normal") else: self.editmenu.entryconfig(self.redo_idx, state="disabled") can_undo = self.pht.can_undo() self.hk.set_enabled(self.undo, can_undo) if can_undo: self.editmenu.entryconfig(self.undo_idx, state="normal") else: self.editmenu.entryconfig(self.undo_idx, state="disabled") def set_current_file_name(self, file_name=None): if file_name is None: try: del self.current_file_name except AttributeError: pass else: self.current_file_name = file_name self.__update_title__() def set_project(self, project): try: pht = self.pht except AttributeError: # Project was never been set pass else: pht.unwatch_changed(self.on_changed) try: self.pw.destroy() except AttributeError: # project widget was never been created pass # Close history window if self.var_history_window.get(): self.var_history_window.set(False) self.proj = project self.pht = GUIProjectHistoryTracker(self.proj, self.proj.history) self.pw = ProjectWidget(self.proj, self) self.pw.grid(column=0, row=0, sticky="NEWS") self.update_qemu_build_path(project.build_path) self.pht.watch_changed(self.on_changed) self.check_undo_redo() def __saved_asterisk__(self, saved=True): if saved: if self.title_not_saved_asterisk.get() != "": self.title_not_saved_asterisk.set("") else: if self.title_not_saved_asterisk.get() != "*": self.title_not_saved_asterisk.set("*") def __check_saved_asterisk__(self): if self.saved_operation == self.pht.pos: self.__saved_asterisk__(True) else: self.__saved_asterisk__(False) def on_changed(self, op, *args, **kw): self.check_undo_redo() self.__check_saved_asterisk__() if isinstance(op, GUIPOp_SetBuildPath): proj = self.proj if op.p is proj: self.update_qemu_build_path(proj.build_path) def undo(self): self.pht.undo_sequence() def redo(self): self.pht.do_sequence() def rebuild_cache(self): try: qvd = qvd_get(self.proj.build_path) except BadBuildPath as e: showerror( title=_("Cache rebuilding is impossible").get(), message=(_("Selected Qemu build path is bad. Reason: %s") % (e.message)).get()) return # if 'reload_build_path_task' is not failed if hasattr(self.pw, 'reload_build_path_task'): # reload_build_path_task always run at program start that is why # it's in process when it's not in finished_tasks and not failed if self.pw.reload_build_path_task not in self.pw.tm.finished_tasks: ans = askyesno(self, title=_("Cache rebuilding"), message=_("Cache building is already \ in process. Do you want to start cache rebuilding?")) if not ans: return qvd.remove_cache() self.pw.reload_build_path() def on_delete(self): if self.title_not_saved_asterisk.get() == "*": resp = askyesnocancel( title=self.title_suffix.get(), message=_("Current project has unsaved changes." " Would you like to save it?" "\n\nNote that a backup is always saved with name" " project.py in current working directory.").get()) if resp is None: return if resp: self.on_save() if self.title_not_saved_asterisk.get() == "*": # user canceled saving during on_save return try: """ TODO: Note that it is possible to prevent window to close if a generation task is in process. But is it really needed? """ self.task_manager.remove(self._project_generation_task) except AttributeError: pass else: del self._project_generation_task self.task_manager.unwatch_activated(self.__on_task_state_changed) self.task_manager.unwatch_finished(self.__on_task_state_changed) self.task_manager.unwatch_failed(self.__on_task_state_changed) self.task_manager.unwatch_removed(self.__on_task_state_changed) self.quit() def on_add_description(self): d = AddDescriptionDialog(self.pht, self) def on_set_qemu_build_path(self): dir = askdirectory(self, title=_("Select Qemu build path")) if not dir: return self.pht.set_build_path(dir) def on_generate(self): try: t = self._project_generation_task except AttributeError: pass else: if not t.finished: showerror(title=_("Generation is cancelled").get(), message=_("At least one generation task is already \ in process.").get()) return if not self.proj.build_path: showerror( title=_("Generation is impossible").get(), message=_("No Qemu build path is set for the project.").get()) return try: qvd = qvd_get(self.proj.build_path) except BadBuildPath as e: showerror( title=_("Generation is impossible").get(), message=(_("Selected Qemu build path is bad. Reason: %s") % (e.message)).get()) return if not qvd.qvc_is_ready: showerror(title=_("Generation is cancelled").get(), message=_("Qemu version cache is not ready yet. Try \ later.").get()) return self._project_generation_task = ProjectGeneration( self.proj, qvd.src_path, self.sig_qvc_dirtied) self.task_manager.enqueue(self._project_generation_task) def load_project_from_file(self, file_name): loaded_variables = {} try: execfile(file_name, qdt.__dict__, loaded_variables) except Exception as e: raise e else: qproj = None for v in loaded_variables.values(): if isinstance(v, GUIProject): break elif qproj is None and isinstance(v, QProject): qproj = v else: if qproj: v = GUIProject.from_qproject(qproj) else: raise Exception("No project object was loaded") self.set_project(v) self.set_current_file_name(file_name) self.saved_operation = self.pht.pos self.__check_saved_asterisk__() def save_project_to_file(self, file_name): self.pw.refresh_layouts() project = self.proj # Ensure that all machine nodes are in corresponding lists for d in project.descriptions: if isinstance(d, MachineNode): d.link(handle_system_bus=False) PyGenerator().serialize(open(file_name, "wb"), project) self.set_current_file_name(file_name) self.saved_operation = self.pht.pos self.__check_saved_asterisk__() def try_save_project_to_file(self, file_name): try: open(file_name, "wb").close() except IOError as e: if not e.errno == 13: # Do not remove read-only files try: remove(file_name) except: pass showerror(title=_("Cannot save project").get(), message=str(e)) return self.save_project_to_file(file_name) def on_save_as(self): fname = asksaveas(self, [(_("QDC GUI Project defining script"), ".py")], title=_("Save project")) if not fname: return self.try_save_project_to_file(fname) def on_save(self): try: fname = self.current_file_name except AttributeError: self.on_save_as() else: self.try_save_project_to_file(fname) def check_unsaved(self): if self.title_not_saved_asterisk.get() == "*": return askyesno( self, title=self.title_suffix, message= _("Current project has unsaved changes. They will be lost. Continue?" )) else: return True def on_new_project(self): if not self.check_unsaved(): return self.set_project(GUIProject()) self.set_current_file_name() """ There is nothing to save in just created project. So declare that all changes are saved. """ self.saved_operation = self.pht.pos self.__check_saved_asterisk__() def on_load(self): if not self.check_unsaved(): return fname = askopen(self, [(_("QDC GUI Project defining script"), ".py")], title=_("Load project")) if not fname: return try: self.load_project_from_file(fname) except Exception as e: showerror(title=_("Project loading failed").get(), message=str(e)) def update_qemu_build_path(self, bp): if bp is None: self.var_qemu_build_path.set( _("No QEMU build path selected").get()) else: self.var_qemu_build_path.set("QEMU: " + bp)