metasm 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.hgtags +3 -0
- data/Gemfile +1 -0
- data/INSTALL +61 -0
- data/LICENCE +458 -0
- data/README +29 -21
- data/Rakefile +10 -0
- data/TODO +10 -12
- data/doc/code_organisation.txt +2 -0
- data/doc/core/DynLdr.txt +247 -0
- data/doc/core/ExeFormat.txt +43 -0
- data/doc/core/Expression.txt +220 -0
- data/doc/core/GNUExports.txt +27 -0
- data/doc/core/Ia32.txt +236 -0
- data/doc/core/SerialStruct.txt +108 -0
- data/doc/core/VirtualString.txt +145 -0
- data/doc/core/WindowsExports.txt +61 -0
- data/doc/core/index.txt +1 -0
- data/doc/style.css +6 -3
- data/doc/usage/debugger.txt +327 -0
- data/doc/usage/index.txt +1 -0
- data/doc/use_cases.txt +2 -2
- data/metasm.gemspec +22 -0
- data/{lib/metasm.rb → metasm.rb} +11 -3
- data/{lib/metasm → metasm}/compile_c.rb +13 -7
- data/metasm/cpu/arc.rb +8 -0
- data/metasm/cpu/arc/decode.rb +425 -0
- data/metasm/cpu/arc/main.rb +191 -0
- data/metasm/cpu/arc/opcodes.rb +588 -0
- data/{lib/metasm → metasm/cpu}/arm.rb +7 -5
- data/{lib/metasm → metasm/cpu}/arm/debug.rb +2 -2
- data/{lib/metasm → metasm/cpu}/arm/decode.rb +13 -12
- data/{lib/metasm → metasm/cpu}/arm/encode.rb +23 -8
- data/{lib/metasm → metasm/cpu}/arm/main.rb +0 -3
- data/metasm/cpu/arm/opcodes.rb +324 -0
- data/{lib/metasm → metasm/cpu}/arm/parse.rb +25 -13
- data/{lib/metasm → metasm/cpu}/arm/render.rb +2 -2
- data/metasm/cpu/arm64.rb +15 -0
- data/metasm/cpu/arm64/debug.rb +38 -0
- data/metasm/cpu/arm64/decode.rb +289 -0
- data/metasm/cpu/arm64/encode.rb +41 -0
- data/metasm/cpu/arm64/main.rb +105 -0
- data/metasm/cpu/arm64/opcodes.rb +232 -0
- data/metasm/cpu/arm64/parse.rb +20 -0
- data/metasm/cpu/arm64/render.rb +95 -0
- data/{lib/metasm/ppc.rb → metasm/cpu/bpf.rb} +2 -4
- data/metasm/cpu/bpf/decode.rb +142 -0
- data/metasm/cpu/bpf/main.rb +60 -0
- data/metasm/cpu/bpf/opcodes.rb +81 -0
- data/metasm/cpu/bpf/render.rb +41 -0
- data/metasm/cpu/cy16.rb +9 -0
- data/metasm/cpu/cy16/decode.rb +253 -0
- data/metasm/cpu/cy16/main.rb +63 -0
- data/metasm/cpu/cy16/opcodes.rb +78 -0
- data/metasm/cpu/cy16/render.rb +41 -0
- data/metasm/cpu/dalvik.rb +11 -0
- data/{lib/metasm → metasm/cpu}/dalvik/decode.rb +35 -13
- data/{lib/metasm → metasm/cpu}/dalvik/main.rb +51 -2
- data/{lib/metasm → metasm/cpu}/dalvik/opcodes.rb +19 -11
- data/metasm/cpu/ia32.rb +17 -0
- data/{lib/metasm → metasm/cpu}/ia32/compile_c.rb +5 -7
- data/{lib/metasm → metasm/cpu}/ia32/debug.rb +5 -5
- data/{lib/metasm → metasm/cpu}/ia32/decode.rb +246 -59
- data/{lib/metasm → metasm/cpu}/ia32/decompile.rb +7 -7
- data/{lib/metasm → metasm/cpu}/ia32/encode.rb +19 -13
- data/{lib/metasm → metasm/cpu}/ia32/main.rb +51 -8
- data/metasm/cpu/ia32/opcodes.rb +1424 -0
- data/{lib/metasm → metasm/cpu}/ia32/parse.rb +47 -16
- data/{lib/metasm → metasm/cpu}/ia32/render.rb +31 -4
- data/metasm/cpu/mips.rb +14 -0
- data/{lib/metasm → metasm/cpu}/mips/compile_c.rb +1 -1
- data/metasm/cpu/mips/debug.rb +42 -0
- data/{lib/metasm → metasm/cpu}/mips/decode.rb +46 -16
- data/{lib/metasm → metasm/cpu}/mips/encode.rb +4 -3
- data/{lib/metasm → metasm/cpu}/mips/main.rb +11 -4
- data/{lib/metasm → metasm/cpu}/mips/opcodes.rb +86 -17
- data/{lib/metasm → metasm/cpu}/mips/parse.rb +1 -1
- data/{lib/metasm → metasm/cpu}/mips/render.rb +1 -1
- data/{lib/metasm/dalvik.rb → metasm/cpu/msp430.rb} +1 -1
- data/metasm/cpu/msp430/decode.rb +247 -0
- data/metasm/cpu/msp430/main.rb +62 -0
- data/metasm/cpu/msp430/opcodes.rb +101 -0
- data/{lib/metasm → metasm/cpu}/pic16c/decode.rb +6 -7
- data/{lib/metasm → metasm/cpu}/pic16c/main.rb +0 -0
- data/{lib/metasm → metasm/cpu}/pic16c/opcodes.rb +1 -1
- data/{lib/metasm/mips.rb → metasm/cpu/ppc.rb} +4 -4
- data/{lib/metasm → metasm/cpu}/ppc/decode.rb +18 -12
- data/{lib/metasm → metasm/cpu}/ppc/decompile.rb +3 -3
- data/{lib/metasm → metasm/cpu}/ppc/encode.rb +2 -2
- data/{lib/metasm → metasm/cpu}/ppc/main.rb +17 -12
- data/{lib/metasm → metasm/cpu}/ppc/opcodes.rb +11 -5
- data/metasm/cpu/ppc/parse.rb +55 -0
- data/metasm/cpu/python.rb +8 -0
- data/metasm/cpu/python/decode.rb +136 -0
- data/metasm/cpu/python/main.rb +36 -0
- data/metasm/cpu/python/opcodes.rb +180 -0
- data/{lib/metasm → metasm/cpu}/sh4.rb +1 -1
- data/{lib/metasm → metasm/cpu}/sh4/decode.rb +48 -17
- data/{lib/metasm → metasm/cpu}/sh4/main.rb +13 -4
- data/{lib/metasm → metasm/cpu}/sh4/opcodes.rb +7 -8
- data/metasm/cpu/x86_64.rb +15 -0
- data/{lib/metasm → metasm/cpu}/x86_64/compile_c.rb +28 -17
- data/{lib/metasm → metasm/cpu}/x86_64/debug.rb +4 -4
- data/{lib/metasm → metasm/cpu}/x86_64/decode.rb +57 -15
- data/{lib/metasm → metasm/cpu}/x86_64/encode.rb +55 -26
- data/{lib/metasm → metasm/cpu}/x86_64/main.rb +14 -6
- data/metasm/cpu/x86_64/opcodes.rb +136 -0
- data/{lib/metasm → metasm/cpu}/x86_64/parse.rb +10 -2
- data/metasm/cpu/x86_64/render.rb +35 -0
- data/metasm/cpu/z80.rb +9 -0
- data/metasm/cpu/z80/decode.rb +313 -0
- data/metasm/cpu/z80/main.rb +67 -0
- data/metasm/cpu/z80/opcodes.rb +224 -0
- data/metasm/cpu/z80/render.rb +59 -0
- data/{lib/metasm/os/main.rb → metasm/debug.rb} +160 -401
- data/{lib/metasm → metasm}/decode.rb +35 -4
- data/{lib/metasm → metasm}/decompile.rb +15 -16
- data/{lib/metasm → metasm}/disassemble.rb +201 -45
- data/{lib/metasm → metasm}/disassemble_api.rb +651 -87
- data/{lib/metasm → metasm}/dynldr.rb +220 -133
- data/{lib/metasm → metasm}/encode.rb +10 -1
- data/{lib/metasm → metasm}/exe_format/a_out.rb +9 -6
- data/{lib/metasm → metasm}/exe_format/autoexe.rb +1 -0
- data/{lib/metasm → metasm}/exe_format/bflt.rb +57 -27
- data/{lib/metasm → metasm}/exe_format/coff.rb +11 -3
- data/{lib/metasm → metasm}/exe_format/coff_decode.rb +53 -20
- data/{lib/metasm → metasm}/exe_format/coff_encode.rb +11 -13
- data/{lib/metasm → metasm}/exe_format/dex.rb +13 -5
- data/{lib/metasm → metasm}/exe_format/dol.rb +1 -0
- data/{lib/metasm → metasm}/exe_format/elf.rb +93 -57
- data/{lib/metasm → metasm}/exe_format/elf_decode.rb +143 -34
- data/{lib/metasm → metasm}/exe_format/elf_encode.rb +122 -31
- data/metasm/exe_format/gb.rb +65 -0
- data/metasm/exe_format/javaclass.rb +424 -0
- data/{lib/metasm → metasm}/exe_format/macho.rb +204 -16
- data/{lib/metasm → metasm}/exe_format/main.rb +26 -3
- data/{lib/metasm → metasm}/exe_format/mz.rb +1 -0
- data/{lib/metasm → metasm}/exe_format/nds.rb +7 -4
- data/{lib/metasm → metasm}/exe_format/pe.rb +71 -8
- data/metasm/exe_format/pyc.rb +167 -0
- data/{lib/metasm → metasm}/exe_format/serialstruct.rb +67 -14
- data/{lib/metasm → metasm}/exe_format/shellcode.rb +7 -3
- data/metasm/exe_format/shellcode_rwx.rb +114 -0
- data/metasm/exe_format/swf.rb +205 -0
- data/{lib/metasm → metasm}/exe_format/xcoff.rb +7 -7
- data/metasm/exe_format/zip.rb +335 -0
- data/metasm/gui.rb +13 -0
- data/{lib/metasm → metasm}/gui/cstruct.rb +35 -41
- data/{lib/metasm → metasm}/gui/dasm_coverage.rb +11 -11
- data/{lib/metasm → metasm}/gui/dasm_decomp.rb +7 -20
- data/{lib/metasm → metasm}/gui/dasm_funcgraph.rb +0 -0
- data/metasm/gui/dasm_graph.rb +1695 -0
- data/{lib/metasm → metasm}/gui/dasm_hex.rb +12 -8
- data/{lib/metasm → metasm}/gui/dasm_listing.rb +43 -28
- data/{lib/metasm → metasm}/gui/dasm_main.rb +310 -53
- data/{lib/metasm → metasm}/gui/dasm_opcodes.rb +5 -19
- data/{lib/metasm → metasm}/gui/debug.rb +93 -27
- data/{lib/metasm → metasm}/gui/gtk.rb +162 -40
- data/{lib/metasm → metasm}/gui/qt.rb +12 -2
- data/{lib/metasm → metasm}/gui/win32.rb +179 -42
- data/{lib/metasm → metasm}/gui/x11.rb +59 -59
- data/{lib/metasm → metasm}/main.rb +389 -264
- data/{lib/metasm/os/remote.rb → metasm/os/gdbremote.rb} +146 -54
- data/{lib/metasm → metasm}/os/gnu_exports.rb +1 -1
- data/{lib/metasm → metasm}/os/linux.rb +628 -151
- data/metasm/os/main.rb +330 -0
- data/{lib/metasm → metasm}/os/windows.rb +132 -42
- data/{lib/metasm → metasm}/os/windows_exports.rb +141 -0
- data/{lib/metasm → metasm}/parse.rb +26 -24
- data/{lib/metasm → metasm}/parse_c.rb +221 -116
- data/{lib/metasm → metasm}/preprocessor.rb +55 -40
- data/{lib/metasm → metasm}/render.rb +14 -38
- data/misc/hexdump.rb +2 -1
- data/misc/lint.rb +58 -0
- data/misc/txt2html.rb +9 -7
- data/samples/bindiff.rb +3 -4
- data/samples/dasm-plugins/bindiff.rb +15 -0
- data/samples/dasm-plugins/bookmark.rb +133 -0
- data/samples/dasm-plugins/c_constants.rb +57 -0
- data/samples/dasm-plugins/colortheme_solarized.rb +125 -0
- data/samples/dasm-plugins/cppobj_funcall.rb +60 -0
- data/samples/dasm-plugins/dasm_all.rb +70 -0
- data/samples/dasm-plugins/demangle_cpp.rb +31 -0
- data/samples/dasm-plugins/deobfuscate.rb +251 -0
- data/samples/dasm-plugins/dump_text.rb +35 -0
- data/samples/dasm-plugins/export_graph_svg.rb +86 -0
- data/samples/dasm-plugins/findgadget.rb +75 -0
- data/samples/dasm-plugins/hl_opcode.rb +32 -0
- data/samples/dasm-plugins/hotfix_gtk_dbg.rb +19 -0
- data/samples/dasm-plugins/imm2off.rb +34 -0
- data/samples/dasm-plugins/match_libsigs.rb +93 -0
- data/samples/dasm-plugins/patch_file.rb +95 -0
- data/samples/dasm-plugins/scanfuncstart.rb +36 -0
- data/samples/dasm-plugins/scanxrefs.rb +26 -0
- data/samples/dasm-plugins/selfmodify.rb +197 -0
- data/samples/dasm-plugins/stringsxrefs.rb +28 -0
- data/samples/dasmnavig.rb +1 -1
- data/samples/dbg-apihook.rb +24 -9
- data/samples/dbg-plugins/heapscan.rb +283 -0
- data/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c +155 -0
- data/samples/dbg-plugins/heapscan/compiled_heapscan_win.c +128 -0
- data/samples/dbg-plugins/heapscan/graphheap.rb +616 -0
- data/samples/dbg-plugins/heapscan/heapscan.rb +709 -0
- data/samples/dbg-plugins/heapscan/winheap.h +174 -0
- data/samples/dbg-plugins/heapscan/winheap7.h +307 -0
- data/samples/dbg-plugins/trace_func.rb +214 -0
- data/samples/disassemble-gui.rb +35 -5
- data/samples/disassemble.rb +31 -6
- data/samples/dump_upx.rb +24 -12
- data/samples/dynamic_ruby.rb +12 -3
- data/samples/exeencode.rb +6 -5
- data/samples/factorize-headers-peimports.rb +1 -1
- data/samples/lindebug.rb +175 -381
- data/samples/metasm-shell.rb +1 -2
- data/samples/peldr.rb +2 -2
- data/tests/all.rb +1 -1
- data/tests/arc.rb +26 -0
- data/tests/dynldr.rb +22 -4
- data/tests/expression.rb +55 -0
- data/tests/graph_layout.rb +285 -0
- data/tests/ia32.rb +79 -26
- data/tests/mips.rb +9 -2
- data/tests/x86_64.rb +66 -18
- metadata +330 -218
- data/lib/metasm/arm/opcodes.rb +0 -177
- data/lib/metasm/gui.rb +0 -23
- data/lib/metasm/gui/dasm_graph.rb +0 -1354
- data/lib/metasm/ia32.rb +0 -14
- data/lib/metasm/ia32/opcodes.rb +0 -873
- data/lib/metasm/ppc/parse.rb +0 -52
- data/lib/metasm/x86_64.rb +0 -12
- data/lib/metasm/x86_64/opcodes.rb +0 -118
- data/samples/gdbclient.rb +0 -583
- data/samples/rubstop.rb +0 -399
data/metasm/gui.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
backend = ENV['METASM_GUI'] || (
|
|
2
|
+
if RUBY_PLATFORM =~ /(i.86|x(86_)?64)-(mswin|mingw|cygwin)/i
|
|
3
|
+
'win32'
|
|
4
|
+
else
|
|
5
|
+
begin
|
|
6
|
+
require 'gtk2'
|
|
7
|
+
'gtk'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
raise LoadError, 'No GUI ruby binding installed - please install libgtk2-ruby'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
)
|
|
13
|
+
require "metasm/gui/#{backend}"
|
|
@@ -23,8 +23,7 @@ class CStructWidget < DrawableWidget
|
|
|
23
23
|
@cwidth = @cheight = 1 # widget size in chars
|
|
24
24
|
@structdepth = 2
|
|
25
25
|
|
|
26
|
-
@default_color_association =
|
|
27
|
-
:background => :white, :hl_word => :palered, :comment => :darkblue }
|
|
26
|
+
@default_color_association = ColorTheme.merge :keyword => :blue
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def click(x, y)
|
|
@@ -90,19 +89,7 @@ class CStructWidget < DrawableWidget
|
|
|
90
89
|
elsif cx < @view_x
|
|
91
90
|
else
|
|
92
91
|
t = t[(@view_x - cx + t.length)..-1] if cx-t.length < @view_x
|
|
93
|
-
|
|
94
|
-
stmp = t
|
|
95
|
-
pre_x = 0
|
|
96
|
-
while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/
|
|
97
|
-
s1, s2 = $1, $2
|
|
98
|
-
pre_x += s1.length*@font_width
|
|
99
|
-
hl_w = s2.length*@font_width
|
|
100
|
-
draw_rectangle_color(:hl_word, x+pre_x, y, hl_w, @font_height)
|
|
101
|
-
pre_x += hl_w
|
|
102
|
-
stmp = stmp[s1.length+s2.length..-1]
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
draw_string_color(c, x, y, t)
|
|
92
|
+
draw_string_hl(c, x, y, t)
|
|
106
93
|
x += t.length * @font_width
|
|
107
94
|
end
|
|
108
95
|
}
|
|
@@ -116,7 +103,7 @@ class CStructWidget < DrawableWidget
|
|
|
116
103
|
cy = (@caret_y-@view_y)*@font_height
|
|
117
104
|
draw_line_color(:caret, cx, cy, cx, cy+@font_height-1)
|
|
118
105
|
end
|
|
119
|
-
|
|
106
|
+
|
|
120
107
|
@oldcaret_x, @oldcaret_y = @caret_x, @caret_y
|
|
121
108
|
end
|
|
122
109
|
|
|
@@ -179,28 +166,31 @@ class CStructWidget < DrawableWidget
|
|
|
179
166
|
when ?l
|
|
180
167
|
liststructs
|
|
181
168
|
when ?t
|
|
182
|
-
inputbox('new struct name to use', :text => (@curstruct.name rescue '')) { |n|
|
|
183
|
-
lst = @dasm.c_parser.toplevel.struct.keys.grep(String)
|
|
184
|
-
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
|
185
|
-
focus_addr(@curaddr, @dasm.c_parser.toplevel.struct[fn])
|
|
186
|
-
else
|
|
187
|
-
lst = @dasm.c_parser.toplevel.symbol.keys.grep(String).find_all { |ln|
|
|
188
|
-
s = @dasm.c_parser.toplevel.symbol[ln]
|
|
189
|
-
s.kind_of?(C::TypeDef) and s.untypedef.kind_of?(C::Union)
|
|
190
|
-
}
|
|
191
|
-
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
|
192
|
-
focus_addr(@curaddr, @dasm.c_parser.toplevel.symbol[fn].untypedef)
|
|
193
|
-
else
|
|
194
|
-
liststructs(n)
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
}
|
|
169
|
+
inputbox('new struct name to use', :text => (@curstruct.name rescue '')) { |n| focus_struct_byname(n) }
|
|
198
170
|
else return false
|
|
199
171
|
end
|
|
200
172
|
true
|
|
201
173
|
end
|
|
202
174
|
|
|
203
|
-
|
|
175
|
+
# display the struct or pop a list of matching struct names if ambiguous
|
|
176
|
+
def focus_struct_byname(n, addr=@curaddr)
|
|
177
|
+
lst = @dasm.c_parser.toplevel.struct.keys.grep(String)
|
|
178
|
+
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
|
179
|
+
focus_addr(addr, @dasm.c_parser.toplevel.struct[fn])
|
|
180
|
+
else
|
|
181
|
+
lst = @dasm.c_parser.toplevel.symbol.keys.grep(String).find_all { |ln|
|
|
182
|
+
s = @dasm.c_parser.toplevel.symbol[ln]
|
|
183
|
+
s.kind_of?(C::TypeDef) and s.untypedef.kind_of?(C::Union)
|
|
184
|
+
}
|
|
185
|
+
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
|
186
|
+
focus_addr(addr, @dasm.c_parser.toplevel.symbol[fn].untypedef)
|
|
187
|
+
else
|
|
188
|
+
liststructs(n, addr)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def liststructs(partname=nil, addr=@curaddr)
|
|
204
194
|
tl = @dasm.c_parser.toplevel
|
|
205
195
|
list = [['name', 'size']]
|
|
206
196
|
list += tl.struct.keys.grep(String).sort.map { |stn|
|
|
@@ -216,12 +206,12 @@ class CStructWidget < DrawableWidget
|
|
|
216
206
|
}.compact
|
|
217
207
|
|
|
218
208
|
if partname and list.length == 2
|
|
219
|
-
focus_addr(
|
|
209
|
+
focus_addr(addr, tl.struct[list[1][0]] || tl.symbol[list[1][0]].untypedef)
|
|
220
210
|
return
|
|
221
211
|
end
|
|
222
212
|
|
|
223
213
|
listwindow('structs', list) { |stn|
|
|
224
|
-
focus_addr(
|
|
214
|
+
focus_addr(addr, tl.struct[stn[0]] || tl.symbol[stn[0]].untypedef)
|
|
225
215
|
}
|
|
226
216
|
end
|
|
227
217
|
|
|
@@ -240,7 +230,7 @@ class CStructWidget < DrawableWidget
|
|
|
240
230
|
def update_caret
|
|
241
231
|
if @caret_x < @view_x or @caret_x >= @view_x + @cwidth or @caret_y < @view_y or @caret_y >= @view_y + @cheight
|
|
242
232
|
redraw
|
|
243
|
-
elsif update_hl_word(@line_text[@caret_y], @caret_x)
|
|
233
|
+
elsif update_hl_word(@line_text[@caret_y], @caret_x, :c)
|
|
244
234
|
redraw
|
|
245
235
|
else
|
|
246
236
|
invalidate_caret(@oldcaret_x-@view_x, @oldcaret_y-@view_y)
|
|
@@ -254,9 +244,14 @@ class CStructWidget < DrawableWidget
|
|
|
254
244
|
def focus_addr(addr, struct=@curstruct)
|
|
255
245
|
return if @parent_widget and not addr = @parent_widget.normalize(addr)
|
|
256
246
|
@curaddr = addr
|
|
257
|
-
@curstruct = struct
|
|
258
247
|
@caret_x = @caret_y = 0
|
|
259
|
-
|
|
248
|
+
if struct.kind_of? String
|
|
249
|
+
@curstruct = nil
|
|
250
|
+
focus_struct_byname(struct)
|
|
251
|
+
else
|
|
252
|
+
@curstruct = struct
|
|
253
|
+
gui_update
|
|
254
|
+
end
|
|
260
255
|
true
|
|
261
256
|
end
|
|
262
257
|
|
|
@@ -278,7 +273,7 @@ class CStructWidget < DrawableWidget
|
|
|
278
273
|
@line_text_col << []
|
|
279
274
|
render[indent * [@structdepth - maxdepth, 0].max, :text]
|
|
280
275
|
}
|
|
281
|
-
|
|
276
|
+
|
|
282
277
|
if not obj
|
|
283
278
|
@line_text_col = [[]]
|
|
284
279
|
@line_dereference = []
|
|
@@ -308,7 +303,6 @@ class CStructWidget < DrawableWidget
|
|
|
308
303
|
elsif struct.kind_of?(C::Struct)
|
|
309
304
|
render["struct #{struct.name || '_'} st_#{Expression[@curaddr]} = ", :text] if not off
|
|
310
305
|
fldoff = struct.fldoffset
|
|
311
|
-
fbo = struct.fldbitoffset || {}
|
|
312
306
|
else
|
|
313
307
|
render["union #{struct.name || '_'} un_#{Expression[@curaddr]} = ", :text] if not off
|
|
314
308
|
end
|
|
@@ -363,7 +357,7 @@ class CStructWidget < DrawableWidget
|
|
|
363
357
|
else
|
|
364
358
|
@line_text_col = [[[:text, '/* no struct selected (list with "l") */']]]
|
|
365
359
|
end
|
|
366
|
-
|
|
360
|
+
|
|
367
361
|
@line_text = @line_text_col.map { |l| l.map { |c, s| s }.join }
|
|
368
362
|
update_caret
|
|
369
363
|
redraw
|
|
@@ -19,15 +19,15 @@ class CoverageWidget < DrawableWidget
|
|
|
19
19
|
@section_x = []
|
|
20
20
|
@slave = nil # another dasmwidget whose curaddr is kept sync
|
|
21
21
|
|
|
22
|
-
@default_color_association =
|
|
23
|
-
:background => :palegrey, :code => :red, :data => :blue
|
|
22
|
+
@default_color_association = ColorTheme.merge :caret => :yellow, :caret_col => :darkyellow,
|
|
23
|
+
:background => :palegrey, :code => :red, :data => :blue
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def click(x, y)
|
|
27
27
|
x, y = x.to_i - 1, y.to_i
|
|
28
|
-
@sections.zip(@section_x).each { |
|
|
29
|
-
if x >= sx and x <
|
|
30
|
-
@curaddr =
|
|
28
|
+
@sections.zip(@section_x).each { |s, sx|
|
|
29
|
+
if x >= sx[0] and x < sx[1]+@pixel_w
|
|
30
|
+
@curaddr = s[0] + (x-sx[0])/@pixel_w*@byte_per_col + (y/@pixel_h-@spacing)*@byte_per_col/@col_height
|
|
31
31
|
@slave.focus_addr(@curaddr) if @slave rescue @slave=nil
|
|
32
32
|
redraw
|
|
33
33
|
break
|
|
@@ -125,13 +125,13 @@ class CoverageWidget < DrawableWidget
|
|
|
125
125
|
x += @spacing*@pixel_w
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
@sections.zip(@section_x).each { |
|
|
129
|
-
next if @curaddr.kind_of? Integer and not
|
|
130
|
-
next if @curaddr.kind_of? Expression and not
|
|
131
|
-
co = @curaddr-
|
|
132
|
-
if co >= 0 and co <
|
|
128
|
+
@sections.zip(@section_x).each { |s, sx|
|
|
129
|
+
next if @curaddr.kind_of? Integer and not s[0].kind_of? Integer
|
|
130
|
+
next if @curaddr.kind_of? Expression and not s[0].kind_of? Expression
|
|
131
|
+
co = @curaddr-s[0]
|
|
132
|
+
if co >= 0 and co < s[1]
|
|
133
133
|
draw_color :caret_col
|
|
134
|
-
x = sx + (co/@byte_per_col)*@pixel_w
|
|
134
|
+
x = sx[0] + (co/@byte_per_col)*@pixel_w
|
|
135
135
|
draw_rect[-@spacing, -1, 1]
|
|
136
136
|
draw_rect[@col_height, @col_height+@spacing, 1]
|
|
137
137
|
draw_color :caret
|
|
@@ -19,9 +19,8 @@ class CdecompListingWidget < DrawableWidget
|
|
|
19
19
|
@curaddr = nil
|
|
20
20
|
@tabwidth = 8
|
|
21
21
|
|
|
22
|
-
@default_color_association =
|
|
23
|
-
|
|
24
|
-
:globalvar => :darkgreen, :intrinsic => :darkyellow }
|
|
22
|
+
@default_color_association = ColorTheme.merge :keyword => :blue, :localvar => :darkred,
|
|
23
|
+
:globalvar => :darkgreen, :intrinsic => :darkyellow
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
def curfunc
|
|
@@ -91,19 +90,7 @@ class CdecompListingWidget < DrawableWidget
|
|
|
91
90
|
# must not include newline
|
|
92
91
|
render = lambda { |str, color|
|
|
93
92
|
# function ends when we write under the bottom of the listing
|
|
94
|
-
|
|
95
|
-
stmp = str
|
|
96
|
-
pre_x = 0
|
|
97
|
-
while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/
|
|
98
|
-
s1, s2 = $1, $2
|
|
99
|
-
pre_x += s1.length*@font_width
|
|
100
|
-
hl_w = s2.length*@font_width
|
|
101
|
-
draw_rectangle_color(:hl_word, x+pre_x, y, hl_w, @font_height)
|
|
102
|
-
pre_x += hl_w
|
|
103
|
-
stmp = stmp[s1.length+s2.length..-1]
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
draw_string_color(color, x, y, str)
|
|
93
|
+
draw_string_hl(color, x, y, str)
|
|
107
94
|
x += str.length * @font_width
|
|
108
95
|
}
|
|
109
96
|
|
|
@@ -128,7 +115,7 @@ class CdecompListingWidget < DrawableWidget
|
|
|
128
115
|
cy = (@caret_y-@view_y)*@font_height
|
|
129
116
|
draw_line_color(:caret, cx, cy, cx, cy+@font_height-1)
|
|
130
117
|
end
|
|
131
|
-
|
|
118
|
+
|
|
132
119
|
@oldcaret_x, @oldcaret_y = @caret_x, @caret_y
|
|
133
120
|
end
|
|
134
121
|
|
|
@@ -184,7 +171,7 @@ class CdecompListingWidget < DrawableWidget
|
|
|
184
171
|
f.decompdata[:stackoff_name][s.stackoff] = v if s.stackoff
|
|
185
172
|
elsif @dasm.c_parser.toplevel.symbol[n]
|
|
186
173
|
@dasm.rename_label(n, v)
|
|
187
|
-
@curaddr = v if @curaddr == n
|
|
174
|
+
@curaddr = v if @curaddr == n
|
|
188
175
|
end
|
|
189
176
|
gui_update
|
|
190
177
|
}
|
|
@@ -264,13 +251,13 @@ class CdecompListingWidget < DrawableWidget
|
|
|
264
251
|
invalidate_caret(@caret_x-@view_x, @caret_y-@view_y)
|
|
265
252
|
@oldcaret_x, @oldcaret_y = @caret_x, @caret_y
|
|
266
253
|
|
|
267
|
-
redraw if update_hl_word(@line_text[@caret_y], @caret_x)
|
|
254
|
+
redraw if update_hl_word(@line_text[@caret_y], @caret_x, :c)
|
|
268
255
|
end
|
|
269
256
|
|
|
270
257
|
# focus on addr
|
|
271
258
|
# returns true on success (address exists & decompiled)
|
|
272
259
|
def focus_addr(addr)
|
|
273
|
-
if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr])
|
|
260
|
+
if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr].kind_of?(C::Union))
|
|
274
261
|
@curaddr = addr
|
|
275
262
|
@caret_x = @caret_y = 0
|
|
276
263
|
gui_update
|
|
File without changes
|
|
@@ -0,0 +1,1695 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
module Metasm
|
|
7
|
+
module Gui
|
|
8
|
+
class Graph
|
|
9
|
+
# one box, has a text, an id, and a list of other boxes to/from
|
|
10
|
+
class Box
|
|
11
|
+
attr_accessor :id, :x, :y, :w, :h
|
|
12
|
+
attr_accessor :to, :from # other boxes linked (arrays)
|
|
13
|
+
attr_accessor :content
|
|
14
|
+
attr_accessor :direct_to
|
|
15
|
+
def initialize(id, content=nil)
|
|
16
|
+
@id = id
|
|
17
|
+
@x = @y = @w = @h = 0
|
|
18
|
+
@to, @from = [], []
|
|
19
|
+
@content = content
|
|
20
|
+
end
|
|
21
|
+
def [](a) @content[a] end
|
|
22
|
+
#def inspect ; puts caller ; "#{Expression[@id] rescue @id.inspect}" end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_accessor :id, :box, :box_id, :root_addrs, :view_x, :view_y, :keep_split
|
|
26
|
+
def initialize(id)
|
|
27
|
+
@id = id
|
|
28
|
+
@root_addrs = []
|
|
29
|
+
@view_x = @view_y = -0xfff_ffff
|
|
30
|
+
clear
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# empty @box
|
|
34
|
+
def clear
|
|
35
|
+
@box = []
|
|
36
|
+
@box_id = {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# link the two boxes (by id)
|
|
40
|
+
def link_boxes(id1, id2)
|
|
41
|
+
raise "unknown index 1 #{id1}" if not b1 = @box_id[id1]
|
|
42
|
+
raise "unknown index 2 #{id2}" if not b2 = @box_id[id2]
|
|
43
|
+
b1.to |= [b2]
|
|
44
|
+
b2.from |= [b1]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# creates a new box, ensures id is not already taken
|
|
48
|
+
def new_box(id, content=nil)
|
|
49
|
+
raise "duplicate id #{id}" if @box_id[id]
|
|
50
|
+
b = Box.new(id, content)
|
|
51
|
+
@box << b
|
|
52
|
+
@box_id[id] = b
|
|
53
|
+
b
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# returns the [x1, y1, x2, y2] of the rectangle encompassing all boxes
|
|
57
|
+
def boundingbox
|
|
58
|
+
minx = @box.map { |b| b.x }.min.to_i
|
|
59
|
+
miny = @box.map { |b| b.y }.min.to_i
|
|
60
|
+
maxx = @box.map { |b| b.x + b.w }.max.to_i
|
|
61
|
+
maxy = @box.map { |b| b.y + b.h }.max.to_i
|
|
62
|
+
[minx, miny, maxx, maxy]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# a -> b -> c -> d (no other in/outs)
|
|
66
|
+
def pattern_layout_col(groups)
|
|
67
|
+
# find head
|
|
68
|
+
return if not head = groups.find { |g|
|
|
69
|
+
g.to.length == 1 and
|
|
70
|
+
g.to[0].from.length == 1 and
|
|
71
|
+
(g.from.length != 1 or g.from[0].to.length != 1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# find full sequence
|
|
75
|
+
ar = [head]
|
|
76
|
+
while head.to.length == 1 and head.to[0].from.length == 1
|
|
77
|
+
head = head.to[0]
|
|
78
|
+
ar << head
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# move boxes inside this group
|
|
82
|
+
maxw = ar.map { |g| g.w }.max
|
|
83
|
+
fullh = ar.inject(0) { |h, g| h + g.h }
|
|
84
|
+
cury = -fullh/2
|
|
85
|
+
ar.each { |g|
|
|
86
|
+
dy = cury - g.y
|
|
87
|
+
g.content.each { |b| b.y += dy }
|
|
88
|
+
cury += g.h
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# create remplacement group
|
|
92
|
+
newg = Box.new(nil, ar.map { |g| g.content }.flatten)
|
|
93
|
+
newg.w = maxw
|
|
94
|
+
newg.h = fullh
|
|
95
|
+
newg.x = -newg.w/2
|
|
96
|
+
newg.y = -newg.h/2
|
|
97
|
+
newg.from = ar.first.from - ar
|
|
98
|
+
newg.to = ar.last.to - ar
|
|
99
|
+
# fix xrefs
|
|
100
|
+
newg.from.each { |g| g.to -= ar ; g.to << newg }
|
|
101
|
+
newg.to.each { |g| g.from -= ar ; g.from << newg }
|
|
102
|
+
# fix groups
|
|
103
|
+
groups[groups.index(head)] = newg
|
|
104
|
+
ar.each { |g| groups.delete g }
|
|
105
|
+
|
|
106
|
+
true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# if a group has no content close to its x/x+w borders, shrink it
|
|
110
|
+
def group_remove_hz_margin(g, maxw=16)
|
|
111
|
+
if g.content.empty?
|
|
112
|
+
g.x = -maxw/2 if g.x < -maxw/2
|
|
113
|
+
g.w = maxw if g.w > maxw
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
margin_left = g.content.map { |b| b.x }.min - g.x
|
|
118
|
+
margin_right = g.x+g.w - g.content.map { |b| b.x+b.w }.max
|
|
119
|
+
if margin_left + margin_right > maxw
|
|
120
|
+
g.w -= margin_left + margin_right - maxw
|
|
121
|
+
dx = (maxw/2 + margin_right - margin_left)/2
|
|
122
|
+
g.content.each { |b| b.x += dx }
|
|
123
|
+
g.x = -g.w/2
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# a -> [b, c, d] -> e
|
|
128
|
+
def pattern_layout_line(groups)
|
|
129
|
+
# find head
|
|
130
|
+
ar = []
|
|
131
|
+
groups.each { |g|
|
|
132
|
+
if g.from.length == 1 and g.to.length <= 1 and g.from.first.to.length > 1
|
|
133
|
+
ar = g.from.first.to.find_all { |gg| gg.from == g.from and gg.to == g.to }
|
|
134
|
+
elsif g.from.empty? and g.to.length == 1 and g.to.first.from.length > 1
|
|
135
|
+
ar = g.to.first.from.find_all { |gg| gg.from == g.from and gg.to == g.to }
|
|
136
|
+
else ar = []
|
|
137
|
+
end
|
|
138
|
+
break if ar.length > 1
|
|
139
|
+
}
|
|
140
|
+
return if ar.length <= 1
|
|
141
|
+
|
|
142
|
+
ar.each { |g| group_remove_hz_margin(g) }
|
|
143
|
+
|
|
144
|
+
# move boxes inside this group
|
|
145
|
+
#ar = ar.sort_by { |g| -g.h }
|
|
146
|
+
maxh = ar.map { |g| g.h }.max
|
|
147
|
+
fullw = ar.inject(0) { |w, g| w + g.w }
|
|
148
|
+
curx = -fullw/2
|
|
149
|
+
ar.each { |g|
|
|
150
|
+
# if no to, put all boxes at bottom ; if no from, put them at top
|
|
151
|
+
case [g.from.length, g.to.length]
|
|
152
|
+
when [1, 0]; dy = (g.h - maxh)/2
|
|
153
|
+
when [0, 1]; dy = (maxh - g.h)/2
|
|
154
|
+
else dy = 0
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
dx = curx - g.x
|
|
158
|
+
g.content.each { |b| b.x += dx ; b.y += dy }
|
|
159
|
+
curx += g.w
|
|
160
|
+
}
|
|
161
|
+
# add a 'margin-top' proportionnal to the ar width
|
|
162
|
+
# this gap should be relative to the real boxes and not possible previous gaps when
|
|
163
|
+
# merging lines (eg long line + many if patterns -> dont duplicate gaps)
|
|
164
|
+
boxen = ar.map { |g| g.content }.flatten
|
|
165
|
+
realh = boxen.map { |g| g.y + g.h }.max - boxen.map { |g| g.y }.min
|
|
166
|
+
if maxh < realh + fullw/4
|
|
167
|
+
maxh = realh + fullw/4
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# create remplacement group
|
|
171
|
+
newg = Box.new(nil, ar.map { |g| g.content }.flatten)
|
|
172
|
+
newg.w = fullw
|
|
173
|
+
newg.h = maxh
|
|
174
|
+
newg.x = -newg.w/2
|
|
175
|
+
newg.y = -newg.h/2
|
|
176
|
+
newg.from = ar.first.from
|
|
177
|
+
newg.to = ar.first.to
|
|
178
|
+
# fix xrefs
|
|
179
|
+
newg.from.each { |g| g.to -= ar ; g.to << newg }
|
|
180
|
+
newg.to.each { |g| g.from -= ar ; g.from << newg }
|
|
181
|
+
# fix groups
|
|
182
|
+
groups[groups.index(ar.first)] = newg
|
|
183
|
+
ar.each { |g| groups.delete g }
|
|
184
|
+
|
|
185
|
+
true
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# a -> b -> c & a -> c
|
|
189
|
+
def pattern_layout_ifend(groups)
|
|
190
|
+
# find head
|
|
191
|
+
return if not head = groups.find { |g|
|
|
192
|
+
g.to.length == 2 and
|
|
193
|
+
((g.to[0].from.length == 1 and g.to[0].to.length == 1 and g.to[0].to[0] == g.to[1]) or
|
|
194
|
+
(g.to[1].from.length == 1 and g.to[1].to.length == 1 and g.to[1].to[0] == g.to[0]))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if head.to[0].to.include?(head.to[1])
|
|
198
|
+
ten = head.to[0]
|
|
199
|
+
else
|
|
200
|
+
ten = head.to[1]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# stuff 'then' inside the 'if'
|
|
204
|
+
# move 'if' up, 'then' down
|
|
205
|
+
head.content.each { |g| g.y -= ten.h/2 }
|
|
206
|
+
ten.content.each { |g| g.y += head.h/2 }
|
|
207
|
+
head.h += ten.h
|
|
208
|
+
head.y -= ten.h/2
|
|
209
|
+
|
|
210
|
+
# widen 'if'
|
|
211
|
+
# this adds a phantom left side
|
|
212
|
+
# drop existing margins first
|
|
213
|
+
group_remove_hz_margin(ten)
|
|
214
|
+
dw = ten.w - head.w/2
|
|
215
|
+
if dw > 0
|
|
216
|
+
# need to widen head to fit ten
|
|
217
|
+
head.w += 2*dw
|
|
218
|
+
head.x -= dw
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# merge
|
|
222
|
+
ten.content.each { |g| g.x += -ten.x }
|
|
223
|
+
head.content.concat ten.content
|
|
224
|
+
|
|
225
|
+
head.to.delete ten
|
|
226
|
+
head.to[0].from.delete ten
|
|
227
|
+
|
|
228
|
+
groups.delete ten
|
|
229
|
+
|
|
230
|
+
true
|
|
231
|
+
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def pattern_layout_complex(groups)
|
|
235
|
+
order = order_graph(groups)
|
|
236
|
+
uniq = nil
|
|
237
|
+
if groups.sort_by { |g| order[g] }.find { |g|
|
|
238
|
+
next if g.to.length <= 1
|
|
239
|
+
# list all nodes reachable for every 'to'
|
|
240
|
+
reach = g.to.map { |t| list_reachable(t) }
|
|
241
|
+
# list all nodes reachable only from a single 'to'
|
|
242
|
+
uniq = []
|
|
243
|
+
reach.each_with_index { |r, i|
|
|
244
|
+
# take all nodes reachable from there ...
|
|
245
|
+
u = uniq[i] = r.dup
|
|
246
|
+
u.delete_if { |k, v| k.content.empty? } # ignore previous layout_complex artifacts
|
|
247
|
+
reach.each_with_index { |rr, ii|
|
|
248
|
+
next if i == ii
|
|
249
|
+
# ... and delete nodes reachable from anywhere else
|
|
250
|
+
rr.each_key { |k| u.delete k }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
uniq.delete_if { |u| u.length <= 1 }
|
|
254
|
+
!uniq.empty?
|
|
255
|
+
}
|
|
256
|
+
# now layout every uniq subgroup independently
|
|
257
|
+
uniq.each { |u|
|
|
258
|
+
subgroups = groups.find_all { |g| u[g] }
|
|
259
|
+
|
|
260
|
+
# isolate subgroup from external links
|
|
261
|
+
# change all external links into a single empty box
|
|
262
|
+
newtop = Box.new(nil, [])
|
|
263
|
+
newtop.x = -8 ; newtop.y = -9
|
|
264
|
+
newtop.w = 16 ; newtop.h = 18
|
|
265
|
+
newbot = Box.new(nil, [])
|
|
266
|
+
newbot.x = -8 ; newbot.y = -9
|
|
267
|
+
newbot.w = 16 ; newbot.h = 18
|
|
268
|
+
hadfrom = [] ; hadto = []
|
|
269
|
+
subgroups.each { |g|
|
|
270
|
+
g.to.dup.each { |t|
|
|
271
|
+
next if u[t]
|
|
272
|
+
newbot.from |= [g]
|
|
273
|
+
g.to.delete t
|
|
274
|
+
hadto << t
|
|
275
|
+
g.to |= [newbot]
|
|
276
|
+
}
|
|
277
|
+
g.from.dup.each { |f|
|
|
278
|
+
next if u[f]
|
|
279
|
+
newtop.to |= [g]
|
|
280
|
+
g.from.delete f
|
|
281
|
+
hadfrom << f
|
|
282
|
+
g.from |= [newtop]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
subgroups << newtop << newbot
|
|
286
|
+
|
|
287
|
+
# subgroup layout
|
|
288
|
+
auto_arrange_step(subgroups) while subgroups.length > 1
|
|
289
|
+
newg = subgroups[0]
|
|
290
|
+
|
|
291
|
+
# patch 'groups'
|
|
292
|
+
idx = groups.index { |g| u[g] }
|
|
293
|
+
groups.delete_if { |g| u[g] }
|
|
294
|
+
groups[idx, 0] = [newg]
|
|
295
|
+
|
|
296
|
+
# restore external links & fix xrefs
|
|
297
|
+
hadfrom.uniq.each { |f|
|
|
298
|
+
f.to.delete_if { |t| u[t] }
|
|
299
|
+
f.to |= [newg]
|
|
300
|
+
newg.from |= [f]
|
|
301
|
+
}
|
|
302
|
+
hadto.uniq.each { |t|
|
|
303
|
+
t.from.delete_if { |f| u[f] }
|
|
304
|
+
t.from |= [newg]
|
|
305
|
+
newg.to |= [t]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
true
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# find the minimal set of nodes from which we can reach all others
|
|
314
|
+
# this is done *before* removing cycles in the graph
|
|
315
|
+
# returns the order (Hash group => group_order)
|
|
316
|
+
# roots have an order of 0
|
|
317
|
+
def order_graph(groups)
|
|
318
|
+
roots = groups.find_all { |g| g.from.empty? }
|
|
319
|
+
o = {} # tentative order
|
|
320
|
+
todo = []
|
|
321
|
+
|
|
322
|
+
loop do
|
|
323
|
+
roots.each { |g|
|
|
324
|
+
o[g] ||= 0
|
|
325
|
+
todo |= g.to.find_all { |gg| not o[gg] }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# order nodes from the tentative roots
|
|
329
|
+
until todo.empty?
|
|
330
|
+
n = todo.find { |g| g.from.all? { |gg| o[gg] } } || order_solve_cycle(todo, o)
|
|
331
|
+
todo.delete n
|
|
332
|
+
o[n] = n.from.map { |g| o[g] }.compact.max + 1
|
|
333
|
+
todo |= n.to.find_all { |g| not o[g] }
|
|
334
|
+
end
|
|
335
|
+
break if o.length >= groups.length
|
|
336
|
+
|
|
337
|
+
# pathological cases
|
|
338
|
+
|
|
339
|
+
if noroot = groups.find_all { |g| o[g] and g.from.find { |gg| not o[gg] } }.sort_by { |g| o[g] }.first
|
|
340
|
+
# we picked a root in the middle of the graph, walk up
|
|
341
|
+
todo |= noroot.from.find_all { |g| not o[g] }
|
|
342
|
+
until todo.empty?
|
|
343
|
+
n = todo.find { |g| g.to.all? { |gg| o[gg] } } ||
|
|
344
|
+
todo.sort_by { |g| g.to.map { |gg| o[gg] }.compact.min }.first
|
|
345
|
+
todo.delete n
|
|
346
|
+
o[n] = n.to.map { |g| o[g] }.compact.min - 1
|
|
347
|
+
todo |= n.from.find_all { |g| not o[g] }
|
|
348
|
+
end
|
|
349
|
+
# setup todo for next fwd iteration
|
|
350
|
+
todo |= groups.find_all { |g| not o[g] and g.from.find { |gg| o[gg] } }
|
|
351
|
+
else
|
|
352
|
+
# disjoint graph, start over from one other random node
|
|
353
|
+
roots << groups.find { |g| not o[g] }
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
if o.values.find { |rank| rank < 0 }
|
|
358
|
+
# did hit a pathological case, restart with found real roots
|
|
359
|
+
roots = groups.find_all { |g| not g.from.find { |gg| o[gg] < o[g] } }
|
|
360
|
+
o = {}
|
|
361
|
+
todo = []
|
|
362
|
+
roots.each { |g|
|
|
363
|
+
o[g] ||= 0
|
|
364
|
+
todo |= g.to.find_all { |gg| not o[gg] }
|
|
365
|
+
}
|
|
366
|
+
until todo.empty?
|
|
367
|
+
n = todo.find { |g| g.from.all? { |gg| o[gg] } } || order_solve_cycle(todo, o)
|
|
368
|
+
todo.delete n
|
|
369
|
+
o[n] = n.from.map { |g| o[g] }.compact.max + 1
|
|
370
|
+
todo |= n.to.find_all { |g| not o[g] }
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# there's something screwy around here !
|
|
374
|
+
raise "moo" if o.length < groups.length
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
o
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def order_solve_cycle(todo, o)
|
|
381
|
+
# 'todo' has no trivial candidate
|
|
382
|
+
# pick one node from todo which no other todo can reach
|
|
383
|
+
# exclude pathing through already ordered nodes
|
|
384
|
+
todo.find { |t1|
|
|
385
|
+
not todo.find { |t2| t1 != t2 and can_find_path(t2, t1, o.dup) }
|
|
386
|
+
} ||
|
|
387
|
+
# some cycle heads are mutually recursive
|
|
388
|
+
todo.sort_by { |t1|
|
|
389
|
+
# find the one who can reach the most others
|
|
390
|
+
[todo.find_all { |t2| t1 != t2 and can_find_path(t1, t2, o.dup) }.length,
|
|
391
|
+
# and with the highest rank
|
|
392
|
+
t1.from.map { |gg| o[gg] }.compact.max]
|
|
393
|
+
}.last
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# checks if there is a path from src to dst avoiding stuff in 'done'
|
|
397
|
+
def can_find_path(src, dst, done={})
|
|
398
|
+
todo = [src]
|
|
399
|
+
while g = todo.pop
|
|
400
|
+
next if done[g]
|
|
401
|
+
return true if g == dst
|
|
402
|
+
done[g] = true
|
|
403
|
+
todo.concat g.to
|
|
404
|
+
end
|
|
405
|
+
false
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# returns a hash with true for every node reachable from src (included)
|
|
409
|
+
def list_reachable(src, done={})
|
|
410
|
+
todo = [src]
|
|
411
|
+
while g = todo.pop
|
|
412
|
+
next if done[g]
|
|
413
|
+
done[g] = true
|
|
414
|
+
todo.concat g.to
|
|
415
|
+
end
|
|
416
|
+
done
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# revert looping edges in groups
|
|
420
|
+
def make_tree(groups, order)
|
|
421
|
+
# now we have the roots and node orders
|
|
422
|
+
# revert cycling edges - o(chld) < o(parent)
|
|
423
|
+
order.each_key { |g|
|
|
424
|
+
g.to.dup.each { |gg|
|
|
425
|
+
if order[gg] < order[g]
|
|
426
|
+
# cycling edge, revert
|
|
427
|
+
g.to.delete gg
|
|
428
|
+
gg.from.delete g
|
|
429
|
+
g.from |= [gg]
|
|
430
|
+
gg.to |= [g]
|
|
431
|
+
end
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# group groups in layers of same order
|
|
437
|
+
# create dummy groups along long edges so that no path exists between non-contiguous layers
|
|
438
|
+
def create_layers(groups, order)
|
|
439
|
+
newemptybox = lambda {
|
|
440
|
+
b = Box.new(nil, [])
|
|
441
|
+
b.x = -8
|
|
442
|
+
b.y = -9
|
|
443
|
+
b.w = 16
|
|
444
|
+
b.h = 18
|
|
445
|
+
groups << b
|
|
446
|
+
b
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
newboxo = {}
|
|
450
|
+
|
|
451
|
+
order.each_key { |g|
|
|
452
|
+
og = order[g] || newboxo[g]
|
|
453
|
+
g.to.dup.each { |gg|
|
|
454
|
+
ogg = order[gg] || newboxo[gg]
|
|
455
|
+
if ogg > og+1
|
|
456
|
+
# long edge, expand
|
|
457
|
+
sq = [g]
|
|
458
|
+
(ogg - 1 - og).times { |i| sq << newemptybox[] }
|
|
459
|
+
sq << gg
|
|
460
|
+
gg.from.delete g
|
|
461
|
+
g.to.delete gg
|
|
462
|
+
newboxo[g] ||= order[g]
|
|
463
|
+
sq.inject { |g1, g2|
|
|
464
|
+
g1.to |= [g2]
|
|
465
|
+
g2.from |= [g1]
|
|
466
|
+
newboxo[g2] = newboxo[g1]+1
|
|
467
|
+
g2
|
|
468
|
+
}
|
|
469
|
+
raise if newboxo[gg] != ogg
|
|
470
|
+
end
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
order.update newboxo
|
|
475
|
+
|
|
476
|
+
# layers[o] = [list of nodes of order o]
|
|
477
|
+
layers = []
|
|
478
|
+
groups.each { |g|
|
|
479
|
+
(layers[order[g]] ||= []) << g
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
layers
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# take all groups, order them by order, layout as layers
|
|
486
|
+
# always return a single group holding everything
|
|
487
|
+
def layout_layers(groups)
|
|
488
|
+
order = order_graph(groups)
|
|
489
|
+
# already a tree
|
|
490
|
+
layers = create_layers(groups, order)
|
|
491
|
+
return if layers.empty?
|
|
492
|
+
|
|
493
|
+
layers.each { |l| l.each { |g| group_remove_hz_margin(g) } }
|
|
494
|
+
|
|
495
|
+
# widest layer width
|
|
496
|
+
maxlw = layers.map { |l| l.inject(0) { |ll, g| ll + g.w } }.max
|
|
497
|
+
|
|
498
|
+
# center the 1st layer boxes on a segment that large
|
|
499
|
+
x0 = -maxlw/2.0
|
|
500
|
+
curlw = layers[0].inject(0) { |ll, g| ll + g.w }
|
|
501
|
+
dx0 = (maxlw - curlw) / (2.0*layers[0].length)
|
|
502
|
+
layers[0].each { |g|
|
|
503
|
+
x0 += dx0
|
|
504
|
+
g.x = x0
|
|
505
|
+
x0 += g.w + dx0
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
# at this point, the goal is to reorder the most populated layer the best we can, and
|
|
509
|
+
# move other layers' boxes accordingly
|
|
510
|
+
layers[1..-1].each { |l|
|
|
511
|
+
# for each subsequent layer, reorder boxes based on their ties with the previous layer
|
|
512
|
+
i = 0
|
|
513
|
+
l.replace l.sort_by { |g|
|
|
514
|
+
# we know g.from is not empty (g would be in @layer[0])
|
|
515
|
+
medfrom = g.from.inject(0.0) { |mx, gg| mx + (gg.x + gg.w/2.0) } / g.from.length
|
|
516
|
+
# on ties, keep original order
|
|
517
|
+
[medfrom, i]
|
|
518
|
+
}
|
|
519
|
+
# now they are reordered, update their #x accordingly
|
|
520
|
+
# evenly distribute them in the layer
|
|
521
|
+
x0 = -maxlw/2.0
|
|
522
|
+
curlw = l.inject(0) { |ll, g| ll + g.w }
|
|
523
|
+
dx0 = (maxlw - curlw) / (2.0*l.length)
|
|
524
|
+
l.each { |g|
|
|
525
|
+
x0 += dx0
|
|
526
|
+
g.x = x0
|
|
527
|
+
x0 += g.w + dx0
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
layers[0...-1].reverse_each { |l|
|
|
532
|
+
# for each subsequent layer, reorder boxes based on their ties with the previous layer
|
|
533
|
+
i = 0
|
|
534
|
+
l.replace l.sort_by { |g|
|
|
535
|
+
if g.to.empty?
|
|
536
|
+
# TODO floating end
|
|
537
|
+
medfrom = 0
|
|
538
|
+
else
|
|
539
|
+
medfrom = g.to.inject(0.0) { |mx, gg| mx + (gg.x + gg.w/2.0) } / g.to.length
|
|
540
|
+
end
|
|
541
|
+
# on ties, keep original order
|
|
542
|
+
[medfrom, i]
|
|
543
|
+
}
|
|
544
|
+
# now they are reordered, update their #x accordingly
|
|
545
|
+
x0 = -maxlw/2.0
|
|
546
|
+
curlw = l.inject(0) { |ll, g| ll + g.w }
|
|
547
|
+
dx0 = (maxlw - curlw) / (2.0*l.length)
|
|
548
|
+
l.each { |g|
|
|
549
|
+
x0 += dx0
|
|
550
|
+
g.x = x0
|
|
551
|
+
x0 += g.w + dx0
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
# now the boxes are (hopefully) sorted correctly
|
|
556
|
+
# position them according to their ties with prev/next layer
|
|
557
|
+
# from the maxw layer (positionning = packed), propagate adjacent layers positions
|
|
558
|
+
maxidx = (0..layers.length).find { |i| l = layers[i] ; l.inject(0) { |ll, g| ll + g.w } == maxlw }
|
|
559
|
+
# list of layer indexes to walk
|
|
560
|
+
ilist = [maxidx]
|
|
561
|
+
ilist.concat((maxidx+1...layers.length).to_a) if maxidx < layers.length-1
|
|
562
|
+
ilist.concat((0..maxidx-1).to_a.reverse) if maxidx > 0
|
|
563
|
+
layerbox = []
|
|
564
|
+
ilist.each { |i|
|
|
565
|
+
l = layers[i]
|
|
566
|
+
curlw = l.inject(0) { |ll, g| ll + g.w }
|
|
567
|
+
# left/rightmost acceptable position for the current box w/o overflowing on the right side
|
|
568
|
+
minx = -maxlw/2.0
|
|
569
|
+
maxx = minx + (maxlw-curlw)
|
|
570
|
+
|
|
571
|
+
# replace whole layer with a box
|
|
572
|
+
newg = layerbox[i] = Box.new(nil, l.map { |g| g.content }.flatten)
|
|
573
|
+
newg.w = maxlw
|
|
574
|
+
newg.h = l.map { |g| g.h }.max
|
|
575
|
+
newg.x = -newg.w/2
|
|
576
|
+
newg.y = -newg.h/2
|
|
577
|
+
# dont care for from/to, we'll return a single box anyway
|
|
578
|
+
|
|
579
|
+
l.each { |g|
|
|
580
|
+
ref = (i < maxidx) ? g.to : g.from
|
|
581
|
+
# TODO elastic positionning around the ideal position
|
|
582
|
+
# (g and g+1 may have the same med, then center both on it)
|
|
583
|
+
if i == maxidx
|
|
584
|
+
nx = minx
|
|
585
|
+
elsif ref.empty?
|
|
586
|
+
nx = (minx+maxx)/2
|
|
587
|
+
else
|
|
588
|
+
# center on the outline of rx
|
|
589
|
+
# may want to center on rx center's center ?
|
|
590
|
+
rx = ref.sort_by { |gg| gg.x }
|
|
591
|
+
med = (rx.first.x + rx.last.x + rx.last.w - g.w) / 2.0
|
|
592
|
+
nx = [[med, minx].max, maxx].min
|
|
593
|
+
end
|
|
594
|
+
dx = nx+g.w/2
|
|
595
|
+
g.content.each { |b| b.x += dx }
|
|
596
|
+
minx = nx+g.w
|
|
597
|
+
maxx += g.w
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
newg = Box.new(nil, layerbox.map { |g| g.content }.flatten)
|
|
602
|
+
newg.w = layerbox.map { |g| g.w }.max
|
|
603
|
+
newg.h = layerbox.inject(0) { |h, g| h + g.h }
|
|
604
|
+
newg.x = -newg.w/2
|
|
605
|
+
newg.y = -newg.h/2
|
|
606
|
+
|
|
607
|
+
# vertical: just center each box on its layer
|
|
608
|
+
y0 = newg.y
|
|
609
|
+
layerbox.each { |lg|
|
|
610
|
+
lg.content.each { |b|
|
|
611
|
+
b.y += y0-lg.y
|
|
612
|
+
}
|
|
613
|
+
y0 += lg.h
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
groups.replace [newg]
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
# place boxes in a good-looking layout
|
|
621
|
+
# create artificial 'group' container for boxes, that will later be merged in geometrical patterns
|
|
622
|
+
def auto_arrange_init
|
|
623
|
+
# 'group' is an array of boxes
|
|
624
|
+
# all groups are centered on the origin
|
|
625
|
+
h = {} # { box => group }
|
|
626
|
+
@groups = @box.map { |b|
|
|
627
|
+
b.x = -b.w/2
|
|
628
|
+
b.y = -b.h/2
|
|
629
|
+
g = Box.new(nil, [b])
|
|
630
|
+
g.x = b.x - 8
|
|
631
|
+
g.y = b.y - 9
|
|
632
|
+
g.w = b.w + 16
|
|
633
|
+
g.h = b.h + 18
|
|
634
|
+
h[b] = g
|
|
635
|
+
g
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
# init group.to/from
|
|
639
|
+
# must always point to something that is in the 'groups' array
|
|
640
|
+
# no self references
|
|
641
|
+
# a box is in one and only one group in 'groups'
|
|
642
|
+
@groups.each { |g|
|
|
643
|
+
g.to = g.content.first.to.map { |t| h[t] if t != g }.compact
|
|
644
|
+
g.from = g.content.first.from.map { |f| h[f] if f != g }.compact
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
# order boxes
|
|
648
|
+
order = order_graph(@groups)
|
|
649
|
+
|
|
650
|
+
# remove cycles from the graph
|
|
651
|
+
make_tree(@groups, order)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def auto_arrange_step(groups=@groups)
|
|
655
|
+
pattern_layout_col(groups) or pattern_layout_line(groups) or
|
|
656
|
+
pattern_layout_ifend(groups) or pattern_layout_complex(groups) or
|
|
657
|
+
layout_layers(groups)
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def auto_arrange_post
|
|
661
|
+
auto_arrange_movebox
|
|
662
|
+
#auto_arrange_vertical_shrink
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# actually move boxes inside the groups
|
|
666
|
+
def auto_arrange_movebox
|
|
667
|
+
@groups.each { |g|
|
|
668
|
+
dx = (g.x + g.w/2).to_i
|
|
669
|
+
dy = (g.y + g.h/2).to_i
|
|
670
|
+
g.content.each { |b|
|
|
671
|
+
b.x += dx
|
|
672
|
+
b.y += dy
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def auto_arrange_vertical_shrink
|
|
678
|
+
# vertical shrink
|
|
679
|
+
# TODO stuff may shrink vertically more if we could move it slightly horizontally...
|
|
680
|
+
@box.sort_by { |b| b.y }.each { |b|
|
|
681
|
+
|
|
682
|
+
next if b.from.empty?
|
|
683
|
+
# move box up to its from, unless something blocks the way
|
|
684
|
+
|
|
685
|
+
min_y = b.from.map { |bb|
|
|
686
|
+
bb.y+bb.h
|
|
687
|
+
}.find_all { |by|
|
|
688
|
+
by <= b.y
|
|
689
|
+
}.max
|
|
690
|
+
|
|
691
|
+
moo = []
|
|
692
|
+
moo << 8*b.from.length
|
|
693
|
+
moo << 8*b.from[0].to.length
|
|
694
|
+
cx = b.x+b.w/2
|
|
695
|
+
moo << b.from.map { |bb| (cx - (bb.x+bb.w/2)).abs }.max / 10
|
|
696
|
+
cx = b.from[0].x+b.from[0].w/2
|
|
697
|
+
moo << b.from[0].to.map { |bb| (cx - (bb.x+bb.w/2)).abs }.max / 10
|
|
698
|
+
margin_y = 16 + moo.max
|
|
699
|
+
|
|
700
|
+
next if not min_y or b.y <= min_y + margin_y
|
|
701
|
+
|
|
702
|
+
blocking = @box.find_all { |bb|
|
|
703
|
+
next if bb == b
|
|
704
|
+
bb.y+bb.h > min_y and bb.y+bb.h < b.y and
|
|
705
|
+
bb.x-12 < b.x+b.w and bb.x+bb.w+12 > b.x
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
may_y = blocking.map { |bb| bb.y+bb.h } << min_y
|
|
709
|
+
|
|
710
|
+
do_y = may_y.sort.map { |by| by + margin_y }.find { |by|
|
|
711
|
+
# should not collision with b if moved to by+margin_y
|
|
712
|
+
not blocking.find { |bb|
|
|
713
|
+
bb.x-12 < b.x+b.w and bb.x+bb.w+12 > b.x and
|
|
714
|
+
bb.y-12 < by+b.h and bb.y+bb.h+12 > by
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
b.y = do_y if do_y < b.y
|
|
719
|
+
|
|
720
|
+
# no need to re-sort outer loop
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
# TODO
|
|
724
|
+
# energy-minimal positionning of boxes from this basic layout
|
|
725
|
+
# avoid arrow confusions
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def auto_arrange_boxes
|
|
729
|
+
auto_arrange_init
|
|
730
|
+
nil while @groups.length > 1 and auto_arrange_step
|
|
731
|
+
auto_arrange_post
|
|
732
|
+
@groups = []
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# gives a text representation of the current graph state
|
|
736
|
+
def dump_layout(groups=@groups)
|
|
737
|
+
groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.sort.inspect}" }
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class GraphViewWidget < DrawableWidget
|
|
746
|
+
attr_accessor :dasm, :caret_box, :curcontext, :zoom, :margin
|
|
747
|
+
# bool, specifies if we should display addresses before instrs
|
|
748
|
+
attr_accessor :show_addresses
|
|
749
|
+
|
|
750
|
+
def initialize_widget(dasm, parent_widget)
|
|
751
|
+
@dasm = dasm
|
|
752
|
+
@parent_widget = parent_widget
|
|
753
|
+
|
|
754
|
+
@show_addresses = false
|
|
755
|
+
|
|
756
|
+
@caret_box = nil
|
|
757
|
+
@selected_boxes = []
|
|
758
|
+
@shown_boxes = []
|
|
759
|
+
@mousemove_origin = @mousemove_origin_ctrl = nil
|
|
760
|
+
@curcontext = Graph.new(nil)
|
|
761
|
+
@want_focus_addr = nil
|
|
762
|
+
@margin = 8
|
|
763
|
+
@zoom = 1.0
|
|
764
|
+
@default_color_association = ColorTheme.merge :hlbox_bg => :palegrey, :box_bg => :white,
|
|
765
|
+
:arrow_hl => :red, :arrow_cond => :darkgreen, :arrow_uncond => :darkblue,
|
|
766
|
+
:arrow_direct => :darkred, :box_bg_shadow => :black, :background => :paleblue
|
|
767
|
+
# @othergraphs = ? (to keep user-specified formatting)
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
def view_x; @curcontext.view_x; end
|
|
771
|
+
def view_x=(vx); @curcontext.view_x = vx; end
|
|
772
|
+
def view_y; @curcontext.view_y; end
|
|
773
|
+
def view_y=(vy); @curcontext.view_y = vy; end
|
|
774
|
+
|
|
775
|
+
def resized(w, h)
|
|
776
|
+
redraw
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def find_box_xy(x, y)
|
|
780
|
+
x = view_x+x/@zoom
|
|
781
|
+
y = view_y+y/@zoom
|
|
782
|
+
@shown_boxes.to_a.reverse.find { |b| b.x <= x and b.x+b.w > x and b.y <= y-1 and b.y+b.h > y+1 }
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
def mouse_wheel_ctrl(dir, x, y)
|
|
786
|
+
case dir
|
|
787
|
+
when :up
|
|
788
|
+
if @zoom < 100
|
|
789
|
+
# zoom in
|
|
790
|
+
oldzoom = @zoom
|
|
791
|
+
@zoom *= 1.1
|
|
792
|
+
@zoom = 1.0 if (@zoom-1.0).abs < 0.05
|
|
793
|
+
@curcontext.view_x += (x / oldzoom - x / @zoom)
|
|
794
|
+
@curcontext.view_y += (y / oldzoom - y / @zoom)
|
|
795
|
+
end
|
|
796
|
+
when :down
|
|
797
|
+
if @zoom > 1.0/1000
|
|
798
|
+
# zoom out
|
|
799
|
+
oldzoom = @zoom
|
|
800
|
+
@zoom /= 1.1
|
|
801
|
+
@zoom = 1.0 if (@zoom-1.0).abs < 0.05
|
|
802
|
+
@curcontext.view_x += (x / oldzoom - x / @zoom)
|
|
803
|
+
@curcontext.view_y += (y / oldzoom - y / @zoom)
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
redraw
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
def mouse_wheel(dir, x, y)
|
|
810
|
+
case dir
|
|
811
|
+
when :up; @curcontext.view_y -= height/4 / @zoom
|
|
812
|
+
when :down; @curcontext.view_y += height/4 / @zoom
|
|
813
|
+
end
|
|
814
|
+
redraw
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
def mousemove(x, y)
|
|
818
|
+
return if not @mousemove_origin
|
|
819
|
+
|
|
820
|
+
dx = (x - @mousemove_origin[0])/@zoom
|
|
821
|
+
dy = (y - @mousemove_origin[1])/@zoom
|
|
822
|
+
@mousemove_origin = [x, y]
|
|
823
|
+
if @selected_boxes.empty?
|
|
824
|
+
@curcontext.view_x -= dx ; @curcontext.view_y -= dy
|
|
825
|
+
else
|
|
826
|
+
@selected_boxes.each { |b| b.x += dx ; b.y += dy }
|
|
827
|
+
end
|
|
828
|
+
redraw
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
def mouserelease(x, y)
|
|
832
|
+
mousemove(x, y)
|
|
833
|
+
@mousemove_origin = nil
|
|
834
|
+
|
|
835
|
+
if @mousemove_origin_ctrl
|
|
836
|
+
x1 = view_x + @mousemove_origin_ctrl[0]/@zoom
|
|
837
|
+
x2 = x1 + (x - @mousemove_origin_ctrl[0])/@zoom
|
|
838
|
+
x1, x2 = x2, x1 if x1 > x2
|
|
839
|
+
y1 = view_y + @mousemove_origin_ctrl[1]/@zoom
|
|
840
|
+
y2 = y1 + (y - @mousemove_origin_ctrl[1])/@zoom
|
|
841
|
+
y1, y2 = y2, y1 if y1 > y2
|
|
842
|
+
@selected_boxes |= @curcontext.box.find_all { |b| b.x >= x1 and b.x + b.w <= x2 and b.y >= y1 and b.y + b.h <= y2 }
|
|
843
|
+
redraw
|
|
844
|
+
@mousemove_origin_ctrl = nil
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
def click_ctrl(x, y)
|
|
849
|
+
if b = find_box_xy(x, y)
|
|
850
|
+
if @selected_boxes.include? b
|
|
851
|
+
@selected_boxes.delete b
|
|
852
|
+
else
|
|
853
|
+
@selected_boxes << b
|
|
854
|
+
end
|
|
855
|
+
redraw
|
|
856
|
+
else
|
|
857
|
+
@mousemove_origin_ctrl = [x, y]
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
def click(x, y)
|
|
862
|
+
@mousemove_origin = [x, y]
|
|
863
|
+
if b = find_box_xy(x, y)
|
|
864
|
+
@selected_boxes = [b] if not @selected_boxes.include? b
|
|
865
|
+
@caret_box = b
|
|
866
|
+
@caret_x = (view_x+x/@zoom-b.x-1).to_i / @font_width
|
|
867
|
+
@caret_y = (view_y+y/@zoom-b.y-1).to_i / @font_height
|
|
868
|
+
update_caret
|
|
869
|
+
else
|
|
870
|
+
@selected_boxes = []
|
|
871
|
+
@caret_box = nil
|
|
872
|
+
end
|
|
873
|
+
redraw
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
def setup_contextmenu(b, m)
|
|
877
|
+
cm = new_menu
|
|
878
|
+
addsubmenu(cm, 'copy _word') { clipboard_copy(@hl_word) if @hl_word }
|
|
879
|
+
addsubmenu(cm, 'copy _line') { clipboard_copy(@caret_box[:line_text_col][@caret_y].map { |ss, cc| ss }.join) }
|
|
880
|
+
addsubmenu(cm, 'copy _box') {
|
|
881
|
+
sb = @selected_boxes
|
|
882
|
+
sb = [@curbox] if sb.empty?
|
|
883
|
+
clipboard_copy(sb.map { |ob| ob[:line_text_col].map { |s| s.map { |ss, cc| ss }.join + "\r\n" }.join }.join("\r\n"))
|
|
884
|
+
} # XXX auto \r\n vs \n
|
|
885
|
+
addsubmenu(m, '_clipboard', cm)
|
|
886
|
+
addsubmenu(m, 'clone _window') { @parent_widget.clone_window(@hl_word, :graph) }
|
|
887
|
+
addsubmenu(m, 'show descendants only') { hide_non_descendants(@selected_boxes) }
|
|
888
|
+
addsubmenu(m, 'show ascendants only') { hide_non_ascendants(@selected_boxes) }
|
|
889
|
+
addsubmenu(m, 'restore graph') { gui_update }
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
# if the target is a call to a subfunction, open a new window with the graph of this function (popup)
|
|
893
|
+
def rightclick(x, y)
|
|
894
|
+
if b = find_box_xy(x, y) and @zoom >= 0.90 and @zoom <= 1.1
|
|
895
|
+
click(x, y)
|
|
896
|
+
@mousemove_origin = nil
|
|
897
|
+
m = new_menu
|
|
898
|
+
setup_contextmenu(b, m)
|
|
899
|
+
if @parent_widget.respond_to?(:extend_contextmenu)
|
|
900
|
+
@parent_widget.extend_contextmenu(self, m, @caret_box[:line_address][@caret_y])
|
|
901
|
+
end
|
|
902
|
+
popupmenu(m, x, y)
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
def doubleclick(x, y)
|
|
907
|
+
@mousemove_origin = nil
|
|
908
|
+
if b = find_box_xy(x, y)
|
|
909
|
+
if @hl_word and @zoom >= 0.90 and @zoom <= 1.1
|
|
910
|
+
@parent_widget.focus_addr(@hl_word)
|
|
911
|
+
else
|
|
912
|
+
@parent_widget.focus_addr((b[:addresses] || b[:line_address]).first)
|
|
913
|
+
end
|
|
914
|
+
elsif doubleclick_check_arrow(x, y)
|
|
915
|
+
elsif @zoom == 1.0
|
|
916
|
+
zoom_all
|
|
917
|
+
else
|
|
918
|
+
@curcontext.view_x += (x/@zoom - x)
|
|
919
|
+
@curcontext.view_y += (y/@zoom - y)
|
|
920
|
+
@zoom = 1.0
|
|
921
|
+
end
|
|
922
|
+
redraw
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# check if the user clicked on the beginning/end of an arrow, if so focus on the other end
|
|
926
|
+
def doubleclick_check_arrow(x, y)
|
|
927
|
+
return if @margin*@zoom < 2
|
|
928
|
+
x = view_x+x/@zoom
|
|
929
|
+
y = view_y+y/@zoom
|
|
930
|
+
sx = nil
|
|
931
|
+
if bt = @shown_boxes.to_a.reverse.find { |b|
|
|
932
|
+
y >= b.y+b.h-1 and y <= b.y+b.h-1+@margin+2 and
|
|
933
|
+
sx = b.x+b.w/2 - b.to.length/2 * @margin/2 and
|
|
934
|
+
x >= sx-@margin/2 and x <= sx+b.to.length*@margin/2 # should be margin/4, but add a little comfort margin
|
|
935
|
+
}
|
|
936
|
+
idx = (x-sx+@margin/4).to_i / (@margin/2)
|
|
937
|
+
idx = 0 if idx < 0
|
|
938
|
+
idx = bt.to.length-1 if idx >= bt.to.length
|
|
939
|
+
if bt.to[idx]
|
|
940
|
+
if @parent_widget
|
|
941
|
+
@caret_box, @caret_y = bt, bt[:line_address].length-1
|
|
942
|
+
@parent_widget.focus_addr bt.to[idx][:line_address][0]
|
|
943
|
+
else
|
|
944
|
+
focus_xy(bt.to[idx].x, bt.to[idx].y)
|
|
945
|
+
end
|
|
946
|
+
end
|
|
947
|
+
true
|
|
948
|
+
elsif bf = @shown_boxes.to_a.reverse.find { |b|
|
|
949
|
+
y >= b.y-@margin-2 and y <= b.y and
|
|
950
|
+
sx = b.x+b.w/2 - b.from.length/2 * @margin/2 and
|
|
951
|
+
x >= sx-@margin/2 and x <= sx+b.from.length*@margin/2
|
|
952
|
+
}
|
|
953
|
+
idx = (x-sx+@margin/4).to_i / (@margin/2)
|
|
954
|
+
idx = 0 if idx < 0
|
|
955
|
+
idx = bf.from.length-1 if idx >= bf.from.length
|
|
956
|
+
if bf.from[idx]
|
|
957
|
+
if @parent_widget
|
|
958
|
+
@caret_box, @caret_y = bf, bf[:line_address].length-1
|
|
959
|
+
@parent_widget.focus_addr bf.from[idx][:line_address][-1]
|
|
960
|
+
else
|
|
961
|
+
focus_xy(bt.from[idx].x, bt.from[idx].y)
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
true
|
|
965
|
+
end
|
|
966
|
+
end
|
|
967
|
+
|
|
968
|
+
# update the zoom & view_xy to show the whole graph in the window
|
|
969
|
+
def zoom_all
|
|
970
|
+
minx, miny, maxx, maxy = @curcontext.boundingbox
|
|
971
|
+
minx -= @margin
|
|
972
|
+
miny -= @margin
|
|
973
|
+
maxx += @margin
|
|
974
|
+
maxy += @margin
|
|
975
|
+
|
|
976
|
+
@zoom = [width.to_f/(maxx-minx), height.to_f/(maxy-miny)].min
|
|
977
|
+
@zoom = 1.0 if @zoom > 1.0 or (@zoom-1.0).abs < 0.1
|
|
978
|
+
@curcontext.view_x = minx + (maxx-minx-width/@zoom)/2
|
|
979
|
+
@curcontext.view_y = miny + (maxy-miny-height/@zoom)/2
|
|
980
|
+
redraw
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
def paint
|
|
984
|
+
update_graph if @want_update_graph
|
|
985
|
+
if @want_focus_addr and @curcontext.box.find { |b_| b_[:line_address].index(@want_focus_addr) }
|
|
986
|
+
focus_addr(@want_focus_addr, false)
|
|
987
|
+
@want_focus_addr = nil
|
|
988
|
+
#zoom_all
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
@curcontext.box.each { |b|
|
|
992
|
+
# reorder arrows so that endings do not overlap
|
|
993
|
+
b.to = b.to.sort_by { |bt| bt.x+bt.w/2 }
|
|
994
|
+
b.from = b.from.sort_by { |bt| bt.x+bt.w/2 }
|
|
995
|
+
}
|
|
996
|
+
# arrows drawn first to stay under the boxes
|
|
997
|
+
# XXX precalc ?
|
|
998
|
+
@curcontext.box.each { |b|
|
|
999
|
+
b.to.each { |bt| paint_arrow(b, bt) }
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
@shown_boxes = []
|
|
1003
|
+
w_w = width
|
|
1004
|
+
w_h = height
|
|
1005
|
+
@curcontext.box.each { |b|
|
|
1006
|
+
next if b.x >= view_x+w_w/@zoom or b.y >= view_y+w_h/@zoom or b.x+b.w <= view_x or b.y+b.h <= view_y
|
|
1007
|
+
@shown_boxes << b
|
|
1008
|
+
paint_box(b)
|
|
1009
|
+
}
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
def set_color_arrow(b1, b2)
|
|
1013
|
+
if b1 == @caret_box or b2 == @caret_box
|
|
1014
|
+
draw_color :arrow_hl
|
|
1015
|
+
elsif b1.to.length == 1
|
|
1016
|
+
draw_color :arrow_uncond
|
|
1017
|
+
elsif b1.direct_to == b2.id
|
|
1018
|
+
draw_color :arrow_direct
|
|
1019
|
+
else
|
|
1020
|
+
draw_color :arrow_cond
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
def paint_arrow(b1, b2)
|
|
1025
|
+
x1 = x1o = b1.x+b1.w/2-view_x
|
|
1026
|
+
y1 = b1.y+b1.h-view_y
|
|
1027
|
+
x2 = x2o = b2.x+b2.w/2-view_x
|
|
1028
|
+
y2 = b2.y-1-view_y
|
|
1029
|
+
margin = @margin
|
|
1030
|
+
x1 += (-(b1.to.length-1)/2 + b1.to.index(b2)) * margin/2
|
|
1031
|
+
x2 += (-(b2.from.length-1)/2 + b2.from.index(b1)) * margin/2
|
|
1032
|
+
return if (y1+margin < 0 and y2 < 0) or (y1 > height/@zoom and y2-margin > height/@zoom) # just clip on y
|
|
1033
|
+
margin, x1, y1, x2, y2, b1w, b2w, x1o, x2o = [margin, x1, y1, x2, y2, b1.w, b2.w, x1o, x2o].map { |v| v*@zoom }
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
# straighten vertical arrows if possible
|
|
1037
|
+
if y2 > y1 and (x1-x2).abs <= margin
|
|
1038
|
+
if b1.to.length == 1
|
|
1039
|
+
x1 = x2
|
|
1040
|
+
elsif b2.from.length == 1
|
|
1041
|
+
x2 = x1
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
set_color_arrow(b1, b2)
|
|
1046
|
+
if margin > 1
|
|
1047
|
+
# draw arrow tip
|
|
1048
|
+
draw_line(x1, y1, x1, y1+margin)
|
|
1049
|
+
draw_line(x2, y2-margin+1, x2, y2)
|
|
1050
|
+
draw_line(x2-margin/2, y2-margin/2, x2, y2)
|
|
1051
|
+
draw_line(x2+margin/2, y2-margin/2, x2, y2)
|
|
1052
|
+
y1 += margin
|
|
1053
|
+
y2 -= margin-1
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
if y2 > y1 - b1.h*@zoom - 2*margin+1
|
|
1057
|
+
# straight arrow
|
|
1058
|
+
draw_line(x1, y1, x2, y2) if x1 != y1 or x2 != y2
|
|
1059
|
+
|
|
1060
|
+
else
|
|
1061
|
+
# arrow goes up: navigate around b2
|
|
1062
|
+
x = (x1 <= x2 ? [x1o-b1w/2-margin, x2o-b2w/2-margin].min : [x1o+b1w/2+margin, x2o+b2w/2+margin].max)
|
|
1063
|
+
draw_line(x1, y1, x, y1)
|
|
1064
|
+
draw_line(x, y1, x, y2)
|
|
1065
|
+
draw_line(x, y2, x2, y2)
|
|
1066
|
+
draw_line(x1, y1+1, x, y1+1) # double
|
|
1067
|
+
draw_line(x+1, y1, x+1, y2)
|
|
1068
|
+
draw_line(x, y2+1, x2, y2+1)
|
|
1069
|
+
end
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
def set_color_boxshadow(b)
|
|
1073
|
+
draw_color :box_bg_shadow
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
def set_color_box(b)
|
|
1077
|
+
if @selected_boxes.include? b
|
|
1078
|
+
draw_color :hlbox_bg
|
|
1079
|
+
else
|
|
1080
|
+
draw_color :box_bg
|
|
1081
|
+
end
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
def paint_box(b)
|
|
1085
|
+
set_color_boxshadow(b)
|
|
1086
|
+
draw_rectangle((b.x-view_x+3)*@zoom, (b.y-view_y+4)*@zoom, b.w*@zoom, b.h*@zoom)
|
|
1087
|
+
set_color_box(b)
|
|
1088
|
+
draw_rectangle((b.x-view_x)*@zoom, (b.y-view_y+1)*@zoom, b.w*@zoom, b.h*@zoom)
|
|
1089
|
+
|
|
1090
|
+
# current text position
|
|
1091
|
+
x = (b.x - view_x + 1)*@zoom
|
|
1092
|
+
y = (b.y - view_y + 1)*@zoom
|
|
1093
|
+
w_w = (b.x - view_x + b.w - @font_width)*@zoom
|
|
1094
|
+
w_h = (b.y - view_y + b.h - @font_height)*@zoom
|
|
1095
|
+
w_h = height if w_h > height
|
|
1096
|
+
|
|
1097
|
+
if @parent_widget and @parent_widget.bg_color_callback
|
|
1098
|
+
ly = 0
|
|
1099
|
+
b[:line_address].each { |a|
|
|
1100
|
+
if c = @parent_widget.bg_color_callback[a]
|
|
1101
|
+
draw_rectangle_color(c, (b.x-view_x)*@zoom, (1+b.y-view_y+ly*@font_height)*@zoom, b.w*@zoom, (@font_height*@zoom).ceil)
|
|
1102
|
+
end
|
|
1103
|
+
ly += 1
|
|
1104
|
+
}
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
if @caret_box == b
|
|
1108
|
+
draw_rectangle_color(:cursorline_bg, (b.x-view_x)*@zoom, (1+b.y-view_y+@caret_y*@font_height)*@zoom, b.w*@zoom, @font_height*@zoom)
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
return if @zoom < 0.99 or @zoom > 1.1
|
|
1112
|
+
# TODO dynamic font size ?
|
|
1113
|
+
|
|
1114
|
+
# renders a string at current cursor position with a color
|
|
1115
|
+
# must not include newline
|
|
1116
|
+
render = lambda { |str, color|
|
|
1117
|
+
next if y >= w_h+2 or x >= w_w
|
|
1118
|
+
draw_string_hl(color, x, y, str)
|
|
1119
|
+
x += str.length * @font_width
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
yoff = @font_height * @zoom
|
|
1123
|
+
b[:line_text_col].each { |list|
|
|
1124
|
+
list.each { |s, c| render[s, c] } if y >= -yoff
|
|
1125
|
+
x = (b.x - view_x + 1)*@zoom
|
|
1126
|
+
y += yoff
|
|
1127
|
+
break if y > w_h+2
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if b == @caret_box and focus?
|
|
1131
|
+
cx = (b.x - view_x + 1 + @caret_x*@font_width)*@zoom
|
|
1132
|
+
cy = (b.y - view_y + 1 + @caret_y*@font_height)*@zoom
|
|
1133
|
+
draw_line_color(:caret, cx, cy, cx, cy+(@font_height-1)*@zoom)
|
|
1134
|
+
end
|
|
1135
|
+
end
|
|
1136
|
+
|
|
1137
|
+
def gui_update
|
|
1138
|
+
@want_update_graph = true
|
|
1139
|
+
redraw
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
#
|
|
1143
|
+
# rebuild the code flow graph from @curcontext.roots
|
|
1144
|
+
# recalc the boxes w/h
|
|
1145
|
+
#
|
|
1146
|
+
def update_graph
|
|
1147
|
+
@want_update_graph = false
|
|
1148
|
+
|
|
1149
|
+
ctx = @curcontext
|
|
1150
|
+
|
|
1151
|
+
boxcnt = ctx.box.length
|
|
1152
|
+
arrcnt = ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
|
|
1153
|
+
ctx.clear
|
|
1154
|
+
|
|
1155
|
+
build_ctx(ctx)
|
|
1156
|
+
|
|
1157
|
+
ctx.auto_arrange_boxes
|
|
1158
|
+
|
|
1159
|
+
return if ctx != @curcontext
|
|
1160
|
+
|
|
1161
|
+
if boxcnt != ctx.box.length or arrcnt != ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
|
|
1162
|
+
zoom_all
|
|
1163
|
+
elsif @caret_box # update @caret_box with a box at the same place
|
|
1164
|
+
bx = @caret_box.x + @caret_box.w/2
|
|
1165
|
+
by = @caret_box.y + @caret_box.h/2
|
|
1166
|
+
@caret_box = ctx.box.find { |cb| cb.x < bx and cb.x+cb.w > bx and cb.y < by and cb.y+cb.h > by }
|
|
1167
|
+
end
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def load_dotfile(path)
|
|
1171
|
+
load_dot(File.read(path))
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1174
|
+
def load_dot(dota)
|
|
1175
|
+
@want_update_graph = false
|
|
1176
|
+
@curcontext.clear
|
|
1177
|
+
boxes = {}
|
|
1178
|
+
new_box = lambda { |text|
|
|
1179
|
+
b = @curcontext.new_box(text, :line_text_col => [[[text, :text]]])
|
|
1180
|
+
b.w = (text.length+1) * @font_width
|
|
1181
|
+
b.h = @font_height
|
|
1182
|
+
b
|
|
1183
|
+
}
|
|
1184
|
+
dota.scan(/^.*$/) { |l|
|
|
1185
|
+
a = l.strip.chomp(';').split(/->/).map { |s| s.strip.delete '"' }
|
|
1186
|
+
next if not id = a.shift
|
|
1187
|
+
b0 = boxes[id] ||= new_box[id]
|
|
1188
|
+
while id = a.shift
|
|
1189
|
+
b1 = boxes[id] ||= new_box[id]
|
|
1190
|
+
b0.to |= [b1]
|
|
1191
|
+
b1.from |= [b0]
|
|
1192
|
+
b0 = b1
|
|
1193
|
+
end
|
|
1194
|
+
}
|
|
1195
|
+
redraw
|
|
1196
|
+
rescue Interrupt
|
|
1197
|
+
puts "dot_len #{boxes.length}"
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
# create the graph objects in ctx
|
|
1201
|
+
def build_ctx(ctx)
|
|
1202
|
+
# graph : block -> following blocks in same function
|
|
1203
|
+
block_rel = {}
|
|
1204
|
+
|
|
1205
|
+
todo = ctx.root_addrs.dup
|
|
1206
|
+
done = [:default, Expression::Unknown]
|
|
1207
|
+
while a = todo.shift
|
|
1208
|
+
a = @dasm.normalize a
|
|
1209
|
+
next if done.include? a
|
|
1210
|
+
done << a
|
|
1211
|
+
next if not di = @dasm.di_at(a)
|
|
1212
|
+
if not di.block_head?
|
|
1213
|
+
block_rel[di.block.address] = [a]
|
|
1214
|
+
@dasm.split_block(a)
|
|
1215
|
+
end
|
|
1216
|
+
block_rel[a] = []
|
|
1217
|
+
di.block.each_to_samefunc(@dasm) { |t|
|
|
1218
|
+
t = @dasm.normalize t
|
|
1219
|
+
next if not @dasm.di_at(t)
|
|
1220
|
+
todo << t
|
|
1221
|
+
block_rel[a] << t
|
|
1222
|
+
}
|
|
1223
|
+
block_rel[a].uniq!
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
# populate boxes
|
|
1227
|
+
addr2box = {}
|
|
1228
|
+
todo = ctx.root_addrs.dup
|
|
1229
|
+
todo.delete_if { |t| not @dasm.di_at(t) } # undefined func start
|
|
1230
|
+
done = []
|
|
1231
|
+
while a = todo.shift
|
|
1232
|
+
next if done.include? a
|
|
1233
|
+
done << a
|
|
1234
|
+
if not ctx.keep_split.to_a.include?(a) and from = block_rel.keys.find_all { |ba| block_rel[ba].include? a } and
|
|
1235
|
+
from.length == 1 and block_rel[from.first].length == 1 and
|
|
1236
|
+
addr2box[from.first] and lst = @dasm.decoded[from.first].block.list.last and
|
|
1237
|
+
lst.next_addr == a and (not lst.opcode.props[:saveip] or lst.block.to_subfuncret)
|
|
1238
|
+
box = addr2box[from.first]
|
|
1239
|
+
else
|
|
1240
|
+
box = ctx.new_box a, :addresses => [], :line_text_col => [], :line_address => []
|
|
1241
|
+
end
|
|
1242
|
+
@dasm.decoded[a].block.list.each { |di_|
|
|
1243
|
+
box[:addresses] << di_.address
|
|
1244
|
+
addr2box[di_.address] = box
|
|
1245
|
+
}
|
|
1246
|
+
todo.concat block_rel[a]
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
# link boxes
|
|
1250
|
+
ctx.box.each { |b|
|
|
1251
|
+
next if not di = @dasm.decoded[b[:addresses].last]
|
|
1252
|
+
a = di.block.address
|
|
1253
|
+
next if not block_rel[a]
|
|
1254
|
+
block_rel[a].each { |t|
|
|
1255
|
+
ctx.link_boxes(b.id, t)
|
|
1256
|
+
b.direct_to = t if t == di.next_addr
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
# calc box dimensions/text
|
|
1261
|
+
ctx.box.each { |b|
|
|
1262
|
+
colstr = []
|
|
1263
|
+
curaddr = nil
|
|
1264
|
+
line = 0
|
|
1265
|
+
render = lambda { |str, col| colstr << [str, col] }
|
|
1266
|
+
nl = lambda {
|
|
1267
|
+
b[:line_address][line] = curaddr
|
|
1268
|
+
b[:line_text_col][line] = colstr
|
|
1269
|
+
colstr = []
|
|
1270
|
+
line += 1
|
|
1271
|
+
}
|
|
1272
|
+
b[:addresses].each { |addr|
|
|
1273
|
+
curaddr = addr
|
|
1274
|
+
if di = @dasm.di_at(curaddr)
|
|
1275
|
+
if di.block_head?
|
|
1276
|
+
# render dump_block_header, add a few colors
|
|
1277
|
+
b_header = '' ; @dasm.dump_block_header(di.block) { |l| b_header << l ; b_header << ?\n if b_header[-1] != ?\n }
|
|
1278
|
+
b_header.strip.each_line { |l| l.chomp!
|
|
1279
|
+
col = :comment
|
|
1280
|
+
col = :label if l[0, 2] != '//' and l[-1] == ?:
|
|
1281
|
+
render[l, col]
|
|
1282
|
+
nl[]
|
|
1283
|
+
}
|
|
1284
|
+
end
|
|
1285
|
+
render["#{Expression[curaddr]} ", :address] if @show_addresses
|
|
1286
|
+
render[di.instruction.to_s.ljust(di.comment ? 18 : 0), :instruction]
|
|
1287
|
+
render[' ; ' + di.comment.join(' ')[0, 64], :comment] if di.comment
|
|
1288
|
+
nl[]
|
|
1289
|
+
else
|
|
1290
|
+
# TODO real data display (dwords, xrefs, strings..)
|
|
1291
|
+
if label = @dasm.get_label_at(curaddr)
|
|
1292
|
+
render[label + ' ', :label]
|
|
1293
|
+
end
|
|
1294
|
+
s = @dasm.get_section_at(curaddr)
|
|
1295
|
+
render['db '+((s and s[0].data.length > s[0].ptr) ? Expression[s[0].read(1)[0]].to_s : '?'), :text]
|
|
1296
|
+
nl[]
|
|
1297
|
+
end
|
|
1298
|
+
}
|
|
1299
|
+
b.w = b[:line_text_col].map { |strc| strc.map { |s, c| s }.join.length }.max.to_i * @font_width + 2
|
|
1300
|
+
b.w += 1 if b.w % 2 == 0 # ensure boxes have odd width -> vertical arrows are straight
|
|
1301
|
+
b.h = line * @font_height
|
|
1302
|
+
}
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
def keypress_ctrl(key)
|
|
1306
|
+
case key
|
|
1307
|
+
when ?F
|
|
1308
|
+
@parent_widget.inputbox('text to search in curview (regex)', :text => @hl_word) { |pat|
|
|
1309
|
+
re = /#{pat}/i
|
|
1310
|
+
list = [['addr', 'instr']]
|
|
1311
|
+
@curcontext.box.each { |b|
|
|
1312
|
+
b[:line_text_col].zip(b[:line_address]) { |l, a|
|
|
1313
|
+
str = l.map { |s, c| s }.join
|
|
1314
|
+
list << [Expression[a], str] if str =~ re
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
@parent_widget.list_bghilight("search result for /#{pat}/i", list) { |i| @parent_widget.focus_addr i[0] }
|
|
1318
|
+
}
|
|
1319
|
+
when ?+; mouse_wheel_ctrl(:up, width/2, height/2)
|
|
1320
|
+
when ?-; mouse_wheel_ctrl(:down, width/2, height/2)
|
|
1321
|
+
else return false
|
|
1322
|
+
end
|
|
1323
|
+
true
|
|
1324
|
+
end
|
|
1325
|
+
|
|
1326
|
+
def keypress(key)
|
|
1327
|
+
case key
|
|
1328
|
+
when :left
|
|
1329
|
+
if @caret_box
|
|
1330
|
+
if @caret_x > 0
|
|
1331
|
+
@caret_x -= 1
|
|
1332
|
+
update_caret
|
|
1333
|
+
elsif b = @curcontext.box.sort_by { |b_| -b_.x }.find { |b_| b_.x < @caret_box.x and
|
|
1334
|
+
b_.y < @caret_box.y+@caret_y*@font_height and
|
|
1335
|
+
b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
|
|
1336
|
+
@caret_x = (b.w/@font_width).to_i
|
|
1337
|
+
@caret_y += ((@caret_box.y-b.y)/@font_height).to_i
|
|
1338
|
+
@caret_box = b
|
|
1339
|
+
update_caret
|
|
1340
|
+
redraw
|
|
1341
|
+
else
|
|
1342
|
+
@curcontext.view_x -= 20/@zoom
|
|
1343
|
+
redraw
|
|
1344
|
+
end
|
|
1345
|
+
else
|
|
1346
|
+
@curcontext.view_x -= 20/@zoom
|
|
1347
|
+
redraw
|
|
1348
|
+
end
|
|
1349
|
+
when :up
|
|
1350
|
+
if @caret_box
|
|
1351
|
+
if @caret_y > 0
|
|
1352
|
+
@caret_y -= 1
|
|
1353
|
+
update_caret
|
|
1354
|
+
elsif b = @curcontext.box.sort_by { |b_| -b_.y }.find { |b_| b_.y < @caret_box.y and
|
|
1355
|
+
b_.x < @caret_box.x+@caret_x*@font_width and
|
|
1356
|
+
b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
|
|
1357
|
+
@caret_x += ((@caret_box.x-b.x)/@font_width).to_i
|
|
1358
|
+
@caret_y = b[:line_address].length-1
|
|
1359
|
+
@caret_box = b
|
|
1360
|
+
update_caret
|
|
1361
|
+
redraw
|
|
1362
|
+
else
|
|
1363
|
+
@curcontext.view_y -= 20/@zoom
|
|
1364
|
+
redraw
|
|
1365
|
+
end
|
|
1366
|
+
else
|
|
1367
|
+
@curcontext.view_y -= 20/@zoom
|
|
1368
|
+
redraw
|
|
1369
|
+
end
|
|
1370
|
+
when :right
|
|
1371
|
+
if @caret_box
|
|
1372
|
+
if @caret_x <= @caret_box[:line_text_col].map { |s| s.map { |ss, cc| ss }.join.length }.max
|
|
1373
|
+
@caret_x += 1
|
|
1374
|
+
update_caret
|
|
1375
|
+
elsif b = @curcontext.box.sort_by { |b_| b_.x }.find { |b_| b_.x > @caret_box.x and
|
|
1376
|
+
b_.y < @caret_box.y+@caret_y*@font_height and
|
|
1377
|
+
b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
|
|
1378
|
+
@caret_x = 0
|
|
1379
|
+
@caret_y += ((@caret_box.y-b.y)/@font_height).to_i
|
|
1380
|
+
@caret_box = b
|
|
1381
|
+
update_caret
|
|
1382
|
+
redraw
|
|
1383
|
+
else
|
|
1384
|
+
@curcontext.view_x += 20/@zoom
|
|
1385
|
+
redraw
|
|
1386
|
+
end
|
|
1387
|
+
else
|
|
1388
|
+
@curcontext.view_x += 20/@zoom
|
|
1389
|
+
redraw
|
|
1390
|
+
end
|
|
1391
|
+
when :down
|
|
1392
|
+
if @caret_box
|
|
1393
|
+
if @caret_y < @caret_box[:line_address].length-1
|
|
1394
|
+
@caret_y += 1
|
|
1395
|
+
update_caret
|
|
1396
|
+
elsif b = @curcontext.box.sort_by { |b_| b_.y }.find { |b_| b_.y > @caret_box.y and
|
|
1397
|
+
b_.x < @caret_box.x+@caret_x*@font_width and
|
|
1398
|
+
b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
|
|
1399
|
+
@caret_x += ((@caret_box.x-b.x)/@font_width).to_i
|
|
1400
|
+
@caret_y = 0
|
|
1401
|
+
@caret_box = b
|
|
1402
|
+
update_caret
|
|
1403
|
+
redraw
|
|
1404
|
+
else
|
|
1405
|
+
@curcontext.view_y += 20/@zoom
|
|
1406
|
+
redraw
|
|
1407
|
+
end
|
|
1408
|
+
else
|
|
1409
|
+
@curcontext.view_y += 20/@zoom
|
|
1410
|
+
redraw
|
|
1411
|
+
end
|
|
1412
|
+
when :pgup
|
|
1413
|
+
if @caret_box
|
|
1414
|
+
@caret_y -= (height/4/@zoom/@font_height).to_i
|
|
1415
|
+
@caret_y = 0 if @caret_y < 0
|
|
1416
|
+
update_caret(false)
|
|
1417
|
+
else
|
|
1418
|
+
@curcontext.view_y -= height/4/@zoom
|
|
1419
|
+
redraw
|
|
1420
|
+
end
|
|
1421
|
+
when :pgdown
|
|
1422
|
+
if @caret_box
|
|
1423
|
+
@caret_y += (height/4/@zoom/@font_height).to_i
|
|
1424
|
+
@caret_y = [@caret_box[:line_address].length-1, @caret_y].min
|
|
1425
|
+
update_caret(false)
|
|
1426
|
+
else
|
|
1427
|
+
@curcontext.view_y += height/4/@zoom
|
|
1428
|
+
redraw
|
|
1429
|
+
end
|
|
1430
|
+
when :home
|
|
1431
|
+
if @caret_box
|
|
1432
|
+
@caret_x = 0
|
|
1433
|
+
update_caret(false)
|
|
1434
|
+
else
|
|
1435
|
+
@curcontext.view_x = @curcontext.box.map { |b_| b_.x }.min-10
|
|
1436
|
+
@curcontext.view_y = @curcontext.box.map { |b_| b_.y }.min-10
|
|
1437
|
+
redraw
|
|
1438
|
+
end
|
|
1439
|
+
when :end
|
|
1440
|
+
if @caret_box
|
|
1441
|
+
@caret_x = @caret_box[:line_text_col][@caret_y].to_a.map { |ss, cc| ss }.join.length
|
|
1442
|
+
update_caret(false)
|
|
1443
|
+
else
|
|
1444
|
+
@curcontext.view_x = [@curcontext.box.map { |b_| b_.x+b_.w }.max-width/@zoom+10, @curcontext.box.map { |b_| b_.x }.min-10].max
|
|
1445
|
+
@curcontext.view_y = [@curcontext.box.map { |b_| b_.y+b_.h }.max-height/@zoom+10, @curcontext.box.map { |b_| b_.y }.min-10].max
|
|
1446
|
+
redraw
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
when :delete
|
|
1450
|
+
@selected_boxes.each { |b_|
|
|
1451
|
+
@curcontext.box.delete b_
|
|
1452
|
+
b_.from.each { |bb| bb.to.delete b_ }
|
|
1453
|
+
b_.to.each { |bb| bb.from.delete b_ }
|
|
1454
|
+
}
|
|
1455
|
+
redraw
|
|
1456
|
+
when :popupmenu
|
|
1457
|
+
if @caret_box
|
|
1458
|
+
cx = (@caret_box.x - view_x + 1 + @caret_x*@font_width)*@zoom
|
|
1459
|
+
cy = (@caret_box.y - view_y + 1 + @caret_y*@font_height)*@zoom
|
|
1460
|
+
rightclick(cx, cy)
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1463
|
+
when ?a
|
|
1464
|
+
t0 = Time.now
|
|
1465
|
+
puts 'autoarrange'
|
|
1466
|
+
@curcontext.auto_arrange_boxes
|
|
1467
|
+
redraw
|
|
1468
|
+
puts 'autoarrange done %.02f' % (Time.now - t0)
|
|
1469
|
+
when ?u
|
|
1470
|
+
gui_update
|
|
1471
|
+
|
|
1472
|
+
when ?R
|
|
1473
|
+
load __FILE__
|
|
1474
|
+
when ?I # create arbitrary boxes/links
|
|
1475
|
+
if @selected_boxes.empty?
|
|
1476
|
+
@fakebox ||= 0
|
|
1477
|
+
b = @curcontext.new_box "id_#@fakebox",
|
|
1478
|
+
:addresses => [], :line_address => [],
|
|
1479
|
+
:line_text_col => [[[" blublu #@fakebox", :text]]]
|
|
1480
|
+
b.w = @font_width * 15
|
|
1481
|
+
b.h = @font_height * 2
|
|
1482
|
+
b.x = rand(200) - 100
|
|
1483
|
+
b.y = rand(200) - 100
|
|
1484
|
+
|
|
1485
|
+
@fakebox += 1
|
|
1486
|
+
else
|
|
1487
|
+
b1, *bl = @selected_boxes
|
|
1488
|
+
bl = [b1] if bl.empty? # loop
|
|
1489
|
+
bl.each { |b2|
|
|
1490
|
+
if b1.to.include? b2
|
|
1491
|
+
b1.to.delete b2
|
|
1492
|
+
b2.from.delete b1
|
|
1493
|
+
else
|
|
1494
|
+
b1.to << b2
|
|
1495
|
+
b2.from << b1
|
|
1496
|
+
end
|
|
1497
|
+
}
|
|
1498
|
+
end
|
|
1499
|
+
redraw
|
|
1500
|
+
|
|
1501
|
+
when ?1 # (numeric) zoom to 1:1
|
|
1502
|
+
if @zoom == 1.0
|
|
1503
|
+
zoom_all
|
|
1504
|
+
else
|
|
1505
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
|
1506
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
|
1507
|
+
@zoom = 1.0
|
|
1508
|
+
end
|
|
1509
|
+
redraw
|
|
1510
|
+
when :insert # split curbox at @caret_y
|
|
1511
|
+
if @caret_box and a = @caret_box[:line_address][@caret_y] and @dasm.decoded[a]
|
|
1512
|
+
@dasm.split_block(a)
|
|
1513
|
+
@curcontext.keep_split ||= []
|
|
1514
|
+
@curcontext.keep_split |= [a]
|
|
1515
|
+
gui_update
|
|
1516
|
+
focus_addr a
|
|
1517
|
+
end
|
|
1518
|
+
else return false
|
|
1519
|
+
end
|
|
1520
|
+
true
|
|
1521
|
+
end
|
|
1522
|
+
|
|
1523
|
+
def hide_non_descendants(list)
|
|
1524
|
+
reach = {}
|
|
1525
|
+
todo = list.dup
|
|
1526
|
+
while b = todo.pop
|
|
1527
|
+
next if reach[b]
|
|
1528
|
+
reach[b] = true
|
|
1529
|
+
b.to.each { |bb|
|
|
1530
|
+
todo << bb if bb.y+bb.h >= b.y
|
|
1531
|
+
}
|
|
1532
|
+
end
|
|
1533
|
+
|
|
1534
|
+
@curcontext.box.delete_if { |bb|
|
|
1535
|
+
!reach[bb]
|
|
1536
|
+
}
|
|
1537
|
+
@curcontext.box.each { |bb|
|
|
1538
|
+
bb.from.delete_if { |bbb| !reach[bbb] }
|
|
1539
|
+
bb.to.delete_if { |bbb| !reach[bbb] }
|
|
1540
|
+
}
|
|
1541
|
+
redraw
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
def hide_non_ascendants(list)
|
|
1545
|
+
reach = {}
|
|
1546
|
+
todo = list.dup
|
|
1547
|
+
while b = todo.pop
|
|
1548
|
+
next if reach[b]
|
|
1549
|
+
reach[b] = true
|
|
1550
|
+
b.from.each { |bb|
|
|
1551
|
+
todo << bb if bb.y <= b.h+b.y
|
|
1552
|
+
}
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
@curcontext.box.delete_if { |bb|
|
|
1556
|
+
!reach[bb]
|
|
1557
|
+
}
|
|
1558
|
+
@curcontext.box.each { |bb|
|
|
1559
|
+
bb.from.delete_if { |bbb| !reach[bbb] }
|
|
1560
|
+
bb.to.delete_if { |bbb| !reach[bbb] }
|
|
1561
|
+
}
|
|
1562
|
+
redraw
|
|
1563
|
+
end
|
|
1564
|
+
|
|
1565
|
+
# find a suitable array of graph roots, walking up from a block (function start/entrypoint)
|
|
1566
|
+
def dasm_find_roots(addr)
|
|
1567
|
+
todo = [addr]
|
|
1568
|
+
done = []
|
|
1569
|
+
roots = []
|
|
1570
|
+
default_root = nil
|
|
1571
|
+
while a = todo.shift
|
|
1572
|
+
next if not di = @dasm.di_at(a)
|
|
1573
|
+
b = di.block
|
|
1574
|
+
a = b.address
|
|
1575
|
+
if done.include? a
|
|
1576
|
+
default_root ||= a
|
|
1577
|
+
next
|
|
1578
|
+
end
|
|
1579
|
+
done << a
|
|
1580
|
+
newf = []
|
|
1581
|
+
b.each_from_samefunc(@dasm) { |f| newf << f }
|
|
1582
|
+
if newf.empty?
|
|
1583
|
+
roots << b.address
|
|
1584
|
+
else
|
|
1585
|
+
todo.concat newf
|
|
1586
|
+
end
|
|
1587
|
+
end
|
|
1588
|
+
roots << default_root if roots.empty? and default_root
|
|
1589
|
+
|
|
1590
|
+
roots
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
def set_cursor_pos(p)
|
|
1594
|
+
addr, x = p
|
|
1595
|
+
focus_addr(addr)
|
|
1596
|
+
@caret_x = x
|
|
1597
|
+
update_caret
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
def get_cursor_pos
|
|
1601
|
+
[current_address, @caret_x]
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
# focus on addr
|
|
1605
|
+
# addr may be a dasm label, dasm address, dasm address in string form (eg "0DEADBEEFh")
|
|
1606
|
+
# addr must point to a decodedinstruction
|
|
1607
|
+
# if the addr is not found in curcontext, the code flow is walked up until a function
|
|
1608
|
+
# start or an entrypoint is found, then the graph is created from there
|
|
1609
|
+
# will call gui_update then
|
|
1610
|
+
def focus_addr(addr, can_update_context=true)
|
|
1611
|
+
return if @parent_widget and not addr = @parent_widget.normalize(addr)
|
|
1612
|
+
return if not @dasm.di_at(addr)
|
|
1613
|
+
|
|
1614
|
+
# move window / change curcontext
|
|
1615
|
+
if b = @curcontext.box.find { |b_| b_[:line_address].index(addr) }
|
|
1616
|
+
@caret_box, @caret_x, @caret_y = b, 0, b[:line_address].rindex(addr)
|
|
1617
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
|
1618
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
|
1619
|
+
@zoom = 1.0
|
|
1620
|
+
|
|
1621
|
+
update_caret
|
|
1622
|
+
elsif can_update_context
|
|
1623
|
+
@curcontext = Graph.new 'testic'
|
|
1624
|
+
@curcontext.root_addrs = dasm_find_roots(addr)
|
|
1625
|
+
@want_focus_addr = addr
|
|
1626
|
+
gui_update
|
|
1627
|
+
else
|
|
1628
|
+
return
|
|
1629
|
+
end
|
|
1630
|
+
true
|
|
1631
|
+
end
|
|
1632
|
+
|
|
1633
|
+
def focus_xy(x, y)
|
|
1634
|
+
# dont move during a click
|
|
1635
|
+
return if @mousemove_origin
|
|
1636
|
+
|
|
1637
|
+
# ensure the caret stays onscreen
|
|
1638
|
+
if not view_x
|
|
1639
|
+
@curcontext.view_x = x - width/5/@zoom
|
|
1640
|
+
redraw
|
|
1641
|
+
elsif @caret_box and @caret_box.w < width*27/30/@zoom
|
|
1642
|
+
# keep @caret_box full if possible
|
|
1643
|
+
if view_x + width/20/@zoom > @caret_box.x
|
|
1644
|
+
@curcontext.view_x = @caret_box.x-width/20/@zoom
|
|
1645
|
+
elsif view_x + width*9/10/@zoom < @caret_box.x+@caret_box.w
|
|
1646
|
+
@curcontext.view_x = @caret_box.x+@caret_box.w-width*9/10/@zoom
|
|
1647
|
+
end
|
|
1648
|
+
elsif view_x + width/20/@zoom > x
|
|
1649
|
+
@curcontext.view_x = x-width/20/@zoom
|
|
1650
|
+
redraw
|
|
1651
|
+
elsif view_x + width*9/10/@zoom < x
|
|
1652
|
+
@curcontext.view_x = x-width*9/10/@zoom
|
|
1653
|
+
redraw
|
|
1654
|
+
end
|
|
1655
|
+
|
|
1656
|
+
if not view_y
|
|
1657
|
+
@curcontext.view_y = y - height/5/@zoom
|
|
1658
|
+
redraw
|
|
1659
|
+
elsif @caret_box and @caret_box.h < height*27/30/@zoom
|
|
1660
|
+
if view_y + height/20/@zoom > @caret_box.y
|
|
1661
|
+
@curcontext.view_y = @caret_box.y-height/20/@zoom
|
|
1662
|
+
elsif view_y + height*9/10/@zoom < @caret_box.y+@caret_box.h
|
|
1663
|
+
@curcontext.view_y = @caret_box.y+@caret_box.h-height*9/10/@zoom
|
|
1664
|
+
end
|
|
1665
|
+
elsif view_y + height/20/@zoom > y
|
|
1666
|
+
@curcontext.view_y = y-height/20/@zoom
|
|
1667
|
+
redraw
|
|
1668
|
+
elsif view_y + height*9/10/@zoom < y
|
|
1669
|
+
@curcontext.view_y = y-height*9/10/@zoom
|
|
1670
|
+
redraw
|
|
1671
|
+
end
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1674
|
+
# hint that the caret moved
|
|
1675
|
+
# redraw, change the hilighted word
|
|
1676
|
+
def update_caret(update_hlword = true)
|
|
1677
|
+
return if not b = @caret_box or not @caret_x or not l = @caret_box[:line_text_col][@caret_y]
|
|
1678
|
+
|
|
1679
|
+
if update_hlword
|
|
1680
|
+
l = l.map { |s, c| s }.join
|
|
1681
|
+
@parent_widget.focus_changed_callback[] if @parent_widget and @parent_widget.focus_changed_callback and @oldcaret_y != @caret_y
|
|
1682
|
+
update_hl_word(l, @caret_x)
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1685
|
+
focus_xy(b.x + @caret_x*@font_width, b.y + @caret_y*@font_height)
|
|
1686
|
+
|
|
1687
|
+
redraw
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
def current_address
|
|
1691
|
+
@caret_box ? @caret_box[:line_address][@caret_y] : @curcontext.root_addrs.first
|
|
1692
|
+
end
|
|
1693
|
+
end
|
|
1694
|
+
end
|
|
1695
|
+
end
|