origami 2.0.4 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1326 +0,0 @@
1
- =begin
2
-
3
- This file is part of PDF Walker, a graphical PDF file browser
4
- Copyright (C) 2016 Guillaume Delugré.
5
-
6
- PDF Walker is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- PDF Walker is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
18
-
19
- This work has been derived from the GHex project. Thanks to them.
20
- Original implementation: Jaka Mocnik <jaka@gnu.org>
21
- =end
22
-
23
- require 'gtk2'
24
-
25
- module Gtk
26
-
27
- class HexEditor < Fixed
28
- module View
29
- HEX = 1
30
- ASCII = 2
31
- end
32
-
33
- module Group
34
- BYTE = 1
35
- WORD = 2
36
- LONG = 4
37
- end
38
-
39
- class Highlight
40
- attr_accessor :start, :end
41
- attr_accessor :start_line, :end_line
42
- attr_accessor :style
43
- attr_accessor :min_select
44
- attr_accessor :valid
45
- end
46
-
47
- class AutoHighlight
48
- attr_accessor :search_view
49
- attr_accessor :search_string
50
- attr_accessor :search_len
51
-
52
- attr_accessor :color
53
-
54
- attr_accessor :view_min
55
- attr_accessor :view_max
56
-
57
- attr_accessor :highlights
58
- end
59
-
60
- DEFAULT_FONT = "Monospace 12"
61
- DEFAULT_CPL = 32
62
- DEFAULT_LINES = 16
63
- DISPLAY_BORDER = 4
64
- SCROLL_TIMEOUT = 100
65
-
66
- type_register
67
-
68
- @@primary = Clipboard.get(Gdk::Selection::PRIMARY)
69
- @@clipboard = Clipboard.get(Gdk::Selection::CLIPBOARD)
70
-
71
- def initialize(data = '')
72
- super()
73
-
74
- @data = data.force_encoding('binary')
75
-
76
- @scroll_timeout = -1
77
- @disp_buffer = ""
78
- @starting_offset = 0
79
-
80
- @xdisp_width = @adisp_width = 200
81
- @xdisp_gc = @adisp_gc = nil
82
- @active_view = View::HEX
83
- @group_type = Group::BYTE
84
- @lines = @vis_lines = @top_line = @cpl = 0
85
- @cursor_pos = 0
86
- @lower_nibble = false
87
- @cursor_shown = false
88
- @button = 0
89
- @insert = false
90
- @selecting = false
91
-
92
- @selection = Highlight.new
93
- @selection.start = @selection.end = 0
94
- @selection.style = nil
95
- @selection.min_select = 1
96
- @selection.valid = false
97
-
98
- @highlights = [ @selection ]
99
-
100
- @auto_highlight = nil
101
-
102
- @disp_font_metrics = load_font DEFAULT_FONT
103
- @font_desc = Pango::FontDescription.new DEFAULT_FONT
104
-
105
- @char_width = get_max_char_width(@disp_font_metrics)
106
- @char_height = Pango.pixels(@disp_font_metrics.ascent) + Pango.pixels(@disp_font_metrics.descent) + 2
107
-
108
- @show_offsets = false
109
- @offsets_gc = nil
110
-
111
- self.can_focus = true
112
- self.events = Gdk::Event::KEY_PRESS_MASK
113
- self.border_width = DISPLAY_BORDER
114
-
115
- mouse_handler = lambda do |widget, event|
116
- if event.event_type == Gdk::Event::BUTTON_RELEASE and event.button == 1
117
- if @scroll_timeout
118
- GLib::Source.remove @scroll_timeout
119
- @scroll_timeout = nil
120
- @scroll_dir = 0
121
- end
122
-
123
- @selecting = false
124
- Gtk.grab_remove(widget)
125
- @button = 0
126
- elsif event.event_type == Gdk::Event::BUTTON_PRESS and event.button == 1
127
- self.grab_focus unless self.has_focus?
128
-
129
- Gtk.grab_add(widget)
130
- @button = event.button
131
-
132
- focus_view = (widget == @xdisp) ? View::HEX : View::ASCII
133
-
134
- if @active_view == focus_view
135
- if @active_view == View::HEX
136
- hex_to_pointer(event.x, event.y)
137
- else
138
- ascii_to_pointer(event.x, event.y)
139
- end
140
-
141
- unless @selecting
142
- @selecting = true
143
- set_selection(@cursor_pos, @cursor_pos)
144
- end
145
- else
146
- hide_cursor
147
- @active_view = focus_view
148
- show_cursor
149
- end
150
- elsif event.event_type == Gdk::Event::BUTTON_PRESS and event.button == 2
151
- # TODO
152
- else
153
- @button = 0
154
- end
155
- end
156
-
157
- @xdisp = DrawingArea.new
158
- @xdisp.modify_font @font_desc
159
- @xlayout = @xdisp.create_pango_layout('')
160
- @xdisp.events =
161
- Gdk::Event::EXPOSURE_MASK |
162
- Gdk::Event::BUTTON_PRESS_MASK |
163
- Gdk::Event::BUTTON_RELEASE_MASK |
164
- Gdk::Event::BUTTON_MOTION_MASK |
165
- Gdk::Event::SCROLL_MASK
166
-
167
- @xdisp.signal_connect 'realize' do
168
- @xdisp_gc = Gdk::GC.new(@xdisp.window)
169
- @xdisp_gc.set_exposures(true)
170
- end
171
-
172
- @xdisp.signal_connect 'expose_event' do |_xdisp, event|
173
- imin = (event.area.y / @char_height).to_i
174
- imax = ((event.area.y + event.area.height) / @char_height).to_i
175
- imax += 1 if (event.area.y + event.area.height).to_i % @char_height != 0
176
-
177
- imax = [ imax, @vis_lines ].min
178
-
179
- render_hex_lines(imin, imax)
180
- end
181
-
182
- @xdisp.signal_connect 'scroll_event' do |_xdisp, event|
183
- @scrollbar.event(event)
184
- end
185
-
186
- @xdisp.signal_connect 'button_press_event' do |xdisp, event|
187
- mouse_handler[xdisp, event]
188
- end
189
-
190
- @xdisp.signal_connect 'button_release_event' do |xdisp, event|
191
- mouse_handler[xdisp, event]
192
- end
193
-
194
- @xdisp.signal_connect 'motion_notify_event' do |xdisp, event|
195
- _w, x, y, _m = xdisp.window.pointer
196
-
197
- if y < 0
198
- @scroll_dir = -1
199
- elsif y >= xdisp.allocation.height
200
- @scroll_dir = 1
201
- else
202
- @scroll_dir = 0
203
- end
204
-
205
- if @scroll_dir != 0
206
- if @scroll_timeout == nil
207
- @scroll_timeout = GLib::Timeout.add(SCROLL_TIMEOUT) {
208
- if @scroll_dir < 0
209
- set_cursor([ 0, @cursor_pos - @cpl ].max)
210
- elsif @scroll_dir > 0
211
- set_cursor([ @data.size - 1, @cursor_pos + @cpl ].min)
212
- end
213
-
214
- true
215
- }
216
- next
217
- end
218
- else
219
- if @scroll_timeout != nil
220
- GLib::Source.remove @scroll_timeout
221
- @scroll_timeout = nil
222
- end
223
- end
224
-
225
- next if event.window != xdisp.window
226
-
227
- hex_to_pointer(x,y) if @active_view == View::HEX and @button == 1
228
- end
229
-
230
- put @xdisp, 0, 0
231
- @xdisp.show
232
-
233
- @adisp = DrawingArea.new
234
- @adisp.modify_font @font_desc
235
- @alayout = @adisp.create_pango_layout('')
236
- @adisp.events =
237
- Gdk::Event::EXPOSURE_MASK |
238
- Gdk::Event::BUTTON_PRESS_MASK |
239
- Gdk::Event::BUTTON_RELEASE_MASK |
240
- Gdk::Event::BUTTON_MOTION_MASK |
241
- Gdk::Event::SCROLL_MASK
242
-
243
- @adisp.signal_connect 'realize' do
244
- @adisp_gc = Gdk::GC.new(@adisp.window)
245
- @adisp_gc.set_exposures(true)
246
- end
247
-
248
- @adisp.signal_connect 'expose_event' do |_adisp, event|
249
- imin = (event.area.y / @char_height).to_i
250
- imax = ((event.area.y + event.area.height) / @char_height).to_i
251
- imax += 1 if (event.area.y + event.area.height).to_i % @char_height != 0
252
-
253
- imax = [ imax, @vis_lines ].min
254
- render_ascii_lines(imin, imax)
255
- end
256
-
257
- @adisp.signal_connect 'scroll_event' do |_adisp, event|
258
- @scrollbar.event(event)
259
- end
260
-
261
- @adisp.signal_connect 'button_press_event' do |adisp, event|
262
- mouse_handler[adisp, event]
263
- end
264
-
265
- @adisp.signal_connect 'button_release_event' do |adisp, event|
266
- mouse_handler[adisp, event]
267
- end
268
-
269
- @adisp.signal_connect 'motion_notify_event' do |adisp, event|
270
- _w, x, y, _m = adisp.window.pointer
271
-
272
- if y < 0
273
- @scroll_dir = -1
274
- elsif y >= adisp.allocation.height
275
- @scroll_dir = 1
276
- else
277
- @scroll_dir = 0
278
- end
279
-
280
- if @scroll_dir != 0
281
- if @scroll_timeout == nil
282
- @scroll_timeout = GLib::Timeout.add(SCROLL_TIMEOUT) {
283
- if @scroll_dir < 0
284
- set_cursor([ 0, @cursor_pos - @cpl ].max)
285
- elsif @scroll_dir > 0
286
- set_cursor([ @data.size - 1, @cursor_pos + @cpl ].min)
287
- end
288
-
289
- true
290
- }
291
- next
292
- end
293
- else
294
- if @scroll_timeout != nil
295
- GLib::Source.remove @scroll_timeout
296
- @scroll_timeout = nil
297
- end
298
- end
299
-
300
- next if event.window != adisp.window
301
-
302
- ascii_to_pointer(x,y) if @active_view == View::ASCII and @button == 1
303
- end
304
-
305
- put @adisp, 0, 0
306
- @adisp.show
307
-
308
- @adj = Gtk::Adjustment.new(0, 0, 0, 0, 0, 0)
309
- @scrollbar = Gtk::VScrollbar.new(@adj)
310
- @adj.signal_connect 'value_changed' do |adj|
311
- unless @xdisp_gc.nil? or @adisp_gc.nil? or not @xdisp.drawable? or not @adisp.drawable?
312
- source_min = (adj.value.to_i - @top_line) * @char_height
313
- source_max = source_min + @xdisp.allocation.height
314
- dest_min = 0
315
- dest_max = @xdisp.allocation.height
316
-
317
- rect = Gdk::Rectangle.new(0, 0, 0, 0)
318
- @top_line = adj.value.to_i
319
- if source_min < 0
320
- rect.y = 0
321
- rect.height = -source_min
322
- rect_height = [ rect.height, @xdisp.allocation.height ].min
323
- source_min = 0
324
- dest_min = rect.height
325
- else
326
- rect.y = 2 * @xdisp.allocation.height - source_max
327
- rect.y = 0 if rect.y < 0
328
- rect.height = @xdisp.allocation.height - rect.y
329
- source_max = @xdisp.allocation.height
330
- dest_max = rect.y
331
- end
332
-
333
- if source_min != source_max
334
- @xdisp.window.draw_drawable(
335
- @xdisp_gc,
336
- @xdisp.window,
337
- 0, source_min,
338
- 0, dest_min,
339
- @xdisp.allocation.width,
340
- source_max - source_min
341
- )
342
- @adisp.window.draw_drawable(
343
- @adisp_gc,
344
- @adisp.window,
345
- 0, source_min,
346
- 0, dest_min,
347
- @adisp.allocation.width,
348
- source_max - source_min
349
- )
350
-
351
- if @offsets
352
- if @offsets_gc.nil?
353
- @offsets_gc = Gdk::GC.new(@offsets.window)
354
- @offsets_gc.set_exposures(true)
355
- end
356
-
357
- @offsets.window.draw_drawable(
358
- @offsets_gc,
359
- @offsets.window,
360
- 0, source_min,
361
- 0, dest_min,
362
- @offsets.allocation.width,
363
- source_max - source_min
364
- )
365
- end
366
-
367
- # TODO update_all_auto_highlights(true, true)
368
- invalidate_all_highlights
369
-
370
- rect.width = @xdisp.allocation.width
371
- @xdisp.window.invalidate(rect, false)
372
- rect.width = @adisp.allocation.width
373
- @adisp.window.invalidate(rect, false)
374
-
375
- if @offsets
376
- rect.width = @offsets.allocation.width
377
- @offsets.window.invalidate(rect, false)
378
- end
379
- end
380
- end
381
- end
382
-
383
- put @scrollbar, 0, 0
384
- @scrollbar.show
385
- end
386
-
387
- def set_selection(s, e)
388
- e = [ e, @data.size ].min
389
-
390
- @@primary.clear if @selection.start != @selection.end
391
-
392
- os, oe = [ @selection.start, @selection.end ].sort
393
-
394
- @selection.start = [ 0, s ].max
395
- @selection.start = [ @selection.start, @data.size ].min
396
- @selection.end = [ e, @data.size ].min
397
-
398
- invalidate_highlight(@selection)
399
-
400
- ns, ne = [ @selection.start, @selection.end ].sort
401
-
402
- if ns != os and ne != oe
403
- bytes_changed([ns, os].min, [ne, oe].max)
404
- elsif ne != oe
405
- bytes_changed(*[ne, oe].sort)
406
- elsif ns != os
407
- bytes_changed(*[ns, os].sort)
408
- end
409
-
410
- if @selection.start != @selection.end
411
- if @active_view == View::HEX
412
- brk_len = 2 * @cpl + @cpl / @group_type
413
- format_xblock(s,e)
414
- (@disp_buffer.size / brk_len + 1).times do |i| @disp_buffer.insert(i * (brk_len + 1), $/) end
415
- else
416
- brk_len = @cpl
417
- format_ablock(s,e)
418
- end
419
-
420
- @@primary.set_text(@disp_buffer)
421
- end
422
- end
423
-
424
- def get_selection
425
- [ @selection.start, @selection.end ].sort
426
- end
427
-
428
- def clear_selection
429
- set_selection(0, 0)
430
- end
431
-
432
- def cursor
433
- @cursor_pos
434
- end
435
-
436
- def set_cursor(index)
437
- return if index < 0 or index > @data.size
438
-
439
- old_pos = @cursor_pos
440
- index -= 1 if @insert and index == @data.size
441
- index = [ 0, index ].max
442
-
443
- hide_cursor
444
-
445
- @cursor_pos = index
446
- return if @cpl == 0
447
-
448
- y = index / @cpl
449
- if y >= @top_line + @vis_lines
450
- @adj.value = [ y - @vis_lines + 1, @lines - @vis_lines ].min
451
- @adj.value = [ 0, @adj.value ].max
452
- @adj.signal_emit 'value_changed'
453
- elsif y < @top_line
454
- @adj.value = y
455
- @adj.signal_emit 'value_changed'
456
- end
457
-
458
- @lower_nibble = false if index == @data.size
459
-
460
- if @selecting
461
- set_selection(@selection.start, @cursor_pos)
462
- bytes_changed(*[@cursor_pos, old_pos].sort)
463
- else# @selection.start != @selection.end
464
- s, e = [@selection.start, @selection.end].sort
465
- @selection.end = @selection.start = @cursor_pos
466
- bytes_changed(s, e)
467
- end
468
-
469
- self.signal_emit 'cursor_moved'
470
-
471
- bytes_changed(old_pos, old_pos)
472
- show_cursor
473
- end
474
-
475
- def set_cursor_xy(x, y)
476
- pos = y.to_i * @cpl + x.to_i
477
- return if y < 0 or y >= @lines or x < 0 or x >= @cpl or pos > @data.size
478
-
479
- set_cursor(pos)
480
- end
481
-
482
- def set_cursor_on_lower_nibble(bool)
483
- if @selecting
484
- bytes_changed(@cursor_pos, @cursor_pos)
485
- @lower_nibble = bool
486
- elsif @selection.start != @selection.end
487
- s, e = [ @selection.start, @selection.end ].sort
488
-
489
- @selection.start = @selection.end = 0
490
- bytes_changed(s, e)
491
- @lower_nibble = bool
492
- else
493
- hide_cursor
494
- @lower_nibble = bool
495
- show_cursor
496
- end
497
- end
498
-
499
- def set_group_type(type)
500
- hide_cursor
501
- @group_type = type
502
- recalc_displays(self.allocation.width, self.allocation.height)
503
- self.queue_resize
504
- show_cursor
505
- end
506
-
507
- def show_offsets(bool)
508
- return unless @show_offsets ^ bool
509
-
510
- @show_offsets = bool
511
- if bool
512
- show_offsets_widget
513
- else
514
- hide_offsets_widget
515
- end
516
- end
517
-
518
- def set_font(fontname)
519
- @font_desc = Pango::FontDescription.new(fontname)
520
- @disp_font_metrics = load_font(fontname)
521
-
522
- @xdisp.modify_font(@font_desc) if @xdisp
523
- @adisp.modify_font(@font_desc) if @adisp
524
- @offsets.modify_font(@font_desc) if @offsets
525
-
526
- @char_width = get_max_char_width(@disp_font_metrics)
527
- @char_height = Pango.pixels(@disp_font_metrics.ascent) + Pango.pixels(@disp_font_metrics.descent) + 2
528
- recalc_displays(self.allocation.width, self.allocation.height)
529
-
530
- redraw_widget
531
- end
532
-
533
- def set_data(data)
534
- prev_data_size = @data.size
535
- @data = data.dup
536
-
537
- recalc_displays(self.allocation.width, self.allocation.height)
538
-
539
- set_cursor 0
540
- bytes_changed(0, [ prev_data_size, @data.size ].max)
541
- redraw_widget
542
- end
543
-
544
- def validate_highlight(hl)
545
- unless hl.valid
546
- hl.start_line = [ hl.start, hl.end ].min / @cpl - @top_line
547
- hl.end_line = [ hl.start, hl.end ].max / @cpl - @top_line
548
- hl.valid = true
549
- end
550
- end
551
-
552
- def invalidate_highlight(hl)
553
- hl.valid = false
554
- end
555
-
556
- def invalidate_all_highlights
557
- @highlights.each do |hl| invalidate_highlight(hl) end
558
- end
559
-
560
- private
561
-
562
- signal_new(
563
- 'data_changed',
564
- GLib::Signal::RUN_FIRST,
565
- nil,
566
- nil,
567
- String
568
- )
569
-
570
- signal_new(
571
- 'cursor_moved',
572
- GLib::Signal::RUN_FIRST,
573
- nil,
574
- nil
575
- )
576
-
577
- def signal_do_cursor_moved
578
- end
579
-
580
- def signal_do_data_changed(data)
581
- # TODO
582
- end
583
-
584
- def signal_do_realize
585
- super
586
-
587
- self.window.set_back_pixmap(nil, true)
588
- end
589
-
590
- def signal_do_size_allocate(alloc)
591
- hide_cursor
592
-
593
- recalc_displays(alloc.width, alloc.height)
594
-
595
- self.set_allocation(alloc.x, alloc.y, alloc.width, alloc.height)
596
- self.window.move_resize(
597
- alloc.x, alloc.y,
598
- alloc.width, alloc.height
599
- ) if self.realized?
600
-
601
- bw = self.border_width
602
- xt = widget_get_xt
603
- yt = widget_get_yt
604
-
605
- my_alloc = Gtk::Allocation.new(0, 0, 0, 0)
606
- my_alloc.x = bw + xt
607
- my_alloc.y = bw + yt
608
- my_alloc.height = [ alloc.height - 2*bw - 2*yt, 1 ].max
609
- if @show_offsets
610
- my_alloc.width = 8 * @char_width
611
- @offsets.size_allocate(my_alloc)
612
- @offsets.queue_draw
613
- my_alloc.x += 2*xt + my_alloc.width
614
- end
615
-
616
- my_alloc.width = @xdisp_width
617
- @xdisp.size_allocate(my_alloc)
618
-
619
- my_alloc.x = alloc.width - bw - @scrollbar.requisition[0]
620
- my_alloc.y = bw
621
- my_alloc.width = @scrollbar.requisition[0]
622
- my_alloc.height = [ alloc.height - 2*bw, 1 ].max
623
- @scrollbar.size_allocate(my_alloc)
624
-
625
- my_alloc.x -= @adisp_width + xt
626
- my_alloc.y = bw + yt
627
- my_alloc.width = @adisp_width
628
- my_alloc.height = [ alloc.height - 2*bw - 2*yt, 1 ].max
629
- @adisp.size_allocate(my_alloc)
630
-
631
- show_cursor
632
- end
633
-
634
- def signal_do_size_request(req)
635
- sb_width, _sb_height = @scrollbar.size_request
636
- bw = self.border_width
637
- xt, yt = widget_get_xt, widget_get_yt
638
-
639
- width = 4*xt + 2*bw + sb_width +
640
- @char_width*(DEFAULT_CPL + (DEFAULT_CPL-1)/@group_type)
641
-
642
- width += 2*xt + 8*@char_width if @show_offsets
643
-
644
- height = DEFAULT_LINES * @char_height + 2*yt + 2*bw
645
-
646
- req[0] = width
647
- req[1] = height
648
- end
649
-
650
- def signal_do_expose_event(event)
651
- draw_shadow(event.area)
652
- super(event)
653
-
654
- true
655
- end
656
-
657
- def signal_do_key_press_event(event)
658
-
659
- hide_cursor
660
- @selecting = (event.state & Gdk::Window::SHIFT_MASK) != 0
661
- ret = true
662
-
663
- case event.keyval
664
- when Gdk::Keyval::GDK_KP_Tab, Gdk::Keyval::GDK_Tab
665
- @active_view = (@active_view == View::HEX) ? View::ASCII : View::HEX
666
-
667
- when Gdk::Keyval::GDK_Up
668
- set_cursor(@cursor_pos - @cpl)
669
-
670
- when Gdk::Keyval::GDK_Down
671
- set_cursor(@cursor_pos + @cpl)
672
-
673
- when Gdk::Keyval::GDK_Page_Up
674
- set_cursor([0, @cursor_pos - @vis_lines * @cpl].max)
675
-
676
- when Gdk::Keyval::GDK_Page_Down
677
- set_cursor([@cursor_pos + @vis_lines * @cpl, @data.size].min)
678
-
679
- when Gdk::Keyval::GDK_Left
680
- if @active_view == View::HEX
681
- if @selecting
682
- set_cursor(@cursor_pos - 1)
683
- else
684
- @lower_nibble ^= 1
685
- set_cursor(@cursor_pos - 1) if @lower_nibble
686
- end
687
- else
688
- set_cursor(@cursor_pos - 1)
689
- end
690
-
691
- when Gdk::Keyval::GDK_Right
692
- if @active_view == View::HEX
693
- if @selecting
694
- set_cursor(@cursor_pos + 1)
695
- else
696
- @lower_nibble ^= 1
697
- set_cursor(@cursor_pos + 1) unless @lower_nibble
698
- end
699
- else
700
- set_cursor(@cursor_pos + 1)
701
- end
702
-
703
- when Gdk::Keyval::GDK_c, Gdk::Keyval::GDK_C
704
- if event.state & Gdk::Window::CONTROL_MASK != 0
705
- s,e = @selection.start, @selection.end + 1
706
- if @active_view == View::HEX
707
- brk_len = 2 * @cpl + @cpl / @group_type
708
- format_xblock(s,e)
709
- (@disp_buffer.size / brk_len + 1).times do |i| @disp_buffer.insert(i * (brk_len + 1), $/) end
710
- else
711
- brk_len = @cpl
712
- format_ablock(s,e)
713
- end
714
-
715
- @@clipboard.set_text(@disp_buffer)
716
- end
717
- else
718
- ret = false
719
- end
720
-
721
- show_cursor
722
-
723
- ret
724
- end
725
-
726
- def hex_to_pointer(mx, my)
727
- cy = @top_line + my.to_i / @char_height
728
-
729
- cx = x = 0
730
- while cx < 2 * @cpl
731
- x += @char_width
732
-
733
- if x > mx
734
- set_cursor_xy(cx / 2, cy)
735
- set_cursor_on_lower_nibble(cx % 2 != 0)
736
-
737
- cx = 2 * @cpl
738
- end
739
-
740
- cx += 1
741
- x += @char_width if ( cx % (2 * @group_type) == 0 )
742
- end
743
- end
744
-
745
- def ascii_to_pointer(mx, my)
746
- cx = mx / @char_width
747
- cy = @top_line + my / @char_height
748
-
749
- set_cursor_xy(cx, cy)
750
- end
751
-
752
- def load_font(fontname)
753
- desc = Pango::FontDescription.new(fontname)
754
- context = Gdk::Pango.context
755
- context.set_language(Gtk.default_language)
756
-
757
- font = context.load_font(desc)
758
-
759
- font.metrics(context.language)
760
- end
761
-
762
- def draw_shadow(area)
763
- bw = self.border_width
764
- x = bw
765
- xt = widget_get_xt
766
-
767
- if @show_offsets
768
- self.style.paint_shadow(
769
- self.window,
770
- Gtk::STATE_NORMAL, Gtk::SHADOW_IN,
771
- nil, self, nil,
772
- bw, bw,
773
- 8*@char_width + 2*xt, self.allocation.height - 2*bw
774
- )
775
-
776
- x += 8*@char_width + 2*xt
777
- end
778
-
779
- self.style.paint_shadow(
780
- self.window,
781
- Gtk::STATE_NORMAL, Gtk::SHADOW_IN,
782
- nil, self, nil,
783
- x, bw,
784
- @xdisp_width + 2*xt, self.allocation.height - 2*bw
785
- )
786
-
787
- self.style.paint_shadow(
788
- self.window,
789
- Gtk::STATE_NORMAL, Gtk::SHADOW_IN,
790
- nil, self, nil,
791
- self.allocation.width - bw - @adisp_width - @scrollbar.requisition[0] - 2*xt, bw,
792
- @adisp_width + 2*xt, self.allocation.height - 2*bw
793
- )
794
- end
795
-
796
- def redraw_widget
797
- return unless self.realized?
798
-
799
- self.window.invalidate(nil, false)
800
- end
801
-
802
- def widget_get_xt
803
- self.style.xthickness
804
- end
805
-
806
- def widget_get_yt
807
- self.style.ythickness
808
- end
809
-
810
- def recalc_displays(width, height)
811
- old_cpl = @cpl
812
-
813
- w, _h = @scrollbar.size_request
814
- @xdisp_width = 1
815
- @adisp_width = 1
816
-
817
- total_width = width - 2 * self.border_width - 4 * widget_get_xt - w
818
- total_width -= 2 * widget_get_xt + 8 * @char_width if @show_offsets
819
-
820
- total_cpl = total_width / @char_width
821
- if total_cpl == 0 or total_width < 0
822
- @cpl = @lines = @vis_lines = 0
823
- return
824
- end
825
-
826
- @cpl = 0
827
- begin
828
- break if @cpl % @group_type == 0 and total_cpl < @group_type * 3
829
-
830
- @cpl += 1
831
- total_cpl -= 3
832
-
833
- total_cpl -= 1 if @cpl % @group_type == 0
834
- end while total_cpl > 0
835
-
836
- return if @cpl == 0
837
-
838
- if @data.empty?
839
- @lines = 1
840
- else
841
- @lines = @data.size / @cpl
842
- @lines += 1 if @data.size % @cpl != 0
843
- end
844
-
845
- @vis_lines = (height - 2*self.border_width - 2*widget_get_yt).to_i / @char_height.to_i
846
- @adisp_width = @cpl * @char_width + 1
847
- xcpl = @cpl * 2 + (@cpl - 1) / @group_type
848
- @xdisp_width = xcpl * @char_width + 1
849
-
850
- @disp_buffer = ''
851
-
852
- @adj.value = [@top_line * old_cpl / @cpl, @lines - @vis_lines].min
853
- @adj.value = [ 0, @adj.value ].max
854
- if @cursor_pos / @cpl < @adj.value or @cursor_pos / @cpl > @adj.value + @vis_lines - 1
855
- @adj.value = [ @cursor_pos / @cpl, @lines - @vis_lines ].min
856
- @adj.value = [ 0, @adj.value ].max
857
- end
858
-
859
- @adj.lower = 0
860
- @adj.upper = @lines
861
- @adj.step_increment = 1
862
- @adj.page_increment = @vis_lines - 1
863
- @adj.page_size = @vis_lines
864
-
865
- @adj.signal_emit 'changed'
866
- @adj.signal_emit 'value_changed'
867
- end
868
-
869
- def get_max_char_width(metrics)
870
- layout = self.create_pango_layout('')
871
- layout.set_font_description(@font_desc)
872
- char_widths = [ 0 ]
873
-
874
- (1..100).each do |i|
875
- logical_rect = Pango::Rectangle.new(0, 0, 0, 0)
876
- if is_displayable(i.chr)
877
- layout.set_text(i.chr)
878
- logical_rect = layout.pixel_extents[1]
879
- end
880
- char_widths << logical_rect.width
881
- end
882
-
883
- char_widths[48..122].max
884
- end
885
-
886
- def show_cursor
887
- unless @cursor_shown
888
- if @xdisp_gc and @adisp_gc and @xdisp.realized? and @adisp.realized?
889
- render_xc
890
- render_ac
891
- end
892
-
893
- @cursor_shown = true
894
- end
895
- end
896
-
897
- def hide_cursor
898
- if @cursor_shown
899
- if @xdisp_gc and @adisp_gc and @xdisp.realized? and @adisp.realized?
900
- render_byte(@cursor_pos)
901
- end
902
-
903
- @cursor_shown = false
904
- end
905
- end
906
-
907
- def show_offsets_widget
908
- @offsets = DrawingArea.new
909
- @offsets.modify_font @font_desc
910
- @olayout = @offsets.create_pango_layout('')
911
-
912
- @offsets.events = Gdk::Event::EXPOSURE_MASK
913
- @offsets.signal_connect 'expose_event' do |_offsets, event|
914
- imin = (event.area.y / @char_height).to_i
915
- imax = ((event.area.y + event.area.height) / @char_height).to_i
916
- imax += 1 if (event.area.y + event.area.height).to_i % @char_height != 0
917
-
918
- imax = [ imax, @vis_lines ].min
919
-
920
- render_offsets(imin, imax)
921
- end
922
-
923
- put @offsets, 0, 0
924
- @offsets.show
925
- end
926
-
927
- def hide_offsets_widget
928
- if @offsets
929
- self.remove(@offsets)
930
- @offsets = @offsets_gc = nil
931
- end
932
- end
933
-
934
- def is_displayable(c)
935
- c = c.ord
936
- c >= 0x20 and c < 0x7f
937
- end
938
-
939
- def bytes_changed(s, e)
940
- start_line = s / @cpl - @top_line
941
- end_line = e / @cpl - @top_line
942
-
943
- return if end_line < 0 or start_line > @vis_lines
944
-
945
- start_line = [ 0, start_line ].max
946
-
947
- render_hex_lines(start_line, end_line)
948
- render_ascii_lines(start_line, end_line)
949
- render_offsets(start_line, end_line) if @show_offsets
950
- end
951
-
952
- def render_hex_highlights(cursor_line)
953
- xcpl = @cpl * 2 + @cpl / @group_type
954
-
955
- @highlights.each do |hl|
956
- next if (hl.start - hl.end).abs < hl.min_select
957
-
958
- validate_highlight(hl)
959
-
960
- s, e = [ hl.start, hl.end ].sort
961
- sl, el = hl.start_line, hl.end_line
962
-
963
- hl.style.attach(@xdisp.window) if hl.style
964
- state = (@active_view == View::HEX) ? Gtk::STATE_SELECTED : Gtk::STATE_INSENSITIVE
965
-
966
- if cursor_line == sl
967
- cursor_off = 2 * (s % @cpl) + (s % @cpl) / @group_type
968
- if cursor_line == el
969
- len = 2 * (e % @cpl + 1) + (e % @cpl) / @group_type
970
- else
971
- len = xcpl
972
- end
973
-
974
- len -= cursor_off
975
- (hl.style || self.style).paint_flat_box(
976
- @xdisp.window,
977
- state, Gtk::SHADOW_NONE,
978
- nil, @xdisp, '',
979
- cursor_off * @char_width, cursor_line * @char_height,
980
- len * @char_width, @char_height
981
- ) if len > 0
982
-
983
- elsif cursor_line == el
984
- cursor_off = 2 * (e % @cpl + 1) + (e % @cpl) / @group_type
985
- (hl.style || self.style).paint_flat_box(
986
- @xdisp.window,
987
- state, Gtk::SHADOW_NONE,
988
- nil, @xdisp, '',
989
- 0, cursor_line * @char_height,
990
- cursor_off * @char_width, @char_height
991
- ) if cursor_off > 0
992
-
993
- elsif cursor_line > sl and cursor_line < el
994
- (hl.style || self.style).paint_flat_box(
995
- @xdisp.window,
996
- state, Gtk::SHADOW_NONE,
997
- nil, @xdisp, '',
998
- 0, cursor_line * @char_height,
999
- xcpl * @char_width, @char_height
1000
- )
1001
- end
1002
-
1003
- hl.style.attach(@adisp.window) if hl.style
1004
- end
1005
- end
1006
-
1007
- def render_hex_lines(imin, imax)
1008
- return unless self.realized? and @cpl != 0
1009
-
1010
- cursor_line = @cursor_pos / @cpl - @top_line
1011
-
1012
- @xdisp_gc.set_foreground(self.style.base(Gtk::STATE_NORMAL))
1013
- @xdisp.window.draw_rectangle(
1014
- @xdisp_gc,
1015
- true,
1016
- 0,
1017
- imin * @char_height,
1018
- @xdisp.allocation.width,
1019
- (imax - imin + 1) * @char_height
1020
- )
1021
-
1022
- imax = [ imax, @vis_lines, @lines ].min
1023
-
1024
- @xdisp_gc.set_foreground(self.style.text(Gtk::STATE_NORMAL))
1025
-
1026
- frm_len = format_xblock((@top_line+imin) * @cpl, [(@top_line+imax+1) * @cpl, @data.size].min)
1027
-
1028
- tmp = nil
1029
- xcpl = @cpl*2 + @cpl/@group_type
1030
- (imin..imax).each do |i|
1031
- return unless (tmp = frm_len - ((i - imin) * xcpl)) > 0
1032
-
1033
- render_hex_highlights(i)
1034
- text = @disp_buffer[(i-imin) * xcpl, [xcpl, tmp].min]
1035
- @xlayout.set_text(text)
1036
- @xdisp.window.draw_layout(@xdisp_gc, 0, i * @char_height, @xlayout)
1037
- end
1038
-
1039
- render_xc if cursor_line >= imin and cursor_line <= imax and @cursor_shown
1040
- end
1041
-
1042
- def render_ascii_highlights(cursor_line)
1043
- @highlights.each do |hl|
1044
- next if (hl.start - hl.end).abs < hl.min_select
1045
-
1046
- validate_highlight(hl)
1047
-
1048
- s, e = [ hl.start, hl.end ].sort
1049
- sl, el = hl.start_line, hl.end_line
1050
-
1051
- hl.style.attach(@adisp.window) if hl.style
1052
- state = (@active_view == View::ASCII) ? Gtk::STATE_SELECTED : Gtk::STATE_INSENSITIVE
1053
-
1054
- if cursor_line == sl
1055
- cursor_off = s % @cpl
1056
- len =
1057
- if cursor_line == el
1058
- e - s + 1
1059
- else
1060
- @cpl - cursor_off
1061
- end
1062
-
1063
- (hl.style || self.style).paint_flat_box(
1064
- @adisp.window,
1065
- state, Gtk::SHADOW_NONE,
1066
- nil, @adisp, '',
1067
- cursor_off * @char_width, cursor_line * @char_height,
1068
- len * @char_width, @char_height
1069
- ) if len > 0
1070
-
1071
- elsif cursor_line == el
1072
- cursor_off = e % @cpl + 1
1073
- (hl.style || self.style).paint_flat_box(
1074
- @adisp.window,
1075
- state, Gtk::SHADOW_NONE,
1076
- nil, @adisp, '',
1077
- 0, cursor_line * @char_height,
1078
- cursor_off * @char_width, @char_height
1079
- ) if cursor_off > 0
1080
-
1081
- elsif cursor_line > sl and cursor_line < el
1082
- (hl.style || self.style).paint_flat_box(
1083
- @adisp.window,
1084
- state, Gtk::SHADOW_NONE,
1085
- nil, @adisp, '',
1086
- 0, cursor_line * @char_height,
1087
- @cpl * @char_width, @char_height
1088
- )
1089
- end
1090
-
1091
- hl.style.attach(@adisp.window) if hl.style
1092
- end
1093
- end
1094
-
1095
- def render_ascii_lines(imin, imax)
1096
- return unless self.realized? and @cpl != 0
1097
-
1098
- cursor_line = @cursor_pos / @cpl - @top_line
1099
-
1100
- @adisp_gc.set_foreground(self.style.base(Gtk::STATE_NORMAL))
1101
- @adisp.window.draw_rectangle(
1102
- @adisp_gc,
1103
- true,
1104
- 0,
1105
- imin * @char_height,
1106
- @adisp.allocation.width,
1107
- (imax - imin + 1) * @char_height
1108
- )
1109
-
1110
- imax = [ imax, @vis_lines, @lines ].min
1111
-
1112
- @adisp_gc.set_foreground(self.style.text(Gtk::STATE_NORMAL))
1113
-
1114
- frm_len = format_ablock((@top_line+imin) * @cpl, [(@top_line+imax+1) * @cpl, @data.size].min)
1115
-
1116
- tmp = nil
1117
- (imin..imax).each do |i|
1118
- return unless (tmp = frm_len - ((i - imin) * @cpl)) > 0
1119
-
1120
- render_ascii_highlights(i)
1121
- text = @disp_buffer[(i-imin) * @cpl, [@cpl, tmp].min]
1122
- @alayout.set_text(text)
1123
- @adisp.window.draw_layout(@adisp_gc, 0, i * @char_height, @alayout)
1124
- end
1125
-
1126
- render_ac if cursor_line >= imin and cursor_line <= imax and @cursor_shown
1127
- end
1128
-
1129
- def render_offsets(imin, imax)
1130
- return unless self.realized?
1131
-
1132
- unless @offsets_gc
1133
- @offsets_gc = Gdk::GC.new(@offsets.window)
1134
- @offsets_gc.set_exposures(true)
1135
- end
1136
-
1137
- @offsets_gc.set_foreground(self.style.base(Gtk::STATE_INSENSITIVE))
1138
- @offsets.window.draw_rectangle(
1139
- @offsets_gc,
1140
- true,
1141
- 0, imin * @char_height,
1142
- @offsets.allocation.width, (imax - imin + 1) * @char_height
1143
- )
1144
-
1145
- imax = [ imax, @vis_lines, @lines - @top_line - 1 ].min
1146
- @offsets_gc.set_foreground(self.style.text(Gtk::STATE_NORMAL))
1147
-
1148
- (imin..imax).each do |i|
1149
- text = "%08x" % ((@top_line + i) * @cpl + @starting_offset)
1150
- @olayout.set_text(text)
1151
- @offsets.window.draw_layout(@offsets_gc, 0, i * @char_height, @olayout)
1152
- end
1153
- end
1154
-
1155
- def render_byte(pos)
1156
- return unless @xdisp_gc and @adisp_gc and @xdisp.realized? and @adisp.realized?
1157
-
1158
- return unless (coords = get_xcoords(pos))
1159
- cx, cy = coords
1160
- c = format_xbyte(pos)
1161
-
1162
- @xdisp_gc.set_foreground(self.style.base(Gtk::STATE_NORMAL))
1163
- @xdisp.window.draw_rectangle(
1164
- @xdisp_gc,
1165
- true,
1166
- cx, cy,
1167
- 2 * @char_width, @char_height
1168
- )
1169
-
1170
- if pos < @data.size
1171
- @xdisp_gc.set_foreground(self.style.text(Gtk::STATE_NORMAL))
1172
- @xlayout.set_text(c)
1173
- @xdisp.window.draw_layout(@xdisp_gc, cx, cy, @xlayout)
1174
- end
1175
-
1176
- return unless (coords = get_acoords(pos))
1177
- cx, cy = coords
1178
-
1179
- @adisp_gc.set_foreground(self.style.base(Gtk::STATE_NORMAL))
1180
- @adisp.window.draw_rectangle(
1181
- @adisp_gc,
1182
- true,
1183
- cx, cy,
1184
- @char_width, @char_height
1185
- )
1186
-
1187
- if pos < @data.size
1188
- @adisp_gc.set_foreground(self.style.text(Gtk::STATE_NORMAL))
1189
- c = get_byte(pos)
1190
- c = '.' unless is_displayable(c)
1191
-
1192
- @alayout.set_text(c)
1193
- @adisp.window.draw_layout(@adisp_gc, cx, cy, @alayout)
1194
- end
1195
- end
1196
-
1197
- def render_xc
1198
- return unless @xdisp.realized?
1199
-
1200
- if coords = get_xcoords(@cursor_pos)
1201
- cx, cy = coords
1202
-
1203
- c = format_xbyte(@cursor_pos)
1204
- if @lower_nibble
1205
- cx += @char_width
1206
- c = c[1,1]
1207
- else
1208
- c = c[0,1]
1209
- end
1210
-
1211
- @xdisp_gc.set_foreground(self.style.base(Gtk::STATE_ACTIVE))
1212
- @xdisp.window.draw_rectangle(
1213
- @xdisp_gc,
1214
- (@active_view == View::HEX),
1215
- cx, cy,
1216
- @char_width,
1217
- @char_height - 1
1218
- )
1219
- @xdisp_gc.set_foreground(self.style.text(Gtk::STATE_ACTIVE))
1220
- @xlayout.set_text(c)
1221
- @xdisp.window.draw_layout(@xdisp_gc, cx, cy, @xlayout)
1222
- end
1223
- end
1224
-
1225
- def render_ac
1226
- return unless @adisp.realized?
1227
-
1228
- if coords = get_acoords(@cursor_pos)
1229
- cx, cy = coords
1230
-
1231
- c = get_byte(@cursor_pos)
1232
- c = '.' unless is_displayable(c)
1233
-
1234
- @adisp_gc.set_foreground(self.style.base(Gtk::STATE_ACTIVE))
1235
- @adisp.window.draw_rectangle(
1236
- @adisp_gc,
1237
- (@active_view == View::ASCII),
1238
- cx, cy,
1239
- @char_width,
1240
- @char_height - 1
1241
- )
1242
- @adisp_gc.set_foreground(self.style.text(Gtk::STATE_ACTIVE))
1243
- @alayout.set_text(c)
1244
- @adisp.window.draw_layout(@adisp_gc, cx, cy, @alayout)
1245
- end
1246
- end
1247
-
1248
- def get_xcoords(pos)
1249
- return nil if @cpl == 0
1250
-
1251
- cy = pos / @cpl - @top_line
1252
- return nil if cy < 0
1253
-
1254
- cx = 2 * (pos % @cpl)
1255
- spaces = (pos % @cpl) / @group_type
1256
-
1257
- cx *= @char_width
1258
- cy *= @char_height
1259
- spaces *= @char_width
1260
-
1261
- [cx + spaces, cy]
1262
- end
1263
-
1264
- def get_acoords(pos)
1265
- return nil if @cpl == 0
1266
-
1267
- cy = pos / @cpl - @top_line
1268
- return nil if cy < 0
1269
-
1270
- cy *= @char_height
1271
- cx = @char_width * (pos % @cpl)
1272
-
1273
- [cx, cy]
1274
- end
1275
-
1276
- def format_xblock(s, e)
1277
- @disp_buffer = ''
1278
-
1279
- (s+1..e).each do |i|
1280
- @disp_buffer << get_byte(i - 1).unpack('H2')[0]
1281
- @disp_buffer << ' ' if i % @group_type == 0
1282
- end
1283
-
1284
- @disp_buffer.size
1285
- end
1286
-
1287
- def format_ablock(s, e)
1288
- @disp_buffer = ''
1289
-
1290
- (s..e-1).each do |i|
1291
- c = get_byte(i)
1292
- c = '.' unless is_displayable(c)
1293
- @disp_buffer << c
1294
- end
1295
-
1296
- @disp_buffer.size
1297
- end
1298
-
1299
- def get_byte(offset)
1300
- if offset >= 0 and offset < @data.size
1301
- @data[offset, 1]
1302
- else
1303
- 0.chr
1304
- end
1305
- end
1306
-
1307
- def format_xbyte(pos)
1308
- get_byte(pos).unpack('H2')[0]
1309
- end
1310
- end
1311
- end
1312
-
1313
- __END__
1314
- hexedit = Gtk::HexEditor.new(File.read '/bin/cat')
1315
- hexedit.show_offsets(true)
1316
- hexedit.set_cursor 2
1317
- hexedit.set_cursor_on_lower_nibble true
1318
- hexedit.set_font 'Terminus 12'
1319
- hexedit.set_group_type Gtk::HexEditor::Group::LONG
1320
-
1321
- window = Gtk::Window.new
1322
- window.add(hexedit)
1323
-
1324
- window.show_all
1325
-
1326
- Gtk.main