root/branch/PacketManipulator/widgets/HexView.py @ 3311

Revision 3311, 15.0 kB (checked in by nopper, 5 years ago)

Implemented the block selection in the hexview

Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (C) 2008 Adriano Monteiro Marques
4#
5# Author: Francesco Piccinno <stack.box@gmail.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21import gtk
22import pango
23import gobject
24
25class BaseText(gtk.TextView):
26    __gtype_name__ = "BaseText"
27
28    def __init__(self, parent):
29        self.buffer = gtk.TextBuffer(parent.table)
30        gtk.TextView.__init__(self, self.buffer)
31
32        self._parent = parent
33        self.modify_font(pango.FontDescription(parent.font))
34        self.set_editable(False)
35
36gobject.type_register(BaseText)
37
38class OffsetText(BaseText):
39    def __init__(self, parent):
40        BaseText.__init__(self, parent)
41
42        self.off_len = 1
43        self.connect('button-press-event', self.__on_button_press)
44        self.connect('size-request', self.__on_size_request)
45        self.connect('realize', self.__on_realize)
46
47        self.set_cursor_visible(False)
48
49    def __on_button_press(self, widget, evt):
50        return True
51
52    def __on_realize(self, widget):
53        self.modify_base(gtk.STATE_NORMAL, self.style.dark[gtk.STATE_NORMAL])
54
55    def render(self, txt):
56        self.buffer.set_text('')
57
58        bpl = self._parent.bpl
59        tot_lines = len(txt) / bpl
60
61        if len(txt) % bpl != 0:
62            tot_lines += 1
63
64        self.off_len = len(str(tot_lines)) + 1
65        output = []
66
67        for i in xrange(tot_lines):
68            output.append(("%0" + str(self.off_len) + "d") % i)
69
70        if output:
71            self.buffer.insert_with_tags(
72                self.buffer.get_end_iter(),
73                "\n".join(output),
74                self._parent.tag_offset
75            )
76
77    def __on_size_request(self, widget, alloc):
78        ctx = self.get_pango_context()
79        font = ctx.load_font(pango.FontDescription(self._parent.font))
80        metric = font.get_metrics(ctx.get_language())
81
82        w = pango.PIXELS(metric.get_approximate_char_width()) * (self.off_len + 1)
83        w += 2
84
85        if alloc.width < w:
86            alloc.width = w
87
88class AsciiText(BaseText):
89    _printable = """0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
90
91    def __init__(self, parent):
92        BaseText.__init__(self, parent)
93        self.connect('size-request', self.__on_size_request)
94
95        self.prev_start = None
96        self.prev_end = None
97
98    def render(self, txt):
99        self.buffer.set_text('')
100
101        bpl = self._parent.bpl
102        tot_lines = len(txt) / bpl
103
104        if len(txt) % bpl != 0:
105            tot_lines += 1
106
107        output = []
108
109        convert = lambda i: "".join(
110            map(lambda x: (x in AsciiText._printable) and (x) or ('.'), list(i)))
111
112        for i in xrange(tot_lines):
113            to_fill = 0
114
115            if i * bpl + bpl > len(txt):
116                to_fill = (i * bpl) + bpl - len(txt)
117                output.append(
118                    convert(txt[i * bpl:])
119                )
120            else:
121                output.append(
122                    convert(txt[i * bpl:(i * bpl) + bpl])
123                )
124
125        if output:
126            self.buffer.insert_with_tags(
127                self.buffer.get_end_iter(),
128                "\n".join(output),
129                self._parent.tag_ascii
130            )
131
132    def __on_size_request(self, widget, alloc):
133        ctx = self.get_pango_context()
134        font = ctx.load_font(pango.FontDescription(self._parent.font))
135        metric = font.get_metrics(ctx.get_language())
136
137        w = pango.PIXELS(metric.get_approximate_char_width()) * self._parent.bpl
138        w += 2
139
140        if alloc.width < w:
141            alloc.width = w
142
143    def select_blocks(self, start=None, end=None):
144        if self.prev_start and self.prev_end and self.prev_start != self.prev_end:
145            self.buffer.remove_tag(self._parent.tag_sec_sel, self.prev_start, self.prev_end)
146
147        if not start and not end:
148            return
149
150        start_iter = self.buffer.get_iter_at_line(start / self._parent.bpl)
151        start_iter.forward_chars(start % self._parent.bpl)
152
153        end_iter = self.buffer.get_iter_at_line(end / self._parent.bpl)
154        end_iter.forward_chars(end % self._parent.bpl)
155
156        self.buffer.apply_tag(self._parent.tag_sec_sel, start_iter, end_iter)
157        self.prev_start, self.prev_end = start_iter, end_iter
158
159
160class HexText(BaseText):
161    def __init__(self, parent):
162        BaseText.__init__(self, parent)
163        self.connect('size-request', self.__on_size_request)
164        self.connect('realize', self.__on_realize)
165
166        self.prev_start = None
167        self.prev_end = None
168
169    def __on_realize(self, widget):
170        self.modify_base(gtk.STATE_NORMAL, self.style.mid[gtk.STATE_NORMAL])
171
172    def render(self, txt):
173        self.buffer.set_text('')
174
175        bpl = self._parent.bpl
176        tot_lines = len(txt) / bpl
177
178        if len(txt) % bpl != 0:
179            tot_lines += 1
180
181        output = []
182        convert = lambda x: (len(x) == 1) and ("0%s" % x) or (x)
183
184        for i in xrange(tot_lines):
185            to_fill = 0
186
187            if i * bpl + bpl > len(txt):
188                to_fill = (i * bpl) + bpl - len(txt)
189                output.append(
190                    " ".join(map(lambda x: convert(str(hex(ord(x)))[2:]), txt[i * bpl:]))
191                )
192            else:
193                output.append(
194                    " ".join(map(lambda x: convert(str(hex(ord(x)))[2:]), txt[i * bpl:(i * bpl) + bpl]))
195                )
196
197        if output:
198            self.buffer.insert_with_tags(
199                self.buffer.get_end_iter(),
200                "\n".join(output).upper(),
201                self._parent.tag_hex
202            )
203
204    def __on_size_request(self, widget, alloc):
205        ctx = self.get_pango_context()
206        font = ctx.load_font(pango.FontDescription(self._parent.font))
207        metric = font.get_metrics(ctx.get_language())
208
209        w = pango.PIXELS(metric.get_approximate_char_width()) * (self._parent.bpl * 3 - 1)
210        w += 2
211
212        if alloc.width < w:
213            alloc.width = w
214
215    def select_blocks(self, start=None, end=None):
216        if self.prev_start and self.prev_end and self.prev_start != self.prev_end:
217            self.buffer.remove_tag(self._parent.tag_sec_sel, self.prev_start, self.prev_end)
218
219        if not start and not end:
220            return
221
222        start_iter = self.buffer.get_iter_at_line(start / self._parent.bpl)
223        start_iter.forward_chars(3 * (start % self._parent.bpl))
224
225        end_iter = self.buffer.get_iter_at_line(end / self._parent.bpl)
226        end_iter.forward_chars(3 * (end % self._parent.bpl) - 1)
227
228        self.buffer.apply_tag(self._parent.tag_sec_sel, start_iter, end_iter)
229        self.prev_start, self.prev_end = start_iter, end_iter
230
231
232class HexView(gtk.HBox):
233    __gtype_name__ = "HexView"
234
235    def __init__(self):
236        gtk.HBox.__init__(self, False, 4)
237        self.set_border_width(4)
238
239        self.table = gtk.TextTagTable()
240        self.tag_offset = gtk.TextTag('hex-o-view')       # offset view
241        self.tag_hex = gtk.TextTag('hex-x-view')          # hex view
242        self.tag_ascii = gtk.TextTag('hex-a-view')        # ascii view
243        self.tag_sec_sel = gtk.TextTag('hex-s-selection') # secondary selection
244
245        self.table.add(self.tag_offset)
246        self.table.add(self.tag_hex)
247        self.table.add(self.tag_ascii)
248        self.table.add(self.tag_sec_sel)
249
250        self._bpl = 16
251        self._font = "Monospace 10"
252        self._payload = ""
253
254        vadj, hadj = gtk.Adjustment(), gtk.Adjustment()
255        self.vscroll = gtk.VScrollbar(vadj)
256
257        self.offset_text = OffsetText(self)
258        self.hex_text = HexText(self)
259        self.ascii_text = AsciiText(self)
260
261        self.offset_text.set_scroll_adjustments(hadj, vadj)
262        self.hex_text.set_scroll_adjustments(hadj, vadj)
263        self.ascii_text.set_scroll_adjustments(hadj, vadj)
264
265        self.hex_text.buffer.connect('mark-set', self.__on_hex_change)
266        self.ascii_text.buffer.connect('mark-set', self.__on_ascii_change)
267
268        self.hex_text.connect('populate-popup', self.__on_menu_popup)
269        self.ascii_text.connect('populate-popup', self.__on_menu_popup)
270
271        def scroll(widget):
272            widget.set_size_request(-1, 80)
273            frame = gtk.Frame()
274            frame.set_shadow_type(gtk.SHADOW_IN)
275            frame.add(widget)
276            return frame
277
278        self.pack_start(scroll(self.offset_text), False, False)
279        self.pack_start(scroll(self.hex_text), False, False)
280        self.pack_start(scroll(self.ascii_text), False, False)
281        self.pack_end(self.vscroll, False, False)
282
283    def do_realize(self):
284        gtk.HBox.do_realize(self)
285
286        # Offset view
287        self.tag_offset.set_property('weight', pango.WEIGHT_BOLD)
288
289        # Hex View
290        self.tag_hex.set_property(
291            'background-gdk',
292            self.style.mid[gtk.STATE_NORMAL]
293        )
294
295        # Selection tags
296        self.tag_sec_sel.set_property(
297            'background-gdk',
298            self.style.text_aa[gtk.STATE_NORMAL]
299        )
300
301    def __on_menu_popup(self, widget, menu):
302        item = gtk.SeparatorMenuItem()
303        item.show()
304       
305        menu.append(item)
306
307        action = gtk.Action('', 'Copy from both', '', gtk.STOCK_COPY)
308        item = action.create_menu_item()
309        item.connect('activate', self.__on_copy_from_both)
310       
311        menu.append(item)
312
313    def __on_copy_from_both(self, widget):
314        if self.hex_text.buffer.get_selection_bounds():
315            # Hex active
316
317            start, end = self.hex_text.buffer.get_selection_bounds()
318            txt = self.hex_text.buffer.get_text(start, end).replace("\n", " ")
319
320            hex_s = filter(lambda x: len(x) == 2, txt.split(" "))
321            ascii = map(lambda x: chr(int(x, 16)), hex_s)
322
323            extend = self.bpl - len(hex_s) % self.bpl
324
325            hex_s.extend(["  "] * extend)
326            ascii.extend([" "] * extend)
327
328            output = ""
329
330            for i in xrange(len(hex_s) / self.bpl):
331                output += "%s %s\n" % (
332                    " ".join(hex_s[i * self.bpl:(i * self.bpl) + self.bpl]),
333                    "".join(ascii[i * self.bpl:(i * self.bpl) + self.bpl])
334                )
335
336        elif self.ascii_text.buffer.get_selection_bounds():
337            # Ascii active
338
339            start, end = self.ascii_text.buffer.get_selection_bounds()
340            ascii = list(self.ascii_text.buffer.get_text(start, end).replace("\n", ""))
341            hex_s = map(lambda x: str(hex(ord(x)))[2:], ascii)
342
343            extend = self.bpl - len(ascii) % self.bpl
344
345            hex_s.extend(["  "] * extend)
346            ascii.extend([" "] * extend)
347
348            output = ""
349
350            for i in xrange(len(hex_s) / self.bpl):
351                output += "%s %s\n" % (
352                    " ".join(hex_s[i * self.bpl:i * self.bpl + self.bpl]),
353                    "".join(ascii[i * self.bpl:i * self.bpl + self.bpl])
354                )
355
356    def __on_hex_change(self, buffer, iter, mark):
357        if not self.hex_text.buffer.get_selection_bounds():
358            self.ascii_text.select_blocks() # Deselect
359            return
360
361        start, end = buffer.get_selection_bounds()
362
363        if start.get_line() == end.get_line():
364            tmp = buffer.get_iter_at_line(start.get_line())
365            txt = buffer.get_text(tmp, start)
366            s_off = len(filter(lambda x: x != '', txt.split(" ")))
367
368            txt = buffer.get_text(start, end)
369            e_off = len(filter(lambda x: len(x) == 2, txt.split(" ")))
370
371            self.ascii_text.select_blocks(
372                (self.bpl * start.get_line()) + s_off,
373                (self.bpl * start.get_line()) + s_off + e_off
374            )
375        else:
376            tmp = buffer.get_iter_at_line(start.get_line())
377            txt = buffer.get_text(tmp, start)
378            s_off = len(filter(lambda x: x != '', txt.split(" ")))
379
380            tmp = buffer.get_iter_at_line(end.get_line())
381            txt = buffer.get_text(tmp, end)
382            e_off = len(filter(lambda x: len(x) == 2, txt.split(" ")))
383
384            self.ascii_text.select_blocks(
385                (self.bpl * start.get_line()) + s_off,
386                (self.bpl * end.get_line()) + e_off
387            )
388
389    def __on_ascii_change(self, buffer, iter, mark):
390        if not self.ascii_text.buffer.get_selection_bounds():
391            self.hex_text.select_blocks() # Deselect
392            return
393
394        start, end = self.ascii_text.buffer.get_selection_bounds()
395        self.hex_text.select_blocks(
396            (self.bpl * start.get_line()) + start.get_line_index(),
397            (self.bpl * end.get_line()) + end.get_line_index()
398        )
399
400    def select_block(self, offset, len, ascii=True):
401        """
402        Select a block of data in the HexView
403
404        @param offset the offset byte
405        @param len the lenght of selection
406        @param ascii True to set primary selection on ASCII otherwise on HEX
407        """
408
409        start = offset
410        end = offset + len
411
412        if start > end:
413            start, end = end, start
414
415        if ascii:
416            # We need to get a fucking iter!
417            buffer = self.ascii_text.get_buffer()
418            start_iter = buffer.get_iter_at_offset(start)
419            end_iter   = buffer.get_iter_at_offset(end)
420
421            buffer.select_range(end_iter, start_iter)
422
423    def get_payload(self):
424        return self._payload
425    def set_payload(self, val):
426        self._payload = val
427
428        for view in (self.offset_text, self.hex_text, self.ascii_text):
429           
430            # Invalidate previous iters
431            if hasattr(view, 'prev_start'):
432                view.prev_start = None
433            if hasattr(view, 'prev_end'):
434                view.prev_end = None
435
436            view.render(self._payload)
437
438    def get_font(self):
439        return self._font
440    def modify_font(self, val):
441        try:
442            desc = pango.FontDescription(val)
443            self._font = val
444
445            for view in (self.offset_text, self.hex_text, self.ascii_text):
446                view.modify_font(desc)
447        except Exception:
448            pass
449
450    def get_bpl(self):
451        return self._bpl
452    def set_bpl(self, val):
453        self._bpl = val
454
455        # Redraw!
456        self.payload = self.payload
457
458    payload = property(get_payload, set_payload)
459    font = property(get_font, modify_font)
460    bpl = property(get_bpl, set_bpl)
461
462gobject.type_register(HexView)
463
464if __name__ == "__main__":
465    w = gtk.Window()
466    view = HexView()
467    view.show_payload("Woo welcome this is a simple read/only"
468                      " HexView widget for PacketManipulator")
469    w.add(view)
470    w.show_all()
471    w.connect('delete-event', lambda *w: gtk.main_quit())
472    gtk.main()
Note: See TracBrowser for help on using the browser.