Exemple #1
0
    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)
Exemple #2
0
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())
Exemple #3
0
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())
Exemple #4
0
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())
Exemple #5
0
    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)
Exemple #6
0
    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)
Exemple #7
0
    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)
Exemple #8
0
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()
Exemple #9
0
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()
Exemple #10
0
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."))
Exemple #11
0
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)