# This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # # Licence is LGPL, see LICENCE in the top-level directory require 'gtk2' module Metasm module Gui module Protect @@lasterror = Time.now def protect yield rescue Object puts $!.message, $!.backtrace # also dump on stdout, for c/c delay = Time.now-@@lasterror sleep 1-delay if delay < 1 # msgbox flood protection @@lasterror = Time.now messagebox([$!.message, $!.backtrace].join("\n"), $!.class.name) end end module Msgbox include Protect def toplevel if self.kind_of? Gtk::Window self else super() end end # shows a message box (non-modal) # args: message, title/optionhash def messagebox(*a) MessageBox.new(toplevel, *a) end # asks for user input, yields the result (unless 'cancel' clicked) # args: prompt, :text => default text, :title => title def inputbox(*a) InputBox.new(toplevel, *a) { |*ya| protect { yield(*ya) } } end # asks to choose a file to open, yields filename # args: title, :path => path def openfile(*a) OpenFile.new(toplevel, *a) { |*ya| protect { yield(*ya) } } end # same as openfile, but for writing a (new) file def savefile(*a) SaveFile.new(toplevel, *a) { |*ya| protect { yield(*ya) } } end # displays a popup showing a table, yields the selected row # args: title, [[col0 title, col1 title...], [col0 val0, col1 val0...], [val1], [val2]...] def listwindow(*a) ListWindow.new(toplevel, *a) { |*ya| protect { yield(*ya) } } end end # clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD) # text = clipboard.wait_for_text # clipboard.text = 'foo' # a widget that holds many other widgets, and displays only one of them at a time class ContainerChoiceWidget < Gtk::Notebook include Msgbox attr_accessor :views, :view_indexes def initialize(*a, &b) super() self.show_border = false self.show_tabs = false @views = {} @view_indexes = [] signal_connect('realize') { initialize_visible } if respond_to? :initialize_visible initialize_widget(*a, &b) show_all end def view(i) @views[i] end def showview(i) set_page @view_indexes.index(i) end def addview(name, w) @view_indexes << name @views[name] = w append_page(w, Gtk::Label.new(name.to_s)) end def curview @views[curview_index] end def curview_index return if page == -1 @view_indexes[page] end end class ContainerVBoxWidget < Gtk::VBox include Msgbox def initialize(*a, &b) super() signal_connect('realize') { initialize_visible } if respond_to? :initialize_visible signal_connect('size_request') { |w, alloc| resize(*alloc) } if respond_to? :resize self.spacing = 2 initialize_widget(*a, &b) end def resize_child(cld, w, h) pk = query_child_packing(cld) if h <= 0 pk[0] = true h = 1 else pk[0] = false end return if h == cld.allocation.height set_child_packing(cld, *pk) cld.set_height_request(h) end def redraw end end class DrawableWidget < Gtk::DrawingArea include Msgbox attr_accessor :parent_widget, :caret_x, :caret_y, :hl_word, :hl_word_re # this hash is used to determine the colors of the Gui elements (background, caret, ...) # modifications to it are only useful before the widget is first rendered (IE before Gui.main) attr_accessor :default_color_association # keypress event keyval traduction table Keyboard_trad = Gdk::Keyval.constants.grep(/^GDK_/).inject({}) { |h, cst| v = Gdk::Keyval.const_get(cst) key = cst.to_s.sub(/^GDK_/, '').sub(/^KEY_/, '').sub(/^KP_/, '') if key.length == 1 key = key[0] # ?a, ?b etc else key = key.downcase.to_sym key = { :page_up => :pgup, :page_down => :pgdown, :next => :pgdown, :escape => :esc, :return => :enter, :l1 => :f11, :l2 => :f12, :prior => :pgup, :menu => :popupmenu, :space => ?\ , :asciitilde => ?~, :quoteleft => ?`, :exclam => ?!, :at => ?@, :numbersign => ?#, :dollar => ?$, :percent => ?%, :asciicircum => ?^, :ampersand => ?&, :asterisk => ?*, :parenleft => ?(, :parenright => ?), :bracketleft => ?[, :bracketright => ?], :braceleft => ?{, :braceright => ?}, :less => ?<, :greater => ?>, :quotedbl => ?", :quoteright => ?', :coma => ?,, :period => ?., :colon => ?:, :semicolon => ?;, :slash => ?/, :equal => ?=, :plus => ?+, :minus => ?-, :question => ??, :backslash => ?\\, :underscore => ?_, :bar => ?|, :comma => ?,, :divide => ?/, :multiply => ?*, :subtract => ?-, :add => ?+ }.fetch(key, key) end h.update v => key } BasicColor = { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444', :red => 'f44', :darkred => '800', :palered => 'faa', :green => '4f4', :darkgreen => '080', :palegreen => 'afa', :blue => '44f', :darkblue => '008', :paleblue => 'aaf', :yellow => 'ff4', :darkyellow => '440', :paleyellow => 'ffa', :orange => 'fc8', } def initialize(*a, &b) @parent_widget = nil @caret_x = @caret_y = 0 # text cursor position @oldcaret_x = @oldcaret_y = 1 @hl_word = nil @layout = Pango::Layout.new Gdk::Pango.context # text rendering @color = {} @default_color_association = {:background => :palegrey} super() # events callbacks signal_connect('expose_event') { @w = window ; @gc = Gdk::GC.new(@w) protect { paint } @w = @gc = nil true } signal_connect('size_allocate') { |w, alloc| protect { resized(alloc.width, alloc.height) } } signal_connect('button_press_event') { |w, ev| @last_kb_ev = ev if keyboard_state(:control) next protect { click_ctrl(ev.x, ev.y) } if ev.event_type == Gdk::Event::Type::BUTTON_PRESS and ev.button == 1 and respond_to? :click_ctrl next end case ev.event_type when Gdk::Event::Type::BUTTON_PRESS grab_focus case ev.button when 1; protect { click(ev.x, ev.y) } if respond_to? :click end when Gdk::Event::Type::BUTTON2_PRESS case ev.button when 1; protect { doubleclick(ev.x, ev.y) } if respond_to? :doubleclick end end } signal_connect('motion_notify_event') { |w, ev| @last_kb_ev = ev if keyboard_state(:control) protect { mousemove_ctrl(ev.x, ev.y) } if respond_to? :mousemove_ctrl else protect { mousemove(ev.x, ev.y) } end } if respond_to? :mousemove signal_connect('button_release_event') { |w, ev| case ev.button when 1; protect { mouserelease(ev.x, ev.y) } if respond_to? :mouserelease when 3; protect { rightclick(ev.x, ev.y) } if respond_to? :rightclick end } signal_connect('scroll_event') { |w, ev| dir = case ev.direction when Gdk::EventScroll::Direction::UP; :up when Gdk::EventScroll::Direction::DOWN; :down else next end @last_kb_ev = ev if keyboard_state(:control) protect { mouse_wheel_ctrl(dir, ev.x, ev.y) } if respond_to? :mouse_wheel_ctrl else protect { mouse_wheel(dir, ev.x, ev.y) } end } if respond_to? :mouse_wheel signal_connect('key_press_event') { |w, ev| @last_kb_ev = ev key = Keyboard_trad[ev.keyval] if keyboard_state(:control) protect { keypress_ctrl(key) or (@parent_widget and @parent_widget.keypress_ctrl(key)) } else protect { keypress(key) or (@parent_widget and @parent_widget.keypress(key)) } end } signal_connect('realize') { BasicColor.each { |tag, val| @color[tag] = color(val) } set_color_association @default_color_association initialize_visible if respond_to? :initialize_visible } initialize_widget(*a, &b) # receive keyboard/mouse signals set_events Gdk::Event::ALL_EVENTS_MASK set_can_focus true set_font 'courier 10' end def initialize_widget end # create a color from a 'rgb' description def color(val) if not @color[val] v = case val.length when 3; val.scan(/./).map { |c| (c*4).to_i(16) } when 6; val.scan(/../).map { |c| (c+c).to_i(16) } end @color[val] = Gdk::Color.new(*v) window.colormap.alloc_color(@color[val], true, true) end @color[val] end def set_caret_from_click(x, y) @caret_x = (x-1).to_i / @font_width @caret_y = y.to_i / @font_height update_caret end # change the font of the widget # arg is a Gtk Fontdescription string (eg 'courier 10') def set_font(descr) @layout.font_description = Pango::FontDescription.new(descr) @layout.text = 'x' @font_width, @font_height = @layout.pixel_size gui_update end # change the color association # arg is a hash function symbol => color symbol # check #initialize/sig('realize') for initial function/color list # if called before the widget is first displayed onscreen, will register a hook to re-call itself later def set_color_association(hash) if not realized? sid = signal_connect('realize') { signal_handler_disconnect(sid) set_color_association(hash) } else hord = Hash.new { |h, k| h[k] = (hash[k] ? h[hash[k]] + 1 : 0) } hash.sort_by { |k, v| hord[k] }.each { |k, v| @color[k] = color(v) } modify_bg Gtk::STATE_NORMAL, @color[:background] gui_update end end def new_menu toplevel.new_menu end def addsubmenu(*a, &b) toplevel.addsubmenu(*a, &b) end def popupmenu(m, x, y) toplevel.popupmenu(m, (x+allocation.x).to_i, (y+allocation.y).to_i) end # update @hl_word from a line & offset, return nil if unchanged def update_hl_word(line, offset, mode=:asm) return if not line word = line[0...offset].to_s[/\w*$/] << line[offset..-1].to_s[/^\w*/] word = nil if word == '' if @hl_word != word if word if mode == :asm and defined?(@dasm) and @dasm re = @dasm.gui_hilight_word_regexp(word) else re = Regexp.escape word end @hl_word_re = /^(.*?)(\b(?:#{re})\b)/ end @hl_word = word end end def paint end # invalidate the whole widget area def redraw invalidate(0, 0, 1000000, 1000000) end def invalidate_caret(cx, cy, x=0, y=0) invalidate(x + cx*@font_width, y + cy*@font_height, 2, @font_height) end def invalidate(x, y, w, h) return if not window window.invalidate Gdk::Rectangle.new(x, y, w, h), false end def width allocation.width end def height allocation.height end def resized(w, h) redraw end def keypress(key) end def keypress_ctrl(key) end def gui_update redraw end def draw_color(col) @gc.set_foreground color(col) end def draw_rectangle(x, y, w, h) # GTK clips coords around 0x8000 return if x > 0x7000 or y > 0x7000 if x < -0x7000 w += x + 100 x = -100 end if y < -0x7000 h += y + 100 y = -100 end return if w <= 0 or h <= 0 w = 0x7000 if w > 0x7000 h = 0x7000 if h > 0x7000 @w.draw_rectangle(@gc, true, x, y, w, h) end def draw_rectangle_color(col, x, y, w, h) draw_color(col) draw_rectangle(x, y, w, h) end def draw_line(x, y, ex, ey) if x.abs > 0x7000 return if ex.abs > 0x7000 and ((ex < 0) == (x < 0)) ox = x x = ((x > 0) ? 0x7000 : -0x7000) y = ey+(x-ex)*(y-ey)/(ox-ex) end if ex.abs > 0x7000 oex = ex ex = ((ex > 0) ? 0x7000 : -0x7000) ey = y+(ex-x)*(ey-y)/(oex-x) end if y.abs > 0x7000 return if ey.abs > 0x7000 and ((ey < 0) == (y < 0)) oy = y y = ((y > 0) ? 0x7000 : -0x7000) x = ex+(y-ey)*(x-ex)/(oy-ey) end if ey.abs > 0x7000 oey = ey ey = ((ey > 0) ? 0x7000 : -0x7000) ex = x+(ey-y)*(ex-x)/(oey-y) end @w.draw_line(@gc, x, y, ex, ey) end def draw_line_color(col, x, y, ex, ey) draw_color(col) draw_line(x, y, ex, ey) end def draw_string(x, y, str) return if x.abs > 0x7000 or y.abs > 0x7000 @layout.text = str @w.draw_layout(@gc, x, y, @layout) end def draw_string_color(col, x, y, str) draw_color(col) draw_string(x, y, str) end # same as draw_string_color + hilight @hl_word_re def draw_string_hl(col, x, y, str) if @hl_word while str =~ @hl_word_re s1, s2 = $1, $2 draw_string_color(col, x, y, s1) x += s1.length*@font_width hl_w = s2.length*@font_width draw_rectangle_color(:hl_word_bg, x, y, hl_w, @font_height) draw_string_color(:hl_word, x, y, s2) x += hl_w str = str[s1.length+s2.length..-1] end end draw_string_color(col, x, y, str) end def clipboard_copy(buf) clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) clipboard.text = buf end def clipboard_paste clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) clipboard.wait_for_text end def keyboard_state(query=nil) case query when :control, :ctrl ev = @last_kb_ev and ev.state & Gdk::Window::CONTROL_MASK == Gdk::Window::CONTROL_MASK when :shift ev = @last_kb_ev and ev.state & Gdk::Window::SHIFT_MASK == Gdk::Window::SHIFT_MASK when :alt ev = @last_kb_ev and ev.state & Gdk::Window::MOD1_MASK == Gdk::Window::MOD1_MASK else [:control, :shift, :alt].find_all { |s| keyboard_state(s) } end end end module WindowPos def x; position[0]; end def x=(nx); move(nx, position[1]); end def y; position[1]; end def y=(ny); move(position[0], ny); end def width; size[0] ; end def width=(nw); resize(nw, size[1]); end def height; size[1] ; end def height=(nh); resize(size[0], nh); end end class MessageBox < Gtk::MessageDialog include WindowPos def initialize(owner, str, opts={}) owner = nil if owner and (not owner.kind_of? Gtk::Window or owner.destroyed?) owner ||= Gtk::Window.toplevels.first opts = {:title => opts} if opts.kind_of? String super(owner, Gtk::Dialog::DESTROY_WITH_PARENT, INFO, BUTTONS_CLOSE, str) self.title = opts[:title] if opts[:title] signal_connect('response') { destroy } show_all present # bring the window to the foreground & set focus end end class InputBox < Gtk::Dialog include WindowPos attr_accessor :label, :textwidget # shows a simplitic input box (eg window with a 1-line textbox + OK button), yields the text # TODO history, dropdown, autocomplete, contexts, 3D stereo surround, etc def initialize(owner, str, opts={}) owner ||= Gtk::Window.toplevels.first super(nil, owner, Gtk::Dialog::DESTROY_WITH_PARENT, [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT]) self.title = opts[:title] if opts[:title] @label = Gtk::Label.new(str) @textwidget = Gtk::TextView.new if opts[:text] @textwidget.buffer.text = opts[:text].to_s text_select_all end @@history ||= {} histkey = opts[:history] || str[0, 10] @history = (@@history[histkey] ||= []) @history_off = @history.length @textwidget.signal_connect('key_press_event') { |w, ev| key = DrawableWidget::Keyboard_trad[ev.keyval] case key when :escape response(RESPONSE_REJECT) true when :enter @history << @textwidget.buffer.text.to_s @history.pop if @history.last == '' @history.pop if @history.last == @history[-2] response(RESPONSE_ACCEPT) true when :up, :down txt = @textwidget.buffer.text.to_s if (@history_off < @history.length or @history.last != txt) @history[@history_off] = txt end @history_off += (key == :up ? -1 : 1) @history_off %= @history.length @textwidget.buffer.text = @history[@history_off].to_s text_select_all end } signal_connect('response') { |win, id| resp = @textwidget.buffer.text if id == RESPONSE_ACCEPT destroy yield resp.strip if resp true } vbox.pack_start label, false, false, 8 vbox.pack_start @textwidget, false, false, 8 Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_MOTION | Gtk::Drag::DEST_DEFAULT_DROP, [['text/plain', 0, 0], ['text/uri-list', 0, 0]], Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE) signal_connect('drag_data_received') { |w, dc, x, y, data, info, time| dc.targets.each { |target| next if target.name != 'text/plain' and target.name != 'text/uri-list' data.data.each_line { |l| l = l.chomp.sub(%r{^file://}, '') self.text = l } } Gtk::Drag.finish(dc, true, false, time) } show_all present end def text_select_all @textwidget.buffer.move_mark('selection_bound', @textwidget.buffer.start_iter) @textwidget.buffer.move_mark('insert', @textwidget.buffer.end_iter) end def text ; @textwidget.buffer.text ; end def text=(nt) ; @textwidget.buffer.text = nt ; end end class OpenFile < Gtk::FileChooserDialog include WindowPos @@currentfolder = nil # shows an asynchronous FileChooser window, yields the chosen filename # TODO save last path def initialize(owner, title, opts={}) owner ||= Gtk::Window.toplevels.first super(title, owner, Gtk::FileChooser::ACTION_OPEN, nil, [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL], [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT]) f = opts[:path] || @@currentfolder self.current_folder = f if f signal_connect('response') { |win, id| if id == Gtk::Dialog::RESPONSE_ACCEPT file = filename @@currentfolder = File.dirname(file) end destroy yield file if file true } show_all present end end class SaveFile < Gtk::FileChooserDialog include WindowPos @@currentfolder = nil # shows an asynchronous FileChooser window, yields the chosen filename # TODO save last path def initialize(owner, title, opts={}) owner ||= Gtk::Window.toplevels.first super(title, owner, Gtk::FileChooser::ACTION_SAVE, nil, [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL], [Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT]) f = opts[:path] || @@currentfolder self.current_folder = f if f signal_connect('response') { |win, id| if id == Gtk::Dialog::RESPONSE_ACCEPT file = filename @@currentfolder = File.dirname(file) end destroy yield file if file true } show_all present end end class ListWindow < Gtk::Dialog include WindowPos # shows a window with a list of items # the list is an array of arrays, displayed as String # the first array is the column names # each item clicked yields the block with the selected iterator, double-click also close the popup def initialize(owner, title, list, h={}) owner ||= Gtk::Window.toplevels.first super(title, owner, Gtk::Dialog::DESTROY_WITH_PARENT) cols = list.shift treeview = Gtk::TreeView.new treeview.model = Gtk::ListStore.new(*[String]*cols.length) treeview.selection.mode = Gtk::SELECTION_NONE if @color_callback = h[:color_callback] @drawable = DrawableWidget.new # needed for color()... @drawable.signal_emit('realize') end cols.each_with_index { |col, i| crt = Gtk::CellRendererText.new tvc = Gtk::TreeViewColumn.new(col, crt) tvc.sort_column_id = i tvc.set_cell_data_func(crt) { |_tvc, _crt, model, iter| _crt.text = iter[i] if @color_callback fu = (0...cols.length).map { |ii| iter[ii] } fg, bg = @color_callback[fu] fg ||= :black bg ||= :white _crt.foreground = @drawable.color(fg).to_s _crt.cell_background = @drawable.color(bg).to_s end } treeview.append_column tvc } list.each { |e| iter = treeview.model.append e.each_with_index { |v, i| iter[i] = v.to_s } } treeview.model.set_sort_column_id(0) treeview.signal_connect('cursor_changed') { |x| if iter = treeview.selection.selected yield iter end } treeview.signal_connect('row_activated') { destroy } signal_connect('destroy') { h[:ondestroy].call } if h[:ondestroy] remove vbox add Gtk::ScrolledWindow.new.add(treeview) toplevel.set_default_size cols.length*240, 400 show if not h[:noshow] # so that the 1st line is not selected by default treeview.selection.mode = Gtk::SELECTION_SINGLE end def show show_all present end end class Window < Gtk::Window include WindowPos include Msgbox attr_accessor :menu def initialize(*a, &b) super() signal_connect('destroy') { destroy_window } @vbox = Gtk::VBox.new add @vbox @menu = [] @menubar = Gtk::MenuBar.new @accel_group = Gtk::AccelGroup.new set_gravity Gdk::Window::GRAVITY_STATIC @vbox.add @menubar, 'expand' => false @child = nil s = Gdk::Screen.default set_default_size s.width*3/4, s.height*3/4 Gtk::Settings.default.gtk_menu_bar_accel = nil # disable F10 -> focus menubar (@@mainwindow_list ||= []) << self initialize_window(*a, &b) build_menu update_menu Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_MOTION | Gtk::Drag::DEST_DEFAULT_DROP, [['text/plain', 0, 0], ['text/uri-list', 0, 0]], Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE) signal_connect('drag_data_received') { |w, dc, x, y, data, info, time| dc.targets.each { |target| next if target.name != 'text/plain' and target.name != 'text/uri-list' data.data.each_line { |l| next if not @child or not @child.respond_to? :dragdropfile l = l.chomp.sub(%r{^file://}, '') protect { @child.dragdropfile(l) } } } Gtk::Drag.finish(dc, true, false, time) } show_all end def destroy_window @@mainwindow_list.delete self Gui.main_quit if @@mainwindow_list.empty? # XXX we don't call main_start ourself.. end def widget=(w) @vbox.remove @child if @child @child = w @vbox.add w if w show_all end def widget @child end def build_menu end def new_menu [] end # finds a menu by name (recursive) # returns a valid arg for addsubmenu(ret) def find_menu(name, from=@menu) name = name.gsub('_', '') if not l = from.find { |e| e.grep(::String).find { |es| es.gsub('_', '') == name } } l = from.map { |e| e.grep(::Array).map { |ae| find_menu(name, ae) }.compact.first }.compact.first end l.grep(::Array).first if l end def popupmenu(m, x, y) mh = Gtk::Menu.new m.each { |e| create_menu_item(mh, e) } mh.show_all mh.popup(nil, nil, 2, 0) { |_m, _x, _y, _p| [position[0]+x, position[1]+y, true] } end # append stuff to a menu # arglist: # empty = menu separator # string = menu entry display name (use a single '_' keyboard for shortcut, eg 'Sho_rtcut' => 'r') # :check = menu entry is a checkbox type, add a true/false argument to specify initial value # second string = keyboard shortcut (accelerator) - use '^' for Ctrl, and '' for special keys # a menu object = this entry will open a submenu (you must specify a name, and action is ignored) # the method takes a block or a Proc argument that will be run whenever the menu item is selected # # use @menu to reference the top-level menu bar # call update_menu when the menu is done def addsubmenu(menu, *args, &action) args << action if action menu << args menu.last end # make the window's MenuBar reflect the content of @menu def update_menu # clear @menubar.children.dup.each { |mc| @menubar.remove mc } # populate the menubar using @menu @menu.each { |e| create_menu_item(@menubar, e) } @menubar.show_all end def create_menu_item(menu, entry) args = entry.dup # recognise 'OPEN', 'SAVE' etc, with special icon/localisation stock = (Gtk::Stock.constants.map { |c| c.to_s } & args).first args.delete stock if stock accel = args.grep(/^\^?(\w|<\w+>)$/).first args.delete accel if accel check = args.delete :check action = args.grep(::Proc).first args.delete action if action if submenu = args.grep(::Array).first args.delete submenu sm = Gtk::Menu.new submenu.each { |e| create_menu_item(sm, e) } submenu = sm end label = args.shift if stock item = Gtk::ImageMenuItem.new(Gtk::Stock.const_get(stock)) begin item.label = label if label rescue # in some version of gtk, no #label= item = Gtk::MenuItem.new(label) if label end elsif check item = Gtk::CheckMenuItem.new(label) item.active = args.shift elsif label item = Gtk::MenuItem.new(label) else item = Gtk::MenuItem.new end item.set_submenu(submenu) if submenu if accel key = accel[-1] if key == ?> key = accel[/<(.*)>/, 1] key = DrawableWidget::Keyboard_trad.index(case key when 'enter', 'esc', 'tab', /^f(\d\d?)$/i; key.downcase.to_sym else ?? end) end key = key.unpack('C')[0] if key.kind_of? String # yay rb19 item.add_accelerator('activate', @accel_group, key, (accel[0] == ?^ ? Gdk::Window::CONTROL_MASK : 0), Gtk::ACCEL_VISIBLE) end if action a = action if check a = lambda { |it| it.active = action.call(it.active?) } end item.signal_connect('activate') { protect { a.call(item) } } end menu.append item item end def initialize_window end end class ToolWindow < Gtk::Dialog include WindowPos def initialize(parent=nil, *a, &b) super('toolwin', parent, Gtk::Dialog::DESTROY_WITH_PARENT) set_events Gdk::Event::ALL_EVENTS_MASK set_can_focus true @child = vbox initialize_window(*a, &b) show_all end def widget=(w) remove @child if @child @child = w add @child if @child.respond_to? :initial_size resize(*@child.initial_size) end show_all end def widget @child end end # start the Gui main loop def self.main Gtk.main end # ends the Gui main loop def self.main_quit Gtk.main_quit end # register a proc to be run whenever the gui loop is idle # if the proc returns nil/false, delete it def self.idle_add(&b) Gtk.idle_add(&b) end # run a single iteration of the main_loop # e.g. call this from time to time when doing heavy computation, to keep the UI somewhat responsive def self.main_iter Gtk.main_iteration_do(false) end end end require 'metasm/gui/dasm_main' require 'metasm/gui/debug'