Initial import
[samba] / source / python / gtkdictbrowser.py
diff --git a/source/python/gtkdictbrowser.py b/source/python/gtkdictbrowser.py
new file mode 100755 (executable)
index 0000000..dd8bed8
--- /dev/null
@@ -0,0 +1,272 @@
+#!/usr/bin/python
+#
+# Browse a Python dictionary in a two pane graphical interface written
+# in GTK.
+#
+# The GtkDictBrowser class is supposed to be generic enough to allow
+# applications to override enough methods and produce a
+# domain-specific browser provided the information is presented as a
+# Python dictionary.
+#
+# Possible applications:
+#
+#   - Windows registry browser
+#   - SPOOLSS printerdata browser
+#   - tdb file browser
+#
+
+from gtk import *
+import string, re
+
+class GtkDictBrowser:
+
+    def __init__(self, dict):
+        self.dict = dict
+        
+        # This variable stores a list of (regexp, function) used to
+        # convert the raw value data to a displayable string.
+
+        self.get_value_text_fns = []
+        self.get_key_text = lambda x: x
+
+        # We can filter the list of keys displayed using a regex
+
+        self.filter_regex = ""
+
+    # Create and configure user interface widgets.  A string argument is
+    # used to set the window title.
+
+    def build_ui(self, title):
+        win = GtkWindow()
+        win.set_title(title)
+
+        win.connect("destroy", mainquit)
+
+        hpaned = GtkHPaned()
+        win.add(hpaned)
+        hpaned.set_border_width(5)
+        hpaned.show()
+
+        vbox = GtkVBox()
+        hpaned.add1(vbox)
+        vbox.show()
+
+        scrolled_win = GtkScrolledWindow()
+        scrolled_win.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        vbox.pack_start(scrolled_win)
+        scrolled_win.show()
+
+        hbox = GtkHBox()
+        vbox.pack_end(hbox, expand = 0, padding = 5)
+        hbox.show()
+
+        label = GtkLabel("Filter:")
+        hbox.pack_start(label, expand = 0, padding = 5)
+        label.show()
+
+        self.entry = GtkEntry()
+        hbox.pack_end(self.entry, padding = 5)
+        self.entry.show()
+
+        self.entry.connect("activate", self.filter_activated)
+        
+        self.list = GtkList()
+        self.list.set_selection_mode(SELECTION_MULTIPLE)
+        self.list.set_selection_mode(SELECTION_BROWSE)
+        scrolled_win.add_with_viewport(self.list)
+        self.list.show()
+
+        self.list.connect("select_child", self.key_selected)
+
+        scrolled_win = GtkScrolledWindow()
+        scrolled_win.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        hpaned.add2(scrolled_win)
+        scrolled_win.set_usize(500,400)
+        scrolled_win.show()
+        
+        self.text = GtkText()
+        self.text.set_editable(FALSE)
+        scrolled_win.add_with_viewport(self.text)
+        self.text.show()
+
+        self.text.connect("event", self.event_handler)
+
+        self.menu = GtkMenu()
+        self.menu.show()
+
+        self.font = load_font("fixed")
+
+        self.update_keylist()
+
+        win.show()
+
+    # Add a key to the left hand side of the user interface
+
+    def add_key(self, key):
+        display_key = self.get_key_text(key)
+        list_item = GtkListItem(display_key)
+        list_item.set_data("raw_key", key) # Store raw key in item data
+        self.list.add(list_item)
+        list_item.show()
+
+    # Event handler registered by build_ui()
+
+    def event_handler(self, event, menu):
+        return FALSE
+
+    # Set the text to appear in the right hand side of the user interface 
+
+    def set_value_text(self, item):
+
+        # Clear old old value in text window
+
+        self.text.delete_text(0, self.text.get_length())
+        
+        if type(item) == str:
+
+            # The text widget has trouble inserting text containing NULL
+            # characters.
+            
+            item = string.replace(item, "\x00", ".")
+            
+            self.text.insert(self.font, None, None, item)
+
+        else:
+
+            # A non-text item
+            
+            self.text.insert(self.font, None, None, repr(item))
+            
+    # This function is called when a key is selected in the left hand side
+    # of the user interface.
+
+    def key_selected(self, list, list_item):
+        key = list_item.children()[0].get()
+
+        # Look for a match in the value display function list
+
+        text = self.dict[list_item.get_data("raw_key")]
+
+        for entry in self.get_value_text_fns:
+            if re.match(entry[0], key):
+                text = entry[1](text)
+                break
+
+        self.set_value_text(text)
+
+    # Refresh the key list by removing all items and re-inserting them.
+    # Items are only inserted if they pass through the filter regexp.
+
+    def update_keylist(self):
+        self.list.remove_items(self.list.children())
+        self.set_value_text("")
+        for k in self.dict.keys():
+            if re.match(self.filter_regex, k):
+                self.add_key(k)
+
+    # Invoked when the user hits return in the filter text entry widget.
+
+    def filter_activated(self, entry):
+        self.filter_regex = entry.get_text()
+        self.update_keylist()
+
+    # Register a key display function
+
+    def register_get_key_text_fn(self, fn):
+        self.get_key_text = fn
+
+    # Register a value display function
+
+    def register_get_value_text_fn(self, regexp, fn):
+        self.get_value_text_fns.append((regexp, fn))
+
+#
+# A utility function to convert a string to the standard hex + ascii format.
+# To display all values in hex do:
+#   register_get_value_text_fn("", gtkdictbrowser.hex_string)
+#
+
+def hex_string(data):
+    """Return a hex dump of a string as a string.
+
+    The output produced is in the standard 16 characters per line hex +
+    ascii format:
+
+    00000000: 40 00 00 00 00 00 00 00  40 00 00 00 01 00 04 80  @....... @.......
+    00000010: 01 01 00 00 00 00 00 01  00 00 00 00              ........ ....
+    """
+    
+    pos = 0                             # Position in data
+    line = 0                            # Line of data
+    
+    hex = ""                            # Hex display
+    ascii = ""                          # ASCII display
+
+    result = ""
+    
+    while pos < len(data):
+        
+       # Start with header
+        
+       if pos % 16 == 0:
+            hex = "%08x: " % (line * 16)
+            ascii = ""
+            
+        # Add character
+            
+       hex = hex + "%02x " % (ord(data[pos]))
+        
+        if ord(data[pos]) < 32 or ord(data[pos]) > 176:
+            ascii = ascii + '.'
+        else:
+            ascii = ascii + data[pos]
+                
+        pos = pos + 1
+            
+        # Add separator if half way
+            
+       if pos % 16 == 8:
+            hex = hex + " "
+            ascii = ascii + " "
+
+        # End of line
+
+       if pos % 16 == 0:
+            result = result + "%s %s\n" % (hex, ascii)
+            line = line + 1
+            
+    # Leftover bits
+
+    if pos % 16 != 0:
+
+        # Pad hex string
+
+        for i in range(0, (16 - (pos % 16))):
+            hex = hex + "   "
+
+        # Half way separator
+
+        if (pos % 16) < 8:
+            hex = hex + " "
+
+        result = result + "%s %s\n" % (hex, ascii)
+
+    return result
+
+# For testing purposes, create a fixed dictionary to browse with
+
+if __name__ == "__main__":
+
+    dict = {"chicken": "ham", "spam": "fun", "subdict": {"a": "b", "c": "d"}}
+
+    db = GtkDictBrowser(dict)
+
+    db.build_ui("GtkDictBrowser")
+
+    # Override Python's handling of ctrl-c so we can break out of the
+    # gui from the command line.
+
+    import signal
+    signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+    mainloop()