origami 2.0.4 → 2.1.0

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.
@@ -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