コード例 #1
0
ファイル: qdc-gui.py プロジェクト: ufwt/qdt
 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
コード例 #2
0
ファイル: qdc-gui.py プロジェクト: ufwt/qdt
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()
コード例 #3
0
ファイル: qdc-gui.py プロジェクト: laerreal/qdt
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)