def __init__(self): Gtk.Box.__init__(self) self.set_orientation(Gtk.Orientation.VERTICAL) self.label_row = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.label = Gtk.Label().new("Hex address or expression") self.label_row.pack_start(self.label, False, False, 0) self.input_row = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.address_input = Gtk.Entry() self.address_input.set_editable(True) self.confirm_button = Gtk.Button() self.confirm_button.set_label("Load") self.input_row.pack_start(self.address_input, False, False, 0) self.input_row.pack_start(self.confirm_button, False, False, 0) self.pack_start(self.label_row, False, False, 2) self.pack_start(self.input_row, False, False, 0) self.set_margin_left(10) self.on_address_selected = EventBroadcaster() self.confirm_button.connect("clicked", lambda *x: self.on_address_selected.notify( self.address_input.get_text()))
def __init__(self, toolbar_builder, debugger): signals = { "toolbar-run": lambda *x: self.run(), "toolbar-continue": lambda *x: self.cont(), "toolbar-stop": lambda *x: self.stop(), "toolbar-pause": lambda *x: self.pause(), "toolbar-step-over": lambda *x: self.step_over(), "toolbar-step-in": lambda *x: self.step_in(), "toolbar-step-out": lambda *x: self.step_out() } toolbar_builder.connect_signals(signals) self.toolbar_builder = toolbar_builder self.toolbar = toolbar_builder.get_object("toolbar") self.debugger = debugger self.debugger.on_process_state_changed.subscribe( self._handle_process_state_change) self.debugger.on_debugger_state_changed.subscribe( self._handle_debugger_state_change) self.grp_halt_control = ["stop", "pause"] self.grp_step = ["continue", "step_over", "step_in", "step_out"] self.on_run_process = EventBroadcaster()
def __init__(self, debugger, file, *args): self.debugger = debugger self.file = os.path.abspath(file) self.args = args self.parent_thread = None self.on_signal = EventBroadcaster() self.msg_queue = Queue.Queue() self.child_pid = None self.last_signal = None
def __init__(self, canvas, **properties): """ @type canvas: drawing.canvas.Canvas """ self.canvas = canvas self._position = self._parse_property(properties, "position", Vector(0, 0), Vector.vectorize) """@type _position: drawing.vector.Vector""" self._margin = self._parse_property(properties, "margin", Margin.all(0)) """@type _margin: drawing.geometry.Margin""" self._padding = self._parse_property(properties, "padding", Padding.all(0)) """@type _padding: drawing.geometry.Padding""" self._request_size = self._parse_property(properties, "size", Size(-1, -1), Size.make_size) """@type _request_size: drawing.size.Size""" self._min_size = self._parse_property(properties, "min_size", Size(0, 0), Size.make_size) """@type _min_size: drawing.size.Size""" self._max_size = self._parse_property(properties, "max_size", Size(999, 999), Size.make_size) """@type _max_size: drawing.size.Size""" self.bg_color = self._parse_property(properties, "bg_color", Drawable.get_default_bg_color(), Color.make_color) """@type bg_color: drawing.drawable.Color""" self.name = self._parse_property(properties, "name", "") """@type name: str""" self.parent = None """@type parent: Drawable""" self.children = [] self._visible = True self.click_handler = ClickHandler(self) self.on_mouse_click = EventBroadcaster() self.on_mouse_enter = EventBroadcaster() self.on_mouse_leave = EventBroadcaster() self.on_mouse_click.subscribe(self.handle_mouse_click) self.on_mouse_enter.subscribe(self.handle_mouse_enter) self.on_mouse_enter.subscribe(self.handle_tooltip_start) self.on_mouse_leave.subscribe(self.handle_mouse_leave) self.on_mouse_leave.subscribe(self.handle_tooltip_end) self.canvas.register_drawable(self) self.cached_rect = None
def __init__(self): super(Canvas, self).__init__() self.set_hexpand(True) self.set_vexpand(True) self.bg_color = Color(0.8, 0.8, 0.8, 1.0) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect("draw", lambda canvas, cr: self._handle_draw(cr)) self.connect( "button-press-event", lambda widget, button_event: self._handle_press( button_event, True)) self.connect( "button-release-event", lambda widget, button_event: self._handle_press( button_event, False)) self.connect( "motion-notify-event", lambda widget, move_event: self._handle_mouse_move(move_event)) self.connect( "scroll-event", lambda widget, scroll_event: self. _handle_mouse_scroll(scroll_event)) self.mouse_data = MouseData(MouseButtonState.Up, MouseButtonState.Up, Vector(0, 0)) self.translation_handler = TranslationHandler(self) self.zoom = 1.0 self.zoom_limits = (0.5, 2.0) self.translation = Vector(0, 0) self.cr = None """@type cr: cairo.Context""" self.drawables = [] """@type drawables: list of drawable.Drawable""" self.drawable_registry = [] """@type drawable_registry: list of drawable.Drawable""" self.tooltip_drawable = None self.first_draw = True self.draw_scheduler = LayeredDrawScheduler(3) self.drag_manager = DragManager(self) self.on_load_start = EventBroadcaster() self.on_load_end = EventBroadcaster() self.redraw()
def __init__(self, debugger): super(SourceManager, self).__init__() self.debugger = debugger self.popup_enable() self.on_breakpoint_changed = EventBroadcaster() self.on_symbol_hover = EventBroadcaster() self.on_symbol_hover.subscribe(self._handle_symbol_hover) self.on_breakpoint_changed.subscribe(self._handle_breakpoint_change) self.debugger.on_process_state_changed.subscribe( self._handle_process_state_change) self.debugger.on_frame_changed.subscribe(self._handle_frame_change)
def __init__(self, debugger, language="cpp"): """ @type debugger: debugger.mi.MiDebugger @type language: str """ self.buffer = GtkSource.Buffer() super(SourceEditor, self).__init__(buffer=self.get_buffer()) self.debugger = debugger self.start_undoable() self.set_language(language) self.get_buffer().set_highlight_syntax(True) self.get_buffer().set_highlight_matching_brackets(True) self.set_editable(False) self.gutter_renderer = BreakpointRenderer( self, paths.get_resource("img/circle.png"), paths.get_resource("img/arrow.png")) gutter = self.get_gutter(Gtk.TextWindowType.LEFT) gutter.insert(self.gutter_renderer, 0) self.stop_undoable() self.set_show_line_numbers(True) self.set_highlight_current_line(True) self.set_show_right_margin(True) self.set_right_margin_position(80) self.file = None self.on_breakpoint_changed = EventBroadcaster() self.exec_line = None self.bp_lines = set() self.analyser = SourceAnalyzer() self.connect("motion-notify-event", lambda widget, event: self._handle_mouse_move(event)) self.connect("button-press-event", lambda widget, event: self._handle_mouse_click(event)) self.on_symbol_hover = EventBroadcaster() self.debugger.breakpoint_manager.on_breakpoint_changed.subscribe( self._handle_model_breakpoint_change) self.breakpoint_lines = []
class ValueEntry(Gtk.Frame): @require_gui_thread def __init__(self, title, text): Gtk.Frame.__init__(self) self.set_label(title) self.set_label_align(0.0, 0.0) self.box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.box.set_margin_bottom(5) self.box.set_margin_left(2) self.text_entry = Gtk.Entry() self.text_entry.set_text(text) self.confirm_button = Gtk.Button(label="Set") self.get_style_context().add_class("value-entry") self.box.pack_start(self.text_entry, False, False, 0) self.box.pack_start(self.confirm_button, False, False, 5) self.add(self.box) self.show_all() self.confirm_button.connect("clicked", lambda btn: self._handle_confirm_click()) self.on_value_entered = EventBroadcaster() @require_gui_thread def set_value(self, value): """ @type value: str """ self.text_entry.set_text(value) def _handle_confirm_click(self): value = self.text_entry.get_text() self.set_value("") self.on_value_entered.notify(value) self.hide()
def __init__(self, title, text): Gtk.Frame.__init__(self) self.set_label(title) self.set_label_align(0.0, 0.0) self.box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.box.set_margin_bottom(5) self.box.set_margin_left(2) self.text_entry = Gtk.Entry() self.text_entry.set_text(text) self.confirm_button = Gtk.Button(label="Set") self.get_style_context().add_class("value-entry") self.box.pack_start(self.text_entry, False, False, 0) self.box.pack_start(self.confirm_button, False, False, 5) self.add(self.box) self.show_all() self.confirm_button.connect("clicked", lambda btn: self._handle_confirm_click()) self.on_value_entered = EventBroadcaster()
def __init__(self, debugger, language="cpp"): """ @type debugger: debugger.mi.MiDebugger @type language: str """ self.buffer = GtkSource.Buffer() super(SourceEditor, self).__init__(buffer=self.get_buffer()) self.debugger = debugger self.start_undoable() self.set_language(language) self.get_buffer().set_highlight_syntax(True) self.get_buffer().set_highlight_matching_brackets(True) self.set_editable(False) self.gutter_renderer = BreakpointRenderer(self, paths.get_resource( "img/circle.png"), paths.get_resource( "img/arrow.png")) gutter = self.get_gutter(Gtk.TextWindowType.LEFT) gutter.insert(self.gutter_renderer, 0) self.stop_undoable() self.set_show_line_numbers(True) self.set_highlight_current_line(True) self.set_show_right_margin(True) self.set_right_margin_position(80) self.file = None self.on_breakpoint_changed = EventBroadcaster() self.exec_line = None self.bp_lines = set() self.analyser = SourceAnalyzer() self.connect("motion-notify-event", lambda widget, event: self._handle_mouse_move(event)) self.connect("button-press-event", lambda widget, event: self._handle_mouse_click(event)) self.on_symbol_hover = EventBroadcaster() self.debugger.breakpoint_manager.on_breakpoint_changed.subscribe( self._handle_model_breakpoint_change) self.breakpoint_lines = []
class AddressInput(Gtk.Box): def __init__(self): Gtk.Box.__init__(self) self.set_orientation(Gtk.Orientation.VERTICAL) self.label_row = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.label = Gtk.Label().new("Hex address or expression") self.label_row.pack_start(self.label, False, False, 0) self.input_row = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) self.address_input = Gtk.Entry() self.address_input.set_editable(True) self.confirm_button = Gtk.Button() self.confirm_button.set_label("Load") self.input_row.pack_start(self.address_input, False, False, 0) self.input_row.pack_start(self.confirm_button, False, False, 0) self.pack_start(self.label_row, False, False, 2) self.pack_start(self.input_row, False, False, 0) self.set_margin_left(10) self.on_address_selected = EventBroadcaster() self.confirm_button.connect("clicked", lambda *x: self.on_address_selected.notify( self.address_input.get_text())) def set_enabled(self, value): """ Sets whether the input should be enabled. @type value: bool """ self.confirm_button.set_sensitive(value) self.address_input.set_sensitive(value)
class ToolbarManager(object): def __init__(self, toolbar_builder, debugger): signals = { "toolbar-run": lambda *x: self.run(), "toolbar-continue": lambda *x: self.cont(), "toolbar-stop": lambda *x: self.stop(), "toolbar-pause": lambda *x: self.pause(), "toolbar-step-over": lambda *x: self.step_over(), "toolbar-step-in": lambda *x: self.step_in(), "toolbar-step-out": lambda *x: self.step_out() } toolbar_builder.connect_signals(signals) self.toolbar_builder = toolbar_builder self.toolbar = toolbar_builder.get_object("toolbar") self.debugger = debugger self.debugger.on_process_state_changed.subscribe( self._handle_process_state_change) self.debugger.on_debugger_state_changed.subscribe( self._handle_debugger_state_change) self.grp_halt_control = ["stop", "pause"] self.grp_step = ["continue", "step_over", "step_in", "step_out"] self.on_run_process = EventBroadcaster() @require_gui_thread def _get_items(self): return [self.toolbar.get_nth_item(i) for i in xrange(0, self.toolbar.get_n_items())] def _state_exited(self): self._change_grp_state(self.grp_halt_control, False) self._change_grp_state(self.grp_step, False) self._change_state("run", True) def _state_stopped(self): self._change_grp_state(self.grp_halt_control, False) self._change_grp_state(self.grp_step, True) self._change_state("stop", True) def _state_running(self): self._change_grp_state(self.grp_halt_control, True) self._change_grp_state(self.grp_step, False) self._change_state("run", False) def _handle_process_state_change(self, state, event_data): if state == ProcessState.Exited: self._state_exited() elif state == ProcessState.Stopped: self._state_stopped() elif state == ProcessState.Running: self._state_running() def _handle_debugger_state_change(self, state, old_value): if (state.is_set(DebuggerState.BinaryLoaded) and not state.is_set(DebuggerState.Running)): self._change_state("run", True) else: self._change_state("run", False) def _change_state(self, item_name, sensitive=True): run_on_gui(self._change_state_ui, item_name, sensitive) def _change_grp_state(self, group, sensitive=True): for item in group: self._change_state(item, sensitive) @require_gui_thread def _change_state_ui(self, item_name, sensitive=True): item = self.toolbar_builder.get_object(item_name) item.set_sensitive(sensitive) def run(self): self.on_run_process.notify() def cont(self): self.debugger.exec_continue() def stop(self): self.debugger.quit_program() def pause(self): self.debugger.exec_pause() def step_over(self): self.debugger.exec_step_over() def step_in(self): self.debugger.exec_step_in() def step_out(self): self.debugger.exec_step_out()
class ProgramRunner(object): def __init__(self, debugger, file, *args): self.debugger = debugger self.file = os.path.abspath(file) self.args = args self.parent_thread = None self.on_signal = EventBroadcaster() self.msg_queue = Queue.Queue() self.child_pid = None self.last_signal = None def run(self): assert not self.parent_thread self.parent_thread = threading.Thread(target=self._start_process) self.parent_thread.start() def exec_step_single(self): self._add_queue_command("step-single") def exec_continue(self): self._add_queue_command("continue") def exec_interrupt(self): try: os.kill(self.child_pid, signal.SIGUSR1) return True except: return False def exec_kill(self): try: os.kill(self.child_pid, signal.SIGKILL) return True except: return False def _add_queue_command(self, cmd): self.msg_queue.put(cmd) def _start_process(self): child_pid = os.fork() if child_pid: self.child_pid = child_pid self._parent() else: self._child() def _child(self): if ptrace.ptrace(ptrace.PTRACE_TRACEME) < 0: raise Exception() os.execl(self.file, self.file, *self.args) exit(0) def _parent(self): try: while True: pid, status = os.waitpid(self.child_pid, os.WNOHANG) if pid != 0: self._handle_child_status(status) try: command = self.msg_queue.get(True, 0.1) self._handle_command(self.child_pid, status, command) except Queue.Empty: pass except: traceback.print_exc() except OSError: self.on_signal.notify(ProcessState.Exited) except ProcessExitException: pass except: traceback.print_exc() self.child_pid = None self.parent_thread = None def _handle_child_status(self, status): if os.WIFSTOPPED(status): self._on_stop(status) elif os.WIFEXITED(status): self.on_signal.notify(ProcessState.Exited, os.WTERMSIG(status), os.WEXITSTATUS(status)) raise ProcessExitException() def _handle_command(self, pid, status, command): if command == "continue": self._do_continue(pid) elif command == "step-single": self._do_step_single(pid) def _on_stop(self, status): self.last_signal = os.WSTOPSIG(status) self.debugger.breakpoint_manager.set_breakpoints(self.child_pid) self.on_signal.notify(ProcessState.Stopped, self.last_signal) def _continue_after_breakpoint(self, exec_continue): pid = self.child_pid regs = ptrace.ptrace_getregs(pid) orig_address = regs.eip - 1 if self.debugger.breakpoint_manager.has_breakpoint_for_address( orig_address): self.debugger.breakpoint_manager.restore_instruction( pid, orig_address) regs.eip -= 1 assert ptrace.ptrace_setregs(pid, regs) self._do_step_single(pid) pid, status = os.waitpid(pid, 0) self.debugger.breakpoint_manager.set_breakpoints(pid) if exec_continue: self.exec_continue() else: self._handle_child_status(status) return True else: return False def _do_continue(self, pid): if self.last_signal == 5: # sigtrap if self._continue_after_breakpoint(True): return ptrace.ptrace(ptrace.PTRACE_CONT, pid) def _do_step_single(self, pid): if self.last_signal == 5: # sigtrap if self._continue_after_breakpoint(False): return ptrace.ptrace(ptrace.PTRACE_SINGLESTEP, pid)
class SourceManager(Gtk.Notebook): def __init__(self, debugger): super(SourceManager, self).__init__() self.debugger = debugger self.popup_enable() self.on_breakpoint_changed = EventBroadcaster() self.on_symbol_hover = EventBroadcaster() self.on_symbol_hover.subscribe(self._handle_symbol_hover) self.on_breakpoint_changed.subscribe(self._handle_breakpoint_change) self.debugger.on_process_state_changed.subscribe( self._handle_process_state_change) self.debugger.on_frame_changed.subscribe(self._handle_frame_change) def _handle_symbol_hover(self, source_editor, symbol): if self.debugger.process_state == ProcessState.Stopped: variable = self.debugger.variable_manager.get_variable(symbol) if variable and variable.address: source_editor.set_tooltip_text(str(variable)) def _handle_breakpoint_change(self, location, change_type): if change_type == BreakpointChangeType.Create: self.debugger.breakpoint_manager.add_breakpoint(*location) elif change_type == BreakpointChangeType.Delete: self.debugger.breakpoint_manager.remove_breakpoint(*location) def _handle_process_state_change(self, state, event_data): if state == ProcessState.Stopped: self._set_debugger_location() elif state == ProcessState.Exited: run_on_gui(self.unset_exec_line) def _handle_frame_change(self, frame): """ @param frame: frame.Frame """ self._set_debugger_location() def _set_debugger_location(self): location = self.debugger.file_manager.get_current_location() Logger.debug("Stop at {0}".format(location)) if location and location[0]: run_on_gui(self.set_exec_line, location[0], location[1]) @require_gui_thread def _create_label(self, path, widget): content = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) label = Gtk.Label(os.path.basename(path)) button = Gtk.Button() button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) button.set_relief(Gtk.ReliefStyle.NONE) button.set_focus_on_click(False) button.connect("clicked", lambda *x: self._close_tab(widget)) event_box = Gtk.EventBox.new() event_box.add(label) event_box.connect( "button-press-event", lambda source, event: self._handle_tab_label_press(event, widget)) content.pack_start(event_box, True, True, 0) content.pack_start(button, False, False, 0) content.show_all() return content @require_gui_thread def _close_tab(self, widget): widget.editor.on_breakpoint_changed.clear() self.remove_page(self.get_tabs().index(widget)) @require_gui_thread def _add_tab(self, file_path): editor = SourceEditor(self.debugger) editor.set_content_from_file(file_path) editor.set_hexpand(True) editor.set_vexpand(True) editor.show_all() window = SourceWindow(editor) label = self._create_label(file_path, window) menu_label = Gtk.Label(label=file_path) menu_label.set_alignment(0, 0) index = self.append_page_menu(window, label, menu_label) if index != -1: self.select_tab(index) self.set_tab_reorderable(window, True) editor.on_breakpoint_changed.redirect(self.on_breakpoint_changed) editor.on_symbol_hover.redirect(self.on_symbol_hover) return editor else: return None @require_gui_thread def _handle_tab_label_press(self, event, window): """ @type event: Gdk.EventButton @type window: SourceWindow """ if (event.type == Gdk.EventType.BUTTON_PRESS and event.button == 2): self._close_tab(window) @require_gui_thread def get_tabs(self): return [self.get_nth_page(i) for i in xrange(0, self.get_n_pages())] @require_gui_thread def open_file(self, file_path): for index, tab in enumerate(self.get_tabs()): if tab.editor.file == file_path: self.select_tab(index) return tab.editor return self._add_tab(file_path) @require_gui_thread def set_exec_line(self, file_path, line_number): self.unset_exec_line() tab = self.open_file(file_path) tab.set_exec_line(line_number - 1) @require_gui_thread def unset_exec_line(self): for tab in self.get_tabs(): tab.editor.unset_exec_line() @require_gui_thread def get_selected_editor(self): selected = self.get_current_page() if selected != -1: return self.get_nth_page(selected).editor else: return None @require_gui_thread def select_tab(self, index): self.set_current_page(index)
class SourceEditor(GtkSource.View): @staticmethod def load_file(path): try: return open(path).read() except: return None def __init__(self, debugger, language="cpp"): """ @type debugger: debugger.mi.MiDebugger @type language: str """ self.buffer = GtkSource.Buffer() super(SourceEditor, self).__init__(buffer=self.get_buffer()) self.debugger = debugger self.start_undoable() self.set_language(language) self.get_buffer().set_highlight_syntax(True) self.get_buffer().set_highlight_matching_brackets(True) self.set_editable(False) self.gutter_renderer = BreakpointRenderer( self, paths.get_resource("img/circle.png"), paths.get_resource("img/arrow.png")) gutter = self.get_gutter(Gtk.TextWindowType.LEFT) gutter.insert(self.gutter_renderer, 0) self.stop_undoable() self.set_show_line_numbers(True) self.set_highlight_current_line(True) self.set_show_right_margin(True) self.set_right_margin_position(80) self.file = None self.on_breakpoint_changed = EventBroadcaster() self.exec_line = None self.bp_lines = set() self.analyser = SourceAnalyzer() self.connect("motion-notify-event", lambda widget, event: self._handle_mouse_move(event)) self.connect("button-press-event", lambda widget, event: self._handle_mouse_click(event)) self.on_symbol_hover = EventBroadcaster() self.debugger.breakpoint_manager.on_breakpoint_changed.subscribe( self._handle_model_breakpoint_change) self.breakpoint_lines = [] def _handle_model_breakpoint_change(self, breakpoint): """ @type breakpoint: debugee.Breakpoint """ self._refresh_breakpoints() def _refresh_breakpoints(self): lines = [] if (self.file and self.debugger.state.is_set(DebuggerState.BinaryLoaded) and self.debugger.process_state != ProcessState.Running): for bp in self.debugger.breakpoint_manager.get_breakpoints(): if os.path.abspath(bp.location) == os.path.abspath(self.file): lines.append(bp.line - 1) self.breakpoint_lines = lines def _handle_mouse_move(self, event): x, y = self.window_to_buffer_coords(Gtk.TextWindowType.TEXT, event.x, event.y) iter = self.get_iter_at_location(x, y) line = iter.get_line() + 1 column = iter.get_line_offset() + 1 symbol = self.analyser.get_symbol_name(line, column) if symbol is not None: self.on_symbol_hover.notify(self, symbol) def _handle_mouse_click(self, event): if (event.type == Gdk.EventType.BUTTON_PRESS and self.get_window(Gtk.TextWindowType.LEFT) == event.window): x, y = self.window_to_buffer_coords(Gtk.TextWindowType.LEFT, event.x, event.y) iter = self.get_iter_at_location(x, y) if self.debugger.state.is_set(DebuggerState.BinaryLoaded): self.toggle_breakpoint(iter.get_line()) def get_buffer(self): return self.buffer def get_file(self): return self.file def get_breakpoint_lines(self): return self.breakpoint_lines @require_gui_thread def toggle_breakpoint(self, line): self.debugger.breakpoint_manager.toggle_breakpoint(self.file, line + 1) self.gutter_renderer.queue_draw() @require_gui_thread def set_language(self, key): manager = GtkSource.LanguageManager() language = manager.get_language(key) self.start_undoable() self.get_buffer().set_language(language) self.stop_undoable() @require_gui_thread def start_undoable(self): self.get_buffer().begin_not_undoable_action() @require_gui_thread def stop_undoable(self): self.get_buffer().end_not_undoable_action() @require_gui_thread def set_content_from_file(self, path): content = SourceEditor.load_file(path) if content: self.file = path self.start_undoable() self.get_buffer().set_text(content) self.stop_undoable() self.analyser.set_file(path) self._refresh_breakpoints() self.gutter_renderer.queue_draw() # show existing breakpoints @require_gui_thread def get_cursor_iter(self): cursor_rectangle = self.get_cursor_locations(None)[0] return self.get_iter_at_location(cursor_rectangle.x, cursor_rectangle.y) @require_gui_thread def get_current_column(self): return self.get_cursor_iter().get_line_offset() @require_gui_thread def get_current_line(self): return self.get_cursor_iter().get_line() @require_gui_thread def get_line_iters(self, line_number): start_line = self.get_buffer().get_iter_at_line(line_number) end_line = start_line.copy() end_line.forward_to_line_end() return (start_line, end_line) @require_gui_thread def set_exec_line(self, line_number): self.unset_exec_line() line_iters = self.get_line_iters(line_number) self.scroll_to_iter(line_iters[0], 0.0, True, 0.5, 0.5) self.exec_line = line_number self.gutter_renderer.queue_draw() @require_gui_thread def unset_exec_line(self): self.exec_line = None self.gutter_renderer.queue_draw() @require_gui_thread def undo(self): if self.get_buffer().can_undo(): self.get_buffer().undo() return True else: return False @require_gui_thread def redo(self): if self.get_buffer().can_redo(): self.get_buffer().redo() return True else: return False
class SourceManager(Gtk.Notebook): def __init__(self, debugger): super(SourceManager, self).__init__() self.debugger = debugger self.popup_enable() self.on_breakpoint_changed = EventBroadcaster() self.on_symbol_hover = EventBroadcaster() self.on_symbol_hover.subscribe(self._handle_symbol_hover) self.on_breakpoint_changed.subscribe(self._handle_breakpoint_change) self.debugger.on_process_state_changed.subscribe( self._handle_process_state_change) self.debugger.on_frame_changed.subscribe(self._handle_frame_change) def _handle_symbol_hover(self, source_editor, symbol): if self.debugger.process_state == ProcessState.Stopped: variable = self.debugger.variable_manager.get_variable(symbol) if variable and variable.address: source_editor.set_tooltip_text(str(variable)) def _handle_breakpoint_change(self, location, change_type): if change_type == BreakpointChangeType.Create: self.debugger.breakpoint_manager.add_breakpoint(*location) elif change_type == BreakpointChangeType.Delete: self.debugger.breakpoint_manager.remove_breakpoint(*location) def _handle_process_state_change(self, state, event_data): if state == ProcessState.Stopped: self._set_debugger_location() elif state == ProcessState.Exited: run_on_gui(self.unset_exec_line) def _handle_frame_change(self, frame): """ @param frame: frame.Frame """ self._set_debugger_location() def _set_debugger_location(self): location = self.debugger.file_manager.get_current_location() Logger.debug("Stop at {0}".format(location)) if location and location[0]: run_on_gui(self.set_exec_line, location[0], location[1]) @require_gui_thread def _create_label(self, path, widget): content = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) label = Gtk.Label(os.path.basename(path)) button = Gtk.Button() button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) button.set_relief(Gtk.ReliefStyle.NONE) button.set_focus_on_click(False) button.connect("clicked", lambda *x: self._close_tab(widget)) event_box = Gtk.EventBox.new() event_box.add(label) event_box.connect("button-press-event", lambda source, event: self._handle_tab_label_press( event, widget)) content.pack_start(event_box, True, True, 0) content.pack_start(button, False, False, 0) content.show_all() return content @require_gui_thread def _close_tab(self, widget): widget.editor.on_breakpoint_changed.clear() self.remove_page(self.get_tabs().index(widget)) @require_gui_thread def _add_tab(self, file_path): editor = SourceEditor(self.debugger) editor.set_content_from_file(file_path) editor.set_hexpand(True) editor.set_vexpand(True) editor.show_all() window = SourceWindow(editor) label = self._create_label(file_path, window) menu_label = Gtk.Label(label=file_path) menu_label.set_alignment(0, 0) index = self.append_page_menu(window, label, menu_label) if index != -1: self.select_tab(index) self.set_tab_reorderable(window, True) editor.on_breakpoint_changed.redirect(self.on_breakpoint_changed) editor.on_symbol_hover.redirect(self.on_symbol_hover) return editor else: return None @require_gui_thread def _handle_tab_label_press(self, event, window): """ @type event: Gdk.EventButton @type window: SourceWindow """ if (event.type == Gdk.EventType.BUTTON_PRESS and event.button == 2): self._close_tab(window) @require_gui_thread def get_tabs(self): return [self.get_nth_page(i) for i in xrange(0, self.get_n_pages())] @require_gui_thread def open_file(self, file_path): for index, tab in enumerate(self.get_tabs()): if tab.editor.file == file_path: self.select_tab(index) return tab.editor return self._add_tab(file_path) @require_gui_thread def set_exec_line(self, file_path, line_number): self.unset_exec_line() tab = self.open_file(file_path) tab.set_exec_line(line_number - 1) @require_gui_thread def unset_exec_line(self): for tab in self.get_tabs(): tab.editor.unset_exec_line() @require_gui_thread def get_selected_editor(self): selected = self.get_current_page() if selected != -1: return self.get_nth_page(selected).editor else: return None @require_gui_thread def select_tab(self, index): self.set_current_page(index)
class ToolbarManager(object): def __init__(self, toolbar_builder, debugger): signals = { "toolbar-run": lambda *x: self.run(), "toolbar-continue": lambda *x: self.cont(), "toolbar-stop": lambda *x: self.stop(), "toolbar-pause": lambda *x: self.pause(), "toolbar-step-over": lambda *x: self.step_over(), "toolbar-step-in": lambda *x: self.step_in(), "toolbar-step-out": lambda *x: self.step_out() } toolbar_builder.connect_signals(signals) self.toolbar_builder = toolbar_builder self.toolbar = toolbar_builder.get_object("toolbar") self.debugger = debugger self.debugger.on_process_state_changed.subscribe( self._handle_process_state_change) self.debugger.on_debugger_state_changed.subscribe( self._handle_debugger_state_change) self.grp_halt_control = ["stop", "pause"] self.grp_step = ["continue", "step_over", "step_in", "step_out"] self.on_run_process = EventBroadcaster() @require_gui_thread def _get_items(self): return [ self.toolbar.get_nth_item(i) for i in xrange(0, self.toolbar.get_n_items()) ] def _state_exited(self): self._change_grp_state(self.grp_halt_control, False) self._change_grp_state(self.grp_step, False) self._change_state("run", True) def _state_stopped(self): self._change_grp_state(self.grp_halt_control, False) self._change_grp_state(self.grp_step, True) self._change_state("stop", True) def _state_running(self): self._change_grp_state(self.grp_halt_control, True) self._change_grp_state(self.grp_step, False) self._change_state("run", False) def _handle_process_state_change(self, state, event_data): if state == ProcessState.Exited: self._state_exited() elif state == ProcessState.Stopped: self._state_stopped() elif state == ProcessState.Running: self._state_running() def _handle_debugger_state_change(self, state, old_value): if (state.is_set(DebuggerState.BinaryLoaded) and not state.is_set(DebuggerState.Running)): self._change_state("run", True) else: self._change_state("run", False) def _change_state(self, item_name, sensitive=True): run_on_gui(self._change_state_ui, item_name, sensitive) def _change_grp_state(self, group, sensitive=True): for item in group: self._change_state(item, sensitive) @require_gui_thread def _change_state_ui(self, item_name, sensitive=True): item = self.toolbar_builder.get_object(item_name) item.set_sensitive(sensitive) def run(self): self.on_run_process.notify() def cont(self): self.debugger.exec_continue() def stop(self): self.debugger.quit_program() def pause(self): self.debugger.exec_pause() def step_over(self): self.debugger.exec_step_over() def step_in(self): self.debugger.exec_step_in() def step_out(self): self.debugger.exec_step_out()
class Drawable(object): @staticmethod def get_default_bg_color(): return Color(0.5, 0.5, 0.5, 1.0) def __init__(self, canvas, **properties): """ @type canvas: drawing.canvas.Canvas """ self.canvas = canvas self._position = self._parse_property(properties, "position", Vector(0, 0), Vector.vectorize) """@type _position: drawing.vector.Vector""" self._margin = self._parse_property(properties, "margin", Margin.all(0)) """@type _margin: drawing.geometry.Margin""" self._padding = self._parse_property(properties, "padding", Padding.all(0)) """@type _padding: drawing.geometry.Padding""" self._request_size = self._parse_property(properties, "size", Size(-1, -1), Size.make_size) """@type _request_size: drawing.size.Size""" self._min_size = self._parse_property(properties, "min_size", Size(0, 0), Size.make_size) """@type _min_size: drawing.size.Size""" self._max_size = self._parse_property(properties, "max_size", Size(999, 999), Size.make_size) """@type _max_size: drawing.size.Size""" self.bg_color = self._parse_property(properties, "bg_color", Drawable.get_default_bg_color(), Color.make_color) """@type bg_color: drawing.drawable.Color""" self.name = self._parse_property(properties, "name", "") """@type name: str""" self.parent = None """@type parent: Drawable""" self.children = [] self._visible = True self.click_handler = ClickHandler(self) self.on_mouse_click = EventBroadcaster() self.on_mouse_enter = EventBroadcaster() self.on_mouse_leave = EventBroadcaster() self.on_mouse_click.subscribe(self.handle_mouse_click) self.on_mouse_enter.subscribe(self.handle_mouse_enter) self.on_mouse_enter.subscribe(self.handle_tooltip_start) self.on_mouse_leave.subscribe(self.handle_mouse_leave) self.on_mouse_leave.subscribe(self.handle_tooltip_end) self.canvas.register_drawable(self) self.cached_rect = None @property def visible(self): return self._visible @visible.setter def visible(self, value): self._visible = value self.invalidate() @property def margin(self): """ @rtype: drawing.geometry.Margin """ return self._margin @margin.setter def margin(self, value): """ @type value: drawing.geometry.Margin """ self._margin = value self.invalidate() @property def padding(self): """ @rtype: drawing.geometry.Padding """ return self._padding @padding.setter def padding(self, value): """ @type value: drawing.geometry.Padding """ self._padding = value self.invalidate() @property def request_size(self): """ @rtype: drawing.size.Size """ return self._request_size @request_size.setter def request_size(self, value): """ @type value: drawing.size.Size """ self._request_size = value self.invalidate() @property def min_size(self): """ @rtype: drawing.size.Size """ return self._min_size @min_size.setter def min_size(self, value): """ @type value: drawing.size.Size """ self._min_size = value self.invalidate() @property def max_size(self): """ @rtype: drawing.size.Size """ return self._max_size @max_size.setter def max_size(self, value): """ @type value: drawing.size.Size """ self._max_size = value self.invalidate() @property def position(self): """ @rtype: drawing.vector.Vector """ return self._position @position.setter def position(self, value): """ @type value: drawing.vector.Vector """ self._position = value.copy() self.cached_rect = None self.place_children() def toggle(self): self.visible = not self.visible def hide(self): self.visible = False def show(self): self.visible = True def get_content_size(self): raise NotImplementedError() def get_computed_size(self): content_size = self.get_content_size() width = self.request_size.width if width < 0: width = content_size.width height = self.request_size.height if height < 0: height = content_size.height if self.min_size.width >= 0: width = max((width, self.min_size.width)) if self.min_size.height >= 0: height = max((height, self.min_size.height)) if self.max_size.width >= 0: width = min((width, self.max_size.width)) if self.max_size.height >= 0: height = min((height, self.max_size.height)) return Size(width, height) def add_child(self, child): """ @type child: Drawable """ self.children.append(child) child.parent = self self.click_handler.propagate_handler(child.click_handler) self.invalidate() def add_children(self, children): """ @type children: list of Drawable | tuple of Drawable """ self.children += children for child in children: child.parent = self self.click_handler.propagate_handler(child.click_handler) if len(children) > 0: self.invalidate() def get_child_at(self, position): """ @type position: vector.Vector @rtype: Drawable """ for child in self.children: drawable = child.get_child_at(position) if drawable: return drawable if self.get_rect().is_point_inside(position): return self else: return None def handle_mouse_event(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ self.click_handler.handle_mouse_event(mouse_data) def handle_mouse_click(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ pass def handle_mouse_enter(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ pass def handle_mouse_leave(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ pass def handle_tooltip_start(self, mouse_data): """ @type mouse_data: mouse.MouseData """ tooltip = self.get_tooltip() if tooltip is not None: self.canvas.set_drawable_tooltip(self, tooltip) def handle_tooltip_end(self, mouse_data): """ @type mouse_data: mouse.MouseData """ self.canvas.set_drawable_tooltip(self, None) def handle_drag_cancel(self): pass def handle_drag_start(self): pass def handle_drag_end(self, target): """ @type target: Drawable """ pass def on_child_changed(self, child): """ @type child: Drawable """ self.invalidate() def get_tooltip(self): """ @rtype: str | None """ return None def get_rect(self): if self.cached_rect: return self.cached_rect if not self.visible: return RectangleBBox(self.position) self.cached_rect = RectangleBBox(self.position, self.get_computed_size() + self.padding.to_size()) return self.cached_rect def get_center(self): """ Returns the center of the drawable. @rtype: canvas.vector.Vector """ rect = self.get_rect() x = rect.x + rect.width / 2.0 y = rect.y + rect.height / 2.0 return Vector(x, y) def place_children(self): pass def draw(self): if self.visible: for child in self.children: child.draw() def invalidate(self): """ Invalidates this drawable, placing it's children and sending it's parent a message that it has changd. """ self.cached_rect = None if self.parent: self.parent.on_child_changed(self) self.place_children() def _parse_property(self, properties, key, default, modifier=None): """ Parses property from property dictionary. Modifier modifies the resulting value. @type properties: dict @type key: str @type default: object @type modifier: function @rtype: object """ if key in properties: if modifier: return modifier(properties[key]) else: return properties[key] else: return default
class Drawable(object): @staticmethod def get_default_bg_color(): return Color(0.5, 0.5, 0.5, 1.0) def __init__(self, canvas, **properties): """ @type canvas: drawing.canvas.Canvas """ self.canvas = canvas self._position = self._parse_property(properties, "position", Vector(0, 0), Vector.vectorize) """@type _position: drawing.vector.Vector""" self._margin = self._parse_property(properties, "margin", Margin.all(0)) """@type _margin: drawing.geometry.Margin""" self._padding = self._parse_property(properties, "padding", Padding.all(0)) """@type _padding: drawing.geometry.Padding""" self._request_size = self._parse_property(properties, "size", Size(-1, -1), Size.make_size) """@type _request_size: drawing.size.Size""" self._min_size = self._parse_property(properties, "min_size", Size(0, 0), Size.make_size) """@type _min_size: drawing.size.Size""" self._max_size = self._parse_property(properties, "max_size", Size(999, 999), Size.make_size) """@type _max_size: drawing.size.Size""" self.bg_color = self._parse_property(properties, "bg_color", Drawable.get_default_bg_color(), Color.make_color) """@type bg_color: drawing.drawable.Color""" self.name = self._parse_property(properties, "name", "") """@type name: str""" self.parent = None """@type parent: Drawable""" self.children = [] self._visible = True self.click_handler = ClickHandler(self) self.on_mouse_click = EventBroadcaster() self.on_mouse_enter = EventBroadcaster() self.on_mouse_leave = EventBroadcaster() self.on_mouse_click.subscribe(self.handle_mouse_click) self.on_mouse_enter.subscribe(self.handle_mouse_enter) self.on_mouse_enter.subscribe(self.handle_tooltip_start) self.on_mouse_leave.subscribe(self.handle_mouse_leave) self.on_mouse_leave.subscribe(self.handle_tooltip_end) self.canvas.register_drawable(self) self.cached_rect = None @property def visible(self): return self._visible @visible.setter def visible(self, value): self._visible = value self.invalidate() @property def margin(self): """ @rtype: drawing.geometry.Margin """ return self._margin @margin.setter def margin(self, value): """ @type value: drawing.geometry.Margin """ self._margin = value self.invalidate() @property def padding(self): """ @rtype: drawing.geometry.Padding """ return self._padding @padding.setter def padding(self, value): """ @type value: drawing.geometry.Padding """ self._padding = value self.invalidate() @property def request_size(self): """ @rtype: drawing.size.Size """ return self._request_size @request_size.setter def request_size(self, value): """ @type value: drawing.size.Size """ self._request_size = value self.invalidate() @property def min_size(self): """ @rtype: drawing.size.Size """ return self._min_size @min_size.setter def min_size(self, value): """ @type value: drawing.size.Size """ self._min_size = value self.invalidate() @property def max_size(self): """ @rtype: drawing.size.Size """ return self._max_size @max_size.setter def max_size(self, value): """ @type value: drawing.size.Size """ self._max_size = value self.invalidate() @property def position(self): """ @rtype: drawing.vector.Vector """ return self._position @position.setter def position(self, value): """ @type value: drawing.vector.Vector """ self._position = value.copy() self.cached_rect = None self.place_children() def toggle(self): self.visible = not self.visible def hide(self): self.visible = False def show(self): self.visible = True def get_content_size(self): raise NotImplementedError() def get_computed_size(self): content_size = self.get_content_size() width = self.request_size.width if width < 0: width = content_size.width height = self.request_size.height if height < 0: height = content_size.height if self.min_size.width >= 0: width = max((width, self.min_size.width)) if self.min_size.height >= 0: height = max((height, self.min_size.height)) if self.max_size.width >= 0: width = min((width, self.max_size.width)) if self.max_size.height >= 0: height = min((height, self.max_size.height)) return Size(width, height) def add_child(self, child): """ @type child: Drawable """ self.children.append(child) child.parent = self self.click_handler.propagate_handler(child.click_handler) self.invalidate() def add_children(self, children): """ @type children: list of Drawable | tuple of Drawable """ self.children += children for child in children: child.parent = self self.click_handler.propagate_handler(child.click_handler) if len(children) > 0: self.invalidate() def get_child_at(self, position): """ @type position: vector.Vector @rtype: Drawable """ for child in self.children: drawable = child.get_child_at(position) if drawable: return drawable if self.get_rect().is_point_inside(position): return self else: return None def handle_mouse_event(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ self.click_handler.handle_mouse_event(mouse_data) def handle_mouse_click(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ pass def handle_mouse_enter(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ pass def handle_mouse_leave(self, mouse_data): """ @type mouse_data: drawing.mouse.MouseData """ pass def handle_tooltip_start(self, mouse_data): """ @type mouse_data: mouse.MouseData """ tooltip = self.get_tooltip() if tooltip is not None: self.canvas.set_drawable_tooltip(self, tooltip) def handle_tooltip_end(self, mouse_data): """ @type mouse_data: mouse.MouseData """ self.canvas.set_drawable_tooltip(self, None) def handle_drag_cancel(self): pass def handle_drag_start(self): pass def handle_drag_end(self, target): """ @type target: Drawable """ pass def on_child_changed(self, child): """ @type child: Drawable """ self.invalidate() def get_tooltip(self): """ @rtype: str | None """ return None def get_rect(self): if self.cached_rect: return self.cached_rect if not self.visible: return RectangleBBox(self.position) self.cached_rect = RectangleBBox( self.position, self.get_computed_size() + self.padding.to_size()) return self.cached_rect def get_center(self): """ Returns the center of the drawable. @rtype: canvas.vector.Vector """ rect = self.get_rect() x = rect.x + rect.width / 2.0 y = rect.y + rect.height / 2.0 return Vector(x, y) def place_children(self): pass def draw(self): if self.visible: for child in self.children: child.draw() def invalidate(self): """ Invalidates this drawable, placing it's children and sending it's parent a message that it has changd. """ self.cached_rect = None if self.parent: self.parent.on_child_changed(self) self.place_children() def _parse_property(self, properties, key, default, modifier=None): """ Parses property from property dictionary. Modifier modifies the resulting value. @type properties: dict @type key: str @type default: object @type modifier: function @rtype: object """ if key in properties: if modifier: return modifier(properties[key]) else: return properties[key] else: return default
class SourceEditor(GtkSource.View): @staticmethod def load_file(path): try: return open(path).read() except: return None def __init__(self, debugger, language="cpp"): """ @type debugger: debugger.mi.MiDebugger @type language: str """ self.buffer = GtkSource.Buffer() super(SourceEditor, self).__init__(buffer=self.get_buffer()) self.debugger = debugger self.start_undoable() self.set_language(language) self.get_buffer().set_highlight_syntax(True) self.get_buffer().set_highlight_matching_brackets(True) self.set_editable(False) self.gutter_renderer = BreakpointRenderer(self, paths.get_resource( "img/circle.png"), paths.get_resource( "img/arrow.png")) gutter = self.get_gutter(Gtk.TextWindowType.LEFT) gutter.insert(self.gutter_renderer, 0) self.stop_undoable() self.set_show_line_numbers(True) self.set_highlight_current_line(True) self.set_show_right_margin(True) self.set_right_margin_position(80) self.file = None self.on_breakpoint_changed = EventBroadcaster() self.exec_line = None self.bp_lines = set() self.analyser = SourceAnalyzer() self.connect("motion-notify-event", lambda widget, event: self._handle_mouse_move(event)) self.connect("button-press-event", lambda widget, event: self._handle_mouse_click(event)) self.on_symbol_hover = EventBroadcaster() self.debugger.breakpoint_manager.on_breakpoint_changed.subscribe( self._handle_model_breakpoint_change) self.breakpoint_lines = [] def _handle_model_breakpoint_change(self, breakpoint): """ @type breakpoint: debugee.Breakpoint """ self._refresh_breakpoints() def _refresh_breakpoints(self): lines = [] if (self.file and self.debugger.state.is_set(DebuggerState.BinaryLoaded) and self.debugger.process_state != ProcessState.Running): for bp in self.debugger.breakpoint_manager.get_breakpoints(): if os.path.abspath(bp.location) == os.path.abspath(self.file): lines.append(bp.line - 1) self.breakpoint_lines = lines def _handle_mouse_move(self, event): x, y = self.window_to_buffer_coords(Gtk.TextWindowType.TEXT, event.x, event.y) iter = self.get_iter_at_location(x, y) line = iter.get_line() + 1 column = iter.get_line_offset() + 1 symbol = self.analyser.get_symbol_name(line, column) if symbol is not None: self.on_symbol_hover.notify(self, symbol) def _handle_mouse_click(self, event): if (event.type == Gdk.EventType.BUTTON_PRESS and self.get_window(Gtk.TextWindowType.LEFT) == event.window): x, y = self.window_to_buffer_coords(Gtk.TextWindowType.LEFT, event.x, event.y) iter = self.get_iter_at_location(x, y) if self.debugger.state.is_set(DebuggerState.BinaryLoaded): self.toggle_breakpoint(iter.get_line()) def get_buffer(self): return self.buffer def get_file(self): return self.file def get_breakpoint_lines(self): return self.breakpoint_lines @require_gui_thread def toggle_breakpoint(self, line): self.debugger.breakpoint_manager.toggle_breakpoint(self.file, line + 1) self.gutter_renderer.queue_draw() @require_gui_thread def set_language(self, key): manager = GtkSource.LanguageManager() language = manager.get_language(key) self.start_undoable() self.get_buffer().set_language(language) self.stop_undoable() @require_gui_thread def start_undoable(self): self.get_buffer().begin_not_undoable_action() @require_gui_thread def stop_undoable(self): self.get_buffer().end_not_undoable_action() @require_gui_thread def set_content_from_file(self, path): content = SourceEditor.load_file(path) if content: self.file = path self.start_undoable() self.get_buffer().set_text(content) self.stop_undoable() self.analyser.set_file(path) self._refresh_breakpoints() self.gutter_renderer.queue_draw() # show existing breakpoints @require_gui_thread def get_cursor_iter(self): cursor_rectangle = self.get_cursor_locations(None)[0] return self.get_iter_at_location(cursor_rectangle.x, cursor_rectangle.y) @require_gui_thread def get_current_column(self): return self.get_cursor_iter().get_line_offset() @require_gui_thread def get_current_line(self): return self.get_cursor_iter().get_line() @require_gui_thread def get_line_iters(self, line_number): start_line = self.get_buffer().get_iter_at_line(line_number) end_line = start_line.copy() end_line.forward_to_line_end() return (start_line, end_line) @require_gui_thread def set_exec_line(self, line_number): self.unset_exec_line() line_iters = self.get_line_iters(line_number) self.scroll_to_iter(line_iters[0], 0.0, True, 0.5, 0.5) self.exec_line = line_number self.gutter_renderer.queue_draw() @require_gui_thread def unset_exec_line(self): self.exec_line = None self.gutter_renderer.queue_draw() @require_gui_thread def undo(self): if self.get_buffer().can_undo(): self.get_buffer().undo() return True else: return False @require_gui_thread def redo(self): if self.get_buffer().can_redo(): self.get_buffer().redo() return True else: return False