[libsendmail-milter-perl] 07/20: Imported Upstream version 0.51

Hilko Bengen bengen at moszumanska.debian.org
Sun Sep 27 18:32:52 UTC 2015


This is an automated email from the git hooks/post-receive script.

bengen pushed a commit to annotated tag debian/1.0-1
in repository libsendmail-milter-perl.

commit c46a09d28f38ccef8d1a97f6811218bbc15bb5ae
Author: Hilko Bengen <bengen at debian.org>
Date:   Thu Dec 3 10:19:24 2009 +0100

    Imported Upstream version 0.51
---
 Changes                                |   27 +
 MANIFEST                               |    4 +
 META.yml                               |    2 +-
 Makefile.PL                            |    5 +
 bin/regcompare.pl                      |  448 ++++++++++--
 bin/regmultidiff.pl                    |   10 +-
 bin/regscope.pl                        | 1200 ++++++++++++++++++++++++++++++++
 bin/regview.pl                         |  331 ++++++---
 lib/Parse/Win32Registry.pm             |   64 +-
 lib/Parse/Win32Registry/Base.pm        |   21 +-
 lib/Parse/Win32Registry/Key.pm         |    5 +-
 lib/Parse/Win32Registry/Win95/Key.pm   |   14 +
 lib/Parse/Win32Registry/Win95/Value.pm |   10 +
 lib/Parse/Win32Registry/WinNT/Key.pm   |   32 +
 lib/Parse/Win32Registry/WinNT/Value.pm |   14 +
 t/iterator.t                           |  253 +++++++
 t/use.t                                |    2 +-
 t/win95_iter_tests.rf                  |  Bin 0 -> 655 bytes
 t/winnt_iter_tests.rf                  |  Bin 0 -> 5376 bytes
 19 files changed, 2280 insertions(+), 162 deletions(-)

diff --git a/Changes b/Changes
index 78c31a7..ab802cc 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,32 @@
 Revision history for Perl extension Parse::Win32Registry.
 
+** 0.51 2009-10-04
+
+Added new regscope.pl script, a GTK+ registry entry viewer that uses color
+to highlight different types of registry entries.
+
+Documented the get_name method of SID objects and the get_value_data
+method of Key objects. The as_string method of the ACE object and the
+as_stanza method of the SecurityDescriptor object now include the well
+known SID names (as returned each SID object's get_name method) for
+each SID object.
+
+Updated the regview.pl and regcompare.pl scripts: regview.pl and
+regcompare.pl can now select keys and/or values when searching,
+regview.pl can now sort columns (e.g. keys can be sorted by timestamp,
+values by type, etc), regcompare.pl can now bookmark keys or values,
+and regview.pl now has a basic report view.
+
+Fixed the get_subtree_iter and make_multiple_subkey_iterator methods
+to return the root key(s) of the subtree(s) as the documentation
+indicates. regview.pl, regmultidiff.pl, and regcompare.pl amended to
+accommodate these changes.
+
+Fixed redisplay problem closing dialogs using Escape in regview.pl and
+regcompare.pl.
+
+Makefile.pl now includes all scripts as exe_files.
+
 ** 0.50 2009-07-19
 
 Security information is now extracted from Windows NT registry files.
diff --git a/MANIFEST b/MANIFEST
index 6878b71..3da569f 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,7 @@ bin/regexport.pl
 bin/regfind.pl
 bin/regmultidiff.pl
 bin/regscan.pl
+bin/regscope.pl
 bin/regsecurity.pl
 bin/regshell.pl
 bin/regstats.pl
@@ -36,6 +37,7 @@ t/constants.t
 t/entry.t
 t/errors.t
 t/file.t
+t/iterator.t
 t/key.t
 t/misc.t
 t/security.t
@@ -57,9 +59,11 @@ t/invalid_regf_header.rf
 t/invalid_rgkn_header.rf
 t/missing_rgkn_header.rf
 t/win95_error_tests.rf
+t/win95_iter_tests.rf
 t/win95_key_tests.rf
 t/win95_value_tests.rf
 t/winnt_error_tests.rf
+t/winnt_iter_tests.rf
 t/winnt_key_tests.rf
 t/winnt_security_tests.rf
 t/winnt_value_tests.rf
diff --git a/META.yml b/META.yml
index 871403f..6d9c8e1 100644
--- a/META.yml
+++ b/META.yml
@@ -1,6 +1,6 @@
 --- #YAML:1.0
 name:                Parse-Win32Registry
-version:             0.50
+version:             0.51
 abstract:            Parse Windows Registry Files
 license:             ~
 author:              
diff --git a/Makefile.PL b/Makefile.PL
index 747baac..3ed244a 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -16,11 +16,16 @@ WriteMakefile(
     AUTHOR          => 'James Macfarlane',
     EXE_FILES       => [
         'bin/regclassnames.pl',
+        'bin/regcompare.pl',
         'bin/regdiff.pl',
         'bin/regdump.pl',
         'bin/regexport.pl',
         'bin/regfind.pl',
+        'bin/regmultidiff.pl',
         'bin/regscan.pl',
+        'bin/regscope.pl',
+        'bin/regsecurity.pl',
+        'bin/regshell.pl',
         'bin/regstats.pl',
         'bin/regtimeline.pl',
         'bin/regtree.pl',
diff --git a/bin/regcompare.pl b/bin/regcompare.pl
index b6e5ee6..05452b5 100755
--- a/bin/regcompare.pl
+++ b/bin/regcompare.pl
@@ -5,9 +5,15 @@ use warnings;
 use Glib ':constants';
 use Gtk2 -init;
 
+my $screen = Gtk2::Gdk::Screen->get_default;
+my $window_width = $screen->get_width * 0.9;
+my $window_height = $screen->get_height * 0.8;
+$window_width = 1100 if $window_width > 1100;
+$window_height = 900 if $window_height > 900;
+
 use File::Basename;
 use File::Spec;
-use Parse::Win32Registry 0.50 qw( make_multiple_subtree_iterator
+use Parse::Win32Registry 0.51 qw( make_multiple_subtree_iterator
                                   make_multiple_subkey_iterator
                                   make_multiple_value_iterator
                                   compare_multiple_keys
@@ -176,6 +182,7 @@ my @actions = (
     # name, stock id, label
     ['FileMenu', undef, '_File'],
     ['SearchMenu', undef, '_Search'],
+    ['BookmarksMenu', undef, '_Bookmarks'],
     ['ViewMenu', undef, '_View'],
     ['HelpMenu', undef, '_Help'],
     # name, stock-id, label, accelerator, tooltip, callback
@@ -184,8 +191,11 @@ my @actions = (
     ['Quit', 'gtk-quit', '_Quit', '<control>Q', undef, \&quit],
     ['Find', 'gtk-find', '_Find', '<control>F', undef, \&find],
     ['FindNext', undef, 'Find Next', '<control>G', undef, \&find_next],
+    ['FindNext2', undef, 'Find Next', 'F3', undef, \&find_next],
     ['FindChange', 'gtk-find', 'Find _Change', '<control>N', undef, \&find_change],
     ['FindNextChange', undef, 'Find Next Change', '<control>M', undef, \&find_next_change],
+    ['AddBookmark', 'gtk-add', '_Add Bookmark', '<control>D', undef, \&add_bookmark],
+    ['EditBookmarks', undef, '_Edit Bookmarks', '<control>B', undef, \&edit_bookmarks],
     ['About', 'gtk-about', '_About', undef, undef, \&about],
 );
 
@@ -194,11 +204,17 @@ $action_group->add_actions(\@actions, undef);
 
 my @toggle_actions = (
     # name, stock id, label, accelerator, tooltip, callback, active
-    ['ShowDetail', undef, 'Show _Detail', '<control>X', undef, \&toggle_item_detail, TRUE],
+    ['ShowToolbar', undef, 'Show _Toolbar', '<control>T', undef, \&toggle_toolbar, TRUE],
+    ['ShowDetail', 'gtk-edit', 'Show _Detail', '<control>X', undef, \&toggle_item_detail, TRUE],
 );
 $action_group->add_toggle_actions(\@toggle_actions, undef);
 
+my $action_group2 = Gtk2::ActionGroup->new('actions2'); # bookmarks
+my $bookmarks_merge_id = $uimanager->new_merge_id;
+my $action_name = 1; # unique action name
+
 $uimanager->insert_action_group($action_group, 0);
+$uimanager->insert_action_group($action_group2, 1);
 
 my $ui_info = <<END_OF_UI;
 <ui>
@@ -216,8 +232,15 @@ my $ui_info = <<END_OF_UI;
             <menuitem action='FindChange'/>
             <menuitem action='FindNextChange'/>
         </menu>
+        <menu action='BookmarksMenu'>
+            <menuitem action='AddBookmark'/>
+            <menuitem action='EditBookmarks'/>
+            <separator/>
+        </menu>
         <menu action='ViewMenu'>
+            <menuitem action='ShowToolbar'/>
             <menuitem action='ShowDetail'/>
+            <separator/>
         </menu>
         <menu action='HelpMenu'>
             <menuitem action='About'/>
@@ -232,6 +255,7 @@ my $ui_info = <<END_OF_UI;
         <separator/>
         <toolitem action='Quit'/>
     </toolbar>
+    <accelerator action='FindNext2'/>
 </ui>
 END_OF_UI
 
@@ -239,6 +263,7 @@ $uimanager->add_ui_from_string($ui_info);
 
 my $menubar = $uimanager->get_widget('/MenuBar');
 my $toolbar = $uimanager->get_widget('/ToolBar');
+my $bookmarks_menu = $uimanager->get_widget('/MenuBar/BookmarksMenu')->get_submenu;
 
 ### STATUSBAR
 
@@ -246,7 +271,7 @@ my $statusbar = Gtk2::Statusbar->new;
 
 ### VBOX
 
-my $main_vbox = Gtk2::VBox->new;
+my $main_vbox = Gtk2::VBox->new(FALSE, 0);
 $main_vbox->pack_start($menubar, FALSE, FALSE, 0);
 $main_vbox->pack_start($toolbar, FALSE, FALSE, 0);
 $main_vbox->pack_start($vpaned1, TRUE, TRUE, 0);
@@ -255,7 +280,7 @@ $main_vbox->pack_start($statusbar, FALSE, FALSE, 0);
 ### WINDOW
 
 my $window = Gtk2::Window->new;
-$window->set_default_size(600, 400);
+$window->set_default_size($window_width, $window_height);
 $window->set_position('center');
 $window->signal_connect(destroy => sub { Gtk2->main_quit });
 $window->add($main_vbox);
@@ -301,10 +326,14 @@ sub build_open_files_dialog {
         'gtk-remove' => 50,
         'gtk-ok' => 'ok',
     );
-    $dialog->set_size_request(-1, 400);
+    $dialog->set_size_request($window_width * 0.8, $window_height * 0.8);
     $dialog->vbox->add($scrolled_file_view);
     $dialog->set_default_response('ok');
 
+    $dialog->signal_connect(delete_event => sub {
+        $dialog->hide;
+        return TRUE;
+    });
     $dialog->signal_connect(response => sub {
         my ($dialog, $response) = @_;
         if ($response eq '70') {
@@ -347,6 +376,119 @@ sub build_open_files_dialog {
 
 my $open_files_dialog = build_open_files_dialog;
 
+### BOOKMARKS STORE
+
+use constant {
+    BMCOL_NAME => 0,
+    BMCOL_LOCATION => 1,
+    BMCOL_ICON => 2,
+};
+
+my $bookmark_store = Gtk2::ListStore->new(
+    'Glib::String', 'Glib::Scalar', 'Glib::String',
+);
+
+sub build_bookmarks_dialog {
+    my $bookmark_view = Gtk2::TreeView->new($bookmark_store);
+    $bookmark_view->set_reorderable(TRUE);
+
+    my $bookmark_icon_cell = Gtk2::CellRendererPixbuf->new;
+    my $bookmark_name_cell = Gtk2::CellRendererText->new;
+    my $bookmark_column0 = Gtk2::TreeViewColumn->new;
+    $bookmark_column0->set_title('Bookmark');
+    $bookmark_column0->pack_start($bookmark_icon_cell, FALSE);
+    $bookmark_column0->pack_start($bookmark_name_cell, TRUE);
+    $bookmark_column0->set_attributes($bookmark_icon_cell,
+        'stock-id', BMCOL_ICON);
+    $bookmark_column0->set_attributes($bookmark_name_cell,
+        'text', BMCOL_NAME);
+    $bookmark_column0->set_resizable(TRUE);
+    $bookmark_view->append_column($bookmark_column0);
+
+    my $bookmark_location_cell = Gtk2::CellRendererText->new;
+    my $bookmark_column1 = $bookmark_view->insert_column_with_data_func(
+        1, 'Location', $bookmark_location_cell,
+        sub {
+            my ($column, $cell, $model, $iter, $num) = @_;
+            my $location = $model->get($iter, BMCOL_LOCATION);
+            if (defined $location) {
+                my ($subkey_path, $value_name) = @$location;
+                my $string = $subkey_path;
+                if (defined $value_name) {
+                    $value_name = '(Default)' if $value_name eq '';
+                    $string .= ", $value_name";
+                }
+                $cell->set('text', $string);
+            }
+            else {
+                $cell->set('text', '?');
+            }
+        },
+    );
+    $bookmark_location_cell->set('ellipsize', 'end');
+
+    my $scrolled_bookmark_view = Gtk2::ScrolledWindow->new;
+    $scrolled_bookmark_view->set_policy('automatic', 'automatic');
+    $scrolled_bookmark_view->set_shadow_type('in');
+    $scrolled_bookmark_view->add($bookmark_view);
+
+    my $label = Gtk2::Label->new;
+    $label->set_markup('<i>Drag bookmarks to reorder them</i>');
+
+    my $dialog = Gtk2::Dialog->new('Edit Bookmarks', $window, 'modal',
+        'gtk-remove' => 50,
+        'gtk-ok' => 'ok',
+    );
+    $dialog->resize($window_width * 0.8, $window_height * 0.8);
+    $dialog->vbox->pack_start($scrolled_bookmark_view, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($label, FALSE, FALSE, 5);
+    $dialog->set_default_response('ok');
+
+    $dialog->signal_connect(delete_event => sub {
+        $dialog->hide;
+        return TRUE;
+    });
+    $dialog->signal_connect(response => sub {
+        my ($dialog, $response) = @_;
+        if ($response eq '50') {
+            # Remove selected bookmark
+            my $selection = $bookmark_view->get_selection;
+            my $iter = $selection->get_selected;
+            if (defined $iter) {
+                $bookmark_store->remove($iter);
+            }
+        }
+        else {
+            # Before exiting, move menuitems into current bookmark order
+            $uimanager->remove_ui($bookmarks_merge_id);
+            $uimanager->ensure_update;
+            foreach my $action ($action_group2->list_actions) {
+                $action_group2->remove_action($action);
+            }
+            $action_name = 1;
+            my $iter = $bookmark_store->get_iter_first;
+            while (defined $iter) {
+                my $bookmark_name = $bookmark_store->get($iter, BMCOL_NAME);
+                my $location = $bookmark_store->get($iter, BMCOL_LOCATION);
+                my $icon = $bookmark_store->get($iter, BMCOL_ICON);
+                my $display_name = $bookmark_name;
+                $display_name =~ s/_/__/g;
+                $action_group2->add_actions([
+                    [$action_name, $icon, $display_name, undef, undef, \&go_to_bookmark],
+                ], $location);
+                $uimanager->add_ui($bookmarks_merge_id, '/MenuBar/BookmarksMenu', $action_name, $action_name, 'menuitem', FALSE);
+                $action_name++;
+                $iter = $bookmark_store->iter_next($iter);
+            }
+            $dialog->hide;
+        }
+    });
+
+    return $dialog;
+}
+
+my $bookmarks_dialog = build_bookmarks_dialog;
+
 ######################## GLOBAL SETUP
 
 my @filenames = ();
@@ -354,7 +496,9 @@ my @root_keys = ();
 
 my $last_dir;
 
-my $find_param;
+my $search_keys = TRUE;
+my $search_values = TRUE;
+my $find_param = '';
 my $find_iter;
 my $change_iter;
 
@@ -423,6 +567,16 @@ sub toggle_item_detail {
     }
 }
 
+sub toggle_toolbar {
+    my ($toggle_action) = @_;
+    if ($toggle_action->get_active) {
+        $toolbar->show;
+    }
+    else {
+        $toolbar->hide;
+    }
+}
+
 sub tree_item_selected {
     my ($tree_selection) = @_;
 
@@ -552,11 +706,12 @@ sub compare_files {
                 my $color = $model->get($iter, TREECOL_COLOR);
                 if (defined $changes) {
                     my $diff = substr($changes->[$num], 0, 1);
-                    $cell->set('text', $diff || '.');
+                    $cell->set('text', $diff || "\x{00bb}");
                     $cell->set('foreground', $color);
                 }
                 else {
-                    $cell->set('text', '.');
+                    $cell->set('text', "\x{00b7}");
+                    $cell->set('foreground', $color);
                 }
             },
             $num, # additional data is passed to callback
@@ -607,11 +762,18 @@ sub add_children {
 
     while (defined(my $subkeys = $subkeys_iter->get_next)) {
         my @changes = compare_multiple_keys(@$subkeys);
+        my $num_changes = grep { $_ } @changes;
+        # insert a 'blank' change for missing subkeys
+        for (my $i = 0; $i < @changes; $i++) {
+            if ($changes[$i] eq '' && !defined $subkeys->[$i]) {
+                $changes[$i] = ' ';
+            }
+        }
 
         my $any_subkey = (grep { defined } @$subkeys)[0];
 
         my $iter = $model->append($parent_iter);
-        my $num_changes = grep { $_ } @changes;
+
         if ($num_changes > 0) {
             $model->set($iter,
                 TREECOL_NAME, $any_subkey->get_name,
@@ -635,13 +797,20 @@ sub add_children {
 
     while (defined(my $values = $values_iter->get_next)) {
         my @changes = compare_multiple_values(@$values);
+        my $num_changes = grep { $_ } @changes;
+        # insert a 'blank' change for missing values
+        for (my $i = 0; $i < @changes; $i++) {
+            if ($changes[$i] eq '' && !defined $values->[$i]) {
+                $changes[$i] = " ";
+            }
+        }
 
         my $any_value = (grep { defined } @$values)[0];
-
         my $name = $any_value->get_name;
         $name = "(Default)" if $name eq '';
+
         my $iter = $model->append($parent_iter);
-        my $num_changes = grep { $_ } @changes;
+
         if ($num_changes > 0) {
             $model->set($iter,
                 TREECOL_NAME, $name,
@@ -751,6 +920,7 @@ sub show_message {
         'ok',
         $message,
     );
+    $dialog->set_title(ucfirst $type);
     $dialog->run;
     $dialog->destroy;
 }
@@ -792,8 +962,8 @@ sub go_to_subkey_and_value {
                         ? ($subkey_path)
                         : split(/\\/, $subkey_path, -1);
 
-    my $root_iter = $tree_store->get_iter_first;
-    my $iter = $root_iter;
+    my $iter = $tree_store->get_iter_first;
+    return if !defined $iter; # no registry loaded
 
     while (defined(my $subkey_name = shift @path_components)) {
         my $items = $tree_store->get($iter, TREECOL_ITEMS);
@@ -813,10 +983,12 @@ sub go_to_subkey_and_value {
                 if (!defined $iter) {
                     return; # no matching child iter
                 }
-
             }
+            my $parent_iter = $tree_store->iter_parent($iter);
+            my $parent_path = $tree_store->get_path($parent_iter);
+            $tree_view->expand_to_path($parent_path);
             my $tree_path = $tree_store->get_path($iter);
-            $tree_view->expand_to_path($tree_path);
+#            $tree_view->expand_to_path($tree_path);
             $tree_view->scroll_to_cell($tree_path);
             $tree_view->set_cursor($tree_path);
             $window->set_focus($tree_view);
@@ -825,19 +997,33 @@ sub go_to_subkey_and_value {
     }
 }
 
+sub get_search_message {
+    my $message;
+    if ($search_keys && $search_values) {
+        $message = "Searching registry keys and values...";
+    }
+    elsif ($search_keys) {
+        $message = "Searching registry keys...";
+    }
+    elsif ($search_values) {
+        $message = "Searching registry values...";
+    }
+    return $message;
+}
+
 sub find_next {
     if (!defined $find_param || !defined $find_iter) {
         return;
     }
 
     my $label = Gtk2::Label->new;
-    $label->set_text("Searching registry...");
+    $label->set_text(get_search_message);
     my $dialog = Gtk2::Dialog->new('Find',
         $window,
         'modal',
         'gtk-cancel' => 'cancel',
     );
-    $dialog->vbox->pack_start($label, TRUE, TRUE, 10);
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
     $dialog->set_default_response('cancel');
     $dialog->show_all;
 
@@ -846,57 +1032,82 @@ sub find_next {
 
         if (!defined $keys_ref) {
             $dialog->response('ok');
-            show_message('info', 'Finished searching.');
             return FALSE; # stop searching
         }
 
         # Obtain the name and path from the first defined key
         my $any_key = (grep { defined } @$keys_ref)[0];
         my $subkey_path = (split(/\\/, $any_key->get_path, 2))[1];
+        if (!defined $subkey_path) {
+            return TRUE;
+        }
 
+        # Check values (if defined) for a match
         if (defined $values_ref) {
-            my $any_value = (grep { defined } @$values_ref)[0];
-            my $value_name = $any_value->get_name;
-            if (index(lc $value_name, lc $find_param) >= 0) {
-                go_to_subkey_and_value($subkey_path, $value_name);
-                $dialog->response('ok');
-                return FALSE; # stop searching
-            }
-            else {
-                return TRUE; # continue searching
+            if ($search_values) {
+                my $any_value = (grep { defined } @$values_ref)[0];
+                my $value_name = $any_value->get_name;
+                if (index(lc $value_name, lc $find_param) >= 0) {
+                    go_to_subkey_and_value($subkey_path, $value_name);
+                    $dialog->response(50);
+                    return FALSE; # stop searching
+                }
             }
+            return TRUE; # continue searching
         }
 
-        my $key_name = $any_key->get_name;
-        if (index(lc $key_name, lc $find_param) >= 0) {
-            go_to_subkey_and_value($subkey_path);
-            $dialog->response('ok');
-            return FALSE; # stop searching
-        }
-        else {
-            return TRUE; # continue searching
+        # Check keys for a match
+        if ($search_keys) {
+            my $key_name = $any_key->get_name;
+            if (index(lc $key_name, lc $find_param) >= 0) {
+                go_to_subkey_and_value($subkey_path);
+                $dialog->response(50);
+                return FALSE; # stop searching
+            }
         }
+        return TRUE; # continue searching
     });
 
     my $response = $dialog->run;
+    $dialog->destroy;
+
     if ($response eq 'cancel' || $response eq 'delete-event') {
         Glib::Source->remove($id);
     }
-    $dialog->destroy;
+    elsif ($response eq 'ok') {
+        show_message('info', 'Finished searching.');
+    }
 }
 
 sub find {
     return if @root_keys == 0;
 
     my $entry = Gtk2::Entry->new;
+    $entry->set_text($find_param);
     $entry->set_activates_default(TRUE);
+    my $check1 = Gtk2::CheckButton->new('Search _Keys');
+    $check1->set_active($search_keys);
+    my $check2 = Gtk2::CheckButton->new('Search _Values');
+    $check2->set_active($search_values);
+    $check1->signal_connect(toggled => sub {
+        if (!$check1->get_active && !$check2->get_active) {
+            $check2->set_active(TRUE);
+        }
+    });
+    $check2->signal_connect(toggled => sub {
+        if (!$check1->get_active && !$check2->get_active) {
+            $check1->set_active(TRUE);
+        }
+    });
     my $dialog = Gtk2::Dialog->new('Find',
         $window,
         'modal',
         'gtk-cancel' => 'cancel',
         'gtk-ok' => 'ok',
     );
-    $dialog->vbox->pack_start($entry, TRUE, TRUE, 10);
+    $dialog->vbox->pack_start($entry, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($check1, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($check2, TRUE, TRUE, 0);
     $dialog->set_default_response('ok');
     $dialog->show_all;
 
@@ -904,6 +1115,8 @@ sub find {
     $dialog->destroy;
 
     if ($response eq 'ok' && @root_keys > 0) {
+        $search_keys = $check1->get_active;
+        $search_values = $check2->get_active;
         $find_param = $entry->get_text;
         if ($find_param ne '') {
             $find_iter = make_multiple_subtree_iterator(@root_keys);
@@ -918,13 +1131,13 @@ sub find_next_change {
     }
 
     my $label = Gtk2::Label->new;
-    $label->set_text("Searching registry...");
+    $label->set_text(get_search_message);
     my $dialog = Gtk2::Dialog->new('Find Change',
         $window,
         'modal',
         'gtk-cancel' => 'cancel',
     );
-    $dialog->vbox->pack_start($label, TRUE, TRUE, 10);
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
     $dialog->set_default_response('cancel');
     $dialog->show_all;
 
@@ -933,48 +1146,54 @@ sub find_next_change {
 
         if (!defined $keys_ref) {
             $dialog->response('ok');
-            show_message('info', 'Finished searching.');
             return FALSE; # stop searching
         }
 
         # Obtain the name and path from the first defined key
         my $any_key = (grep { defined } @$keys_ref)[0];
         my $subkey_path = (split(/\\/, $any_key->get_path, 2))[1];
+        if (!defined $subkey_path) {
+            return TRUE;
+        }
 
+        # Check values (if defined) for changes
         if (defined $values_ref) {
-            my $any_value = (grep { defined } @$values_ref)[0];
-            my $value_name = $any_value->get_name;
-            my @changes = compare_multiple_values(@$values_ref);
+            if ($search_values) {
+                my $any_value = (grep { defined } @$values_ref)[0];
+                my $value_name = $any_value->get_name;
+                my @changes = compare_multiple_values(@$values_ref);
+                my $num_changes = grep { $_ } @changes;
+                if ($num_changes > 0) {
+                    go_to_subkey_and_value($subkey_path, $value_name);
+                    $dialog->response(50);
+                    return FALSE; # stop searching
+                }
+            }
+            return TRUE; # continue searching
+        }
+
+        if ($search_keys) {
+            my $key_name = $any_key->get_name;
+            my @changes = compare_multiple_keys(@$keys_ref);
             my $num_changes = grep { $_ } @changes;
             if ($num_changes > 0) {
-                go_to_subkey_and_value($subkey_path, $value_name);
-                $dialog->response('ok');
+                go_to_subkey_and_value($subkey_path);
+                $dialog->response(50);
                 return FALSE; # stop searching
             }
-            else {
-                return TRUE; # continue searching
-            }
-        }
-
-        my $key_name = $any_key->get_name;
-
-        my @changes = compare_multiple_keys(@$keys_ref);
-        my $num_changes = grep { $_ } @changes;
-        if ($num_changes > 0) {
-            go_to_subkey_and_value($subkey_path);
-            $dialog->response('ok');
-            return FALSE; # stop searching
-        }
-        else {
-            return TRUE; # continue searching
         }
+        return TRUE; # continue searching
     });
 
     my $response = $dialog->run;
+    $dialog->destroy;
+
     if ($response eq 'cancel' || $response eq 'delete-event') {
         Glib::Source->remove($id);
     }
-    $dialog->destroy;
+    elsif ($response eq 'ok') {
+        show_message('info', 'Finished searching.');
+    }
 }
 
 sub find_change {
@@ -1006,14 +1225,30 @@ sub find_change {
     my $key_path = $start_key->get_path;
 
     my $label = Gtk2::Label->new;
-    $label->set_text("Find changes starting from\n'$key_path'?");
+    $label->set_markup("Find changes starting from\n<b>$key_path</b>?");
+    my $check1 = Gtk2::CheckButton->new('Search _Keys');
+    $check1->set_active($search_keys);
+    my $check2 = Gtk2::CheckButton->new('Search _Values');
+    $check2->set_active($search_values);
+    $check1->signal_connect(toggled => sub {
+        if (!$check1->get_active && !$check2->get_active) {
+            $check2->set_active(TRUE);
+        }
+    });
+    $check2->signal_connect(toggled => sub {
+        if (!$check1->get_active && !$check2->get_active) {
+            $check1->set_active(TRUE);
+        }
+    });
     my $dialog = Gtk2::Dialog->new('Find Change',
         $window,
         'modal',
         'gtk-cancel' => 'cancel',
         'gtk-ok' => 'ok',
     );
-    $dialog->vbox->pack_start($label, TRUE, TRUE, 10);
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
+    $dialog->vbox->pack_start($check1, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($check2, TRUE, TRUE, 0);
     $dialog->set_default_response('ok');
     $dialog->show_all;
 
@@ -1021,7 +1256,92 @@ sub find_change {
     $dialog->destroy;
 
     if ($response eq 'ok') {
+        $search_keys = $check1->get_active;
+        $search_values = $check2->get_active;
         $change_iter = make_multiple_subtree_iterator(@start_keys);
+        $change_iter->get_next;
         find_next_change;
     }
 }
+
+sub get_location {
+    my @start_keys;
+    my @start_values;
+    my ($keys_ref, $values_ref);
+    my ($model, $iter) = $tree_selection->get_selected;
+    if (defined $model && defined $iter) {
+        my $icon = $model->get($iter, TREECOL_ICON);
+        if ($icon eq 'gtk-directory') {
+            # Item is a key
+            $keys_ref = $model->get($iter, TREECOL_ITEMS);
+        }
+        else {
+            # Item is a value
+            $values_ref = $model->get($iter, TREECOL_ITEMS);
+
+            # Find parent key
+            $iter = $model->iter_parent($iter);
+            return if !defined $iter;
+
+            $keys_ref = $model->get($iter, TREECOL_ITEMS);
+        }
+        return ($keys_ref, $values_ref);
+    }
+    else {
+        return ($keys_ref, $values_ref);
+    }
+}
+
+sub add_bookmark {
+    my ($keys_ref, $values_ref) = get_location;
+    if (defined $keys_ref) {
+        my $any_key = (grep { defined } @$keys_ref)[0];
+        my $key_path = $any_key->get_path;
+        my $key_name = $any_key->get_name;
+
+        # Remove root key name to get subkey path
+        my $subkey_path = (split(/\\/, $key_path, 2))[1];
+        return if !defined $subkey_path;
+
+        my $bookmark_name;
+        my $location;
+        my $icon;
+        if (defined $values_ref) {
+            my $any_value = (grep { defined } @$values_ref)[0];
+            my $value_name = $any_value->get_name;
+            $location = [$subkey_path, $value_name];
+            $value_name = '(Default)' if $value_name eq '';
+            $bookmark_name = "$value_name [$key_name]";
+            $icon = 'gtk-file';
+        }
+        else {
+            $bookmark_name = $key_name;
+            $location = [$subkey_path];
+            $icon = 'gtk-directory';
+        }
+        my $display_name = $bookmark_name;
+        $display_name =~ s/_/__/g;
+        $action_group2->add_actions([
+            [$action_name, $icon, $display_name, undef, undef, \&go_to_bookmark],
+        ], $location);
+        $uimanager->add_ui($bookmarks_merge_id, '/MenuBar/BookmarksMenu', $action_name, $action_name, 'menuitem', FALSE);
+        $action_name++;
+        if (my $iter = $bookmark_store->append) {
+            $bookmark_store->set($iter,
+                BMCOL_NAME, $bookmark_name,
+                BMCOL_LOCATION, $location,
+                BMCOL_ICON, $icon,
+            );
+        }
+    }
+}
+
+sub edit_bookmarks {
+    $bookmarks_dialog->show_all;
+}
+
+sub go_to_bookmark {
+    my ($menuitem, $location) = @_;
+    my ($subkey_path, $value_name) = @$location;
+    go_to_subkey_and_value($subkey_path, $value_name);
+}
diff --git a/bin/regmultidiff.pl b/bin/regmultidiff.pl
index b51da65..66d39a3 100755
--- a/bin/regmultidiff.pl
+++ b/bin/regmultidiff.pl
@@ -4,7 +4,7 @@ use warnings;
 
 use File::Basename;
 use Getopt::Long;
-use Parse::Win32Registry 0.50 qw( make_multiple_subtree_iterator
+use Parse::Win32Registry 0.51 qw( make_multiple_subtree_iterator
                                   compare_multiple_keys
                                   compare_multiple_values
                                   hexdump );
@@ -68,9 +68,10 @@ for (my $num = 0; $num < $batch_size; $num++) {
 }
 
 my $key_shown;
-my $keys_ref = \@start_keys;
-my $values_ref;
-do {
+#my $keys_ref = \@start_keys;
+#my $values_ref;
+
+while (my ($keys_ref, $values_ref) = $subtree_iter->get_next) {
     my @keys = @$keys_ref;
     my $any_key = (grep { defined } @keys)[0];
     die "Unexpected error: no keys!" if !defined $any_key;
@@ -136,7 +137,6 @@ do {
         }
     }
 }
-while (($keys_ref, $values_ref) = $subtree_iter->get_next);
 
 sub usage {
     my $script_name = basename $0;
diff --git a/bin/regscope.pl b/bin/regscope.pl
new file mode 100755
index 0000000..29d3a87
--- /dev/null
+++ b/bin/regscope.pl
@@ -0,0 +1,1200 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use constant MAX_SCALE => 10; # maximum scale for hbin maps
+
+use Glib ':constants';
+use Gtk2 -init;
+
+my $screen = Gtk2::Gdk::Screen->get_default;
+my $window_width = $screen->get_width * 0.9;
+my $window_height = $screen->get_height * 0.8;
+$window_width = 1100 if $window_width > 1100;
+$window_height = 900 if $window_height > 900;
+
+use Encode;
+use File::Basename;
+use Parse::Win32Registry 0.51 qw(hexdump qquote :REG_);
+
+binmode(STDOUT, ':utf8');
+
+my $script_name = basename $0;
+
+### LIST VIEW
+
+use constant {
+    COLUMN_HBIN_OFFSET => 0,
+    COLUMN_HBIN_OBJECT => 1,
+};
+
+my $hbin_store = Gtk2::ListStore->new(
+    'Glib::String', 'Glib::Scalar',
+);
+
+my $hbin_view = Gtk2::TreeView->new($hbin_store);
+$hbin_view->set_size_request(120, -1);
+
+my $hbin_column1 = Gtk2::TreeViewColumn->new_with_attributes(
+    'Hbin', Gtk2::CellRendererText->new,
+    'text', COLUMN_HBIN_OFFSET,
+);
+$hbin_view->append_column($hbin_column1);
+$hbin_column1->set_resizable(TRUE);
+
+my $hbin_selection = $hbin_view->get_selection;
+$hbin_selection->set_mode('browse');
+$hbin_selection->signal_connect('changed' => \&hbin_selection_changed);
+
+my $scrolled_hbin_view = Gtk2::ScrolledWindow->new;
+$scrolled_hbin_view->set_policy('automatic', 'automatic');
+$scrolled_hbin_view->set_shadow_type('in');
+$scrolled_hbin_view->add($hbin_view);
+
+### LIST VIEW FOR ENTRY
+
+use constant {
+    COLUMN_ENTRY_OFFSET => 0,
+    COLUMN_ENTRY_LENGTH => 1,
+    COLUMN_ENTRY_TAG => 2,
+    COLUMN_ENTRY_IN_USE => 3,
+    COLUMN_ENTRY_COLOR => 4,
+    COLUMN_ENTRY_OBJECT => 5,
+    COLUMN_ENTRY_USED_BY => 6,
+};
+
+my $entry_store = Gtk2::ListStore->new(
+    'Glib::String', 'Glib::String', 'Glib::String',
+    'Glib::String', 'Glib::String', 'Glib::Scalar',
+    'Glib::String',
+);
+
+my $entry_view = Gtk2::TreeView->new($entry_store);
+
+my $entry_column0 = Gtk2::TreeViewColumn->new_with_attributes(
+    'Entry', my $entry_cell0 = Gtk2::CellRendererText->new,
+    'text', COLUMN_ENTRY_OFFSET,
+    'background', COLUMN_ENTRY_COLOR,
+);
+$entry_view->append_column($entry_column0);
+$entry_column0->set_resizable(TRUE);
+
+my $entry_column1 = Gtk2::TreeViewColumn->new_with_attributes(
+    'Tag', my $entry_cell1 = Gtk2::CellRendererText->new,
+    'text', COLUMN_ENTRY_TAG,
+    'background', COLUMN_ENTRY_COLOR,
+);
+$entry_view->append_column($entry_column1);
+$entry_column1->set_resizable(TRUE);
+
+my $entry_column2 = Gtk2::TreeViewColumn->new_with_attributes(
+    'Alloc.', Gtk2::CellRendererText->new,
+    'text', COLUMN_ENTRY_IN_USE,
+    'background', COLUMN_ENTRY_COLOR,
+);
+$entry_view->append_column($entry_column2);
+$entry_column2->set_resizable(TRUE);
+
+my $entry_column3 = Gtk2::TreeViewColumn->new_with_attributes(
+    'Length', Gtk2::CellRendererText->new,
+    'text', COLUMN_ENTRY_LENGTH,
+    'background', COLUMN_ENTRY_COLOR,
+);
+$entry_view->append_column($entry_column3);
+$entry_column3->set_resizable(TRUE);
+
+my $entry_column4 = Gtk2::TreeViewColumn->new_with_attributes(
+    'Owner', my $entry_cell4 = Gtk2::CellRendererText->new,
+    'text', COLUMN_ENTRY_USED_BY,
+    'background', COLUMN_ENTRY_COLOR,
+);
+$entry_view->append_column($entry_column4);
+$entry_column4->set_resizable(TRUE);
+
+my $entry_selection = $entry_view->get_selection;
+$entry_selection->set_mode('browse');
+$entry_selection->signal_connect('changed' => \&entry_selection_changed);
+
+my $scrolled_entry_view = Gtk2::ScrolledWindow->new;
+$scrolled_entry_view->set_policy('automatic', 'automatic');
+$scrolled_entry_view->set_shadow_type('in');
+$scrolled_entry_view->add($entry_view);
+
+### TEXT VIEW
+
+my $text_view = Gtk2::TextView->new;
+$text_view->set_editable(FALSE);
+$text_view->modify_font(Gtk2::Pango::FontDescription->from_string('monospace'));
+
+my $text_buffer = $text_view->get_buffer;
+
+my $scrolled_text_view = Gtk2::ScrolledWindow->new;
+$scrolled_text_view->set_policy('automatic', 'automatic');
+$scrolled_text_view->set_shadow_type('in');
+$scrolled_text_view->add($text_view);
+
+### IMAGE
+
+my $hbin_image = Gtk2::Image->new;
+my $eventbox = Gtk2::EventBox->new;
+$eventbox->add($hbin_image);
+$eventbox->add_events(['button-press-mask']);
+
+$eventbox->signal_connect('button-press-event' => \&hbin_map_click);
+
+my $scrolled_hbin_image = Gtk2::ScrolledWindow->new;
+$scrolled_hbin_image->set_policy('automatic', 'automatic');
+$scrolled_hbin_image->set_shadow_type('in');
+$scrolled_hbin_image->add_with_viewport($eventbox);
+
+### NOTEBOOK
+
+my $notebook = Gtk2::Notebook->new;
+my $hbin_map_page = $notebook->append_page($scrolled_hbin_image,
+			Gtk2::Label->new_with_mnemonic("Hbin _Map"));
+my $info_page = $notebook->append_page($scrolled_text_view,
+			Gtk2::Label->new_with_mnemonic("_Info"));
+
+### HPANED
+
+my $hpaned = Gtk2::HPaned->new;
+$hpaned->add1($scrolled_entry_view);
+$hpaned->add2($notebook);
+$hpaned->set_position(320);
+
+### HBOX
+
+my $hbox = Gtk2::HBox->new;
+$hbox->pack_start($scrolled_hbin_view, FALSE, FALSE, 0);
+$hbox->pack_start($hpaned, TRUE, TRUE, 0);
+
+### UIMANAGER
+
+my $uimanager = Gtk2::UIManager->new;
+
+my @actions = (
+    # name, stock id, label
+    ['FileMenu', undef, '_File'],
+    ['SearchMenu', undef, '_Search'],
+    ['ViewMenu', undef, '_View'],
+    ['HelpMenu', undef, '_Help'],
+    # name, stock-id, label, accelerator, tooltip, callback
+    ['Open', 'gtk-open', '_Open', '<control>O', undef, \&open_file],
+    ['Quit', 'gtk-quit', '_Quit', '<control>Q', undef, \&quit],
+    ['About', 'gtk-about', '_About', undef, undef, \&about],
+);
+
+my $default_actions = Gtk2::ActionGroup->new('actions');
+$default_actions->add_actions(\@actions, undef);
+
+my @actions2 = (
+    # name, stock-id, label, accelerator, tooltip, callback
+    ['Close', 'gtk-close', '_Close', '<control>W', undef, \&close_file],
+    ['Find', 'gtk-find', '_Find', '<control>F', undef, \&find],
+    ['FindNext', undef, 'Find _Next', '<control>G', undef, \&find_next],
+    ['FindNext2', undef, undef, 'F3', undef, \&find_next],
+    ['Process1', undef, '_Scan Entries', undef, undef, \&scan_entries],
+    ['Process2', 'gtk-media-play', 'Identify _Entry Owners', '<control>E', undef, \&scan_tree],
+    ['GoTo', 'gtk-index', '_Go To Offset', '<control>I', undef, \&go_to_offset],
+);
+
+my $file_actions = Gtk2::ActionGroup->new('actions2');
+$file_actions->add_actions(\@actions2, undef);
+
+my @actions3 = (
+    # name, stock-id, label, accelerator, tooltip, callback
+    ['ZoomIn', 'gtk-zoom-in', 'Zoom Hbin Map _In', '<control>plus', undef, \&zoom_in],
+    ['ZoomIn2', undef, undef, '<control>equal', undef, \&zoom_in],
+    ['ZoomOut', 'gtk-zoom-out', 'Zoom Hbin Map _Out', '<control>minus', undef, \&zoom_out],
+    ['ZoomFit', 'gtk-zoom-fit', 'Zoom Hbin Map To _Fit', '<control>0', undef, \&zoom_fit],
+    ['SaveHbinMap', 'gtk-save', '_Save Hbin Map', '<control>S', undef, \&save_hbin_map],
+);
+
+my $hbin_actions = Gtk2::ActionGroup->new('actions3');
+$hbin_actions->add_actions(\@actions3, undef);
+
+my @actions4 = (
+    # name, stock-id, label, accelerator, tooltip, callback
+    ['Jump', 'gtk-jump-to', '_Jump To Owner', '<control>J', undef, \&jump_to_owner],
+    ['JumpBack', 'gtk-go-back', 'Jump _Back', 'BackSpace', undef, \&jump_back],
+);
+
+my $owner_actions = Gtk2::ActionGroup->new('actions4');
+$owner_actions->add_actions(\@actions4, undef);
+
+my @toggle_actions = (
+    # name, stock id, label, accelerator, tooltip, callback, active
+    ['ShowToolbar', undef, 'Show _Toolbar', '<control>T', undef, \&toggle_toolbar, TRUE],
+    ['ShowHbins', undef, 'Show _Hbins', undef, undef, \&toggle_hbins, TRUE],
+    ['ShowHbinMap', undef, 'Show Hbin _Map', undef, undef, \&toggle_hbin_map, TRUE],
+);
+$default_actions->add_toggle_actions(\@toggle_actions, undef);
+
+$uimanager->insert_action_group($default_actions, 0);
+$uimanager->insert_action_group($file_actions, 0);
+$uimanager->insert_action_group($hbin_actions, 0);
+$uimanager->insert_action_group($owner_actions, 0);
+
+$file_actions->set_sensitive(FALSE);
+$hbin_actions->set_sensitive(FALSE);
+$owner_actions->set_sensitive(FALSE);
+
+my $ui_info = <<END_OF_UI;
+<ui>
+    <menubar name='MenuBar'>
+        <menu action='FileMenu'>
+            <menuitem action='Open'/>
+            <menuitem action='SaveHbinMap'/>
+            <menuitem action='Close'/>
+            <separator/>
+            <menuitem action='Quit'/>
+        </menu>
+        <menu action='SearchMenu'>
+            <menuitem action='Find'/>
+            <menuitem action='FindNext'/>
+            <separator/>
+            <menuitem action='GoTo'/>
+            <separator/>
+            <menuitem action='Process2'/>
+            <menuitem action='Jump'/>
+            <menuitem action='JumpBack'/>
+        </menu>
+        <menu action='ViewMenu'>
+            <menuitem action='ShowToolbar'/>
+            <menuitem action='ShowHbins'/>
+            <menuitem action='ShowHbinMap'/>
+            <separator/>
+            <menuitem action='ZoomIn'/>
+            <menuitem action='ZoomOut'/>
+            <menuitem action='ZoomFit'/>
+        </menu>
+        <menu action='HelpMenu'>
+            <menuitem action='About'/>
+        </menu>
+    </menubar>
+    <toolbar name='ToolBar'>
+        <toolitem action='Open'/>
+        <toolitem action='Close'/>
+        <separator/>
+        <toolitem action='Find'/>
+        <toolitem action='GoTo'/>
+        <toolitem action='Jump'/>
+        <separator/>
+        <toolitem action='Quit'/>
+    </toolbar>
+    <accelerator action='FindNext2'/>
+    <accelerator action='ZoomIn2'/>
+</ui>
+END_OF_UI
+
+$uimanager->add_ui_from_string($ui_info);
+
+my $menubar = $uimanager->get_widget('/MenuBar');
+my $toolbar = $uimanager->get_widget('/ToolBar');
+
+### STATUSBAR
+
+my $statusbar = Gtk2::Statusbar->new;
+
+### VBOX
+
+my $main_vbox = Gtk2::VBox->new(FALSE, 0);
+$main_vbox->pack_start($menubar, FALSE, FALSE, 0);
+$main_vbox->pack_start($toolbar, FALSE, FALSE, 0);
+$main_vbox->pack_start($hbox, TRUE, TRUE, 0);
+$main_vbox->pack_start($statusbar, FALSE, FALSE, 0);
+
+### WINDOW
+
+my $window = Gtk2::Window->new;
+$window->set_default_size($window_width, $window_height);
+$window->set_position('center');
+$window->signal_connect(destroy => sub { Gtk2->main_quit });
+$window->add($main_vbox);
+$window->add_accel_group($uimanager->get_accel_group);
+$window->set_title($script_name);
+$window->show_all;
+
+my $filename = shift;
+if (defined $filename && -r $filename) {
+    load_file($filename);
+}
+
+### GLOBALS
+
+my $registry;
+
+my $last_dir;
+
+my $find_param = '';
+my $find_iter;
+my $find_hbin;
+my $find_hbin_iter;
+my $find_entry_iter;
+
+my $entry_source; # will be a registry for Win95, a hbin for WinNT
+
+my $map_width;
+my $map_height;
+my $map_pixbuf;
+my $map_scale = 6;
+
+my %owners = ();
+
+my @jump_history = ();
+
+Gtk2->main;
+
+###############################################################################
+
+sub load_entries {
+    return if !defined $entry_source;
+
+    $entry_store->clear;
+
+    # $entry_source is either a WinNT::Hbin or a Win95::File.
+    my $entry_iter = $entry_source->get_entry_iterator;
+    while (my $entry = $entry_iter->get_next) {
+        my $iter = $entry_store->append;
+
+        my $tag = $entry->get_tag;
+        my $offset = $entry->get_offset;
+
+        # colorize each row according to its tag (NT only)
+        # '#FF8080' red, sat 50%
+        # '#80FFFF' cyan, sat 50%
+        # '#80FF80' green, sat 50%
+        # '#FF80FF' magenta, sat 50%
+        my $color = '#E6E6E6';
+        if ($tag eq 'nk') {
+            $color = '#FF8080';
+        }
+        elsif ($tag eq 'sk') {
+            $color = '#80FFFF';
+        }
+        elsif ($tag eq 'vk') {
+            $color = '#80FF80';
+        }
+        elsif ($tag =~ /(lf|lh|li|ri)/) {
+            $color = '#FF80FF';
+        }
+
+        $entry_store->set($iter,
+            COLUMN_ENTRY_OFFSET, sprintf("0x%x", $offset),
+            COLUMN_ENTRY_LENGTH, $entry->get_length,
+            COLUMN_ENTRY_TAG, $tag,
+            COLUMN_ENTRY_IN_USE, $entry->is_allocated,
+            COLUMN_ENTRY_COLOR, $color,
+            COLUMN_ENTRY_OBJECT, $entry);
+
+        # If owners have been identified, add this to the Owner column
+        if (exists $owners{$offset}) {
+            my $desc = "";
+            my $num_referrers = @{$owners{$offset}};
+            if ($num_referrers == 1) {
+                my $rtype = $owners{$offset}[0]{type};
+                my $roffset = $owners{$offset}[0]{offset};
+                $desc = sprintf "$rtype @ 0x%x", $roffset;
+                if ($roffset == $offset) {
+                    $desc = "Self";
+                }
+            }
+            else {
+                $desc = "$num_referrers referrers";
+            }
+            $entry_store->set($iter,
+                COLUMN_ENTRY_USED_BY, $desc);
+        }
+    }
+}
+
+sub hbin_selection_changed {
+    my ($model, $iter) = $hbin_selection->get_selected;
+    if (!defined $model || !defined $iter) {
+        return;
+    }
+
+    my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT);
+    $entry_source = $hbin; # set global entry source
+
+    my $str = $hbin->parse_info . "\n";
+    $str .= $hbin->unparsed;
+
+    $text_buffer->set_text($str);
+
+    $statusbar->pop(0);
+    $statusbar->push(0, sprintf("Hbin @ 0x%x", $hbin->get_offset));
+
+    load_entries();
+
+    make_hbin_map();
+    zoom_fit();
+
+    $notebook->set_current_page($hbin_map_page);
+}
+
+sub entry_selection_changed {
+    my ($model, $iter) = $entry_selection->get_selected;
+    if (!defined $model || !defined $iter) {
+        return;
+    }
+
+    my $entry = $model->get($iter, COLUMN_ENTRY_OBJECT);
+    my $offset = $entry->get_offset;
+
+    my $desc;
+    if ($entry->looks_like_key) {
+        $desc = sprintf "Key @ 0x%x", $offset;
+    }
+    elsif ($entry->looks_like_value) {
+        $desc = sprintf "Value @ 0x%x", $offset;
+    }
+    elsif ($entry->looks_like_security) {
+        $desc = sprintf "Security @ 0x%x", $offset;
+    }
+    else {
+        $desc = sprintf "Entry @ 0x%x", $offset;
+    }
+
+    my $str = "$desc\n\n"
+            . $entry->parse_info . "\n"
+            . $entry->unparsed . "\n";
+
+    my $status = $desc;
+
+    if ($entry->looks_like_key) {
+        $str .= $entry->as_string . "\n\n";
+        $status .= ' "' . $entry->get_name . '"';
+    }
+    elsif ($entry->looks_like_value) {
+        my $name = $entry->get_name;
+        $name = '(Default)' if $name eq '';
+        my $type_as_string = $entry->get_type_as_string;
+
+        $str .= "$name ($type_as_string)\n\n";
+        $status .= ' "' . $entry->get_name . '"';
+    }
+
+    $text_buffer->set_text($str);
+    $notebook->set_current_page($info_page);
+
+    $statusbar->pop(0);
+    $statusbar->push(0, $status);
+}
+
+sub show_message {
+    my $type = shift;
+    my $message = shift;
+
+    my $dialog = Gtk2::MessageDialog->new(
+        $window,
+        'destroy-with-parent',
+        $type,
+        'ok',
+        $message,
+    );
+    $dialog->set_title(ucfirst $type);
+    $dialog->run;
+    $dialog->destroy;
+}
+
+sub load_file {
+    my $filename = shift;
+
+    my ($name, $path) = fileparse($filename);
+
+    close_file();
+
+    if (!-r $filename) {
+        show_message('error', "Unable to open '$name'.");
+    }
+    elsif ($registry = Parse::Win32Registry->new($filename)) {
+        if (my $root_key = $registry->get_root_key) {
+            $window->set_title("$name - $script_name");
+
+            my $hbin_iter = $registry->get_hbin_iterator;
+            if (defined $hbin_iter) { # WinNT
+                # load hbins
+                while (my $hbin = $hbin_iter->get_next) {
+                    my $iter = $hbin_store->append;
+                    $hbin_store->set($iter,
+                        COLUMN_HBIN_OFFSET, sprintf("0x%x", $hbin->{_offset}),
+                        COLUMN_HBIN_OBJECT, $hbin);
+                }
+                show_hbin_functions(TRUE);
+            }
+            else { # Win95
+                $entry_source = $registry;
+                load_entries();
+                show_hbin_functions(FALSE);
+            }
+            $file_actions->set_sensitive(TRUE);
+        }
+    }
+    else {
+        show_message('error', "'$name' is not a registry file.");
+    }
+}
+
+sub choose_file {
+    my ($title, $type, $suggested_name) = @_;
+
+    my $file_chooser = Gtk2::FileChooserDialog->new(
+        $title,
+        undef,
+        $type,
+        'gtk-cancel' => 'cancel',
+        'gtk-ok' => 'ok',
+    );
+    if ($type eq 'save') {
+        $file_chooser->set_current_name($suggested_name);
+    }
+    if (defined $last_dir) {
+        $file_chooser->set_current_folder($last_dir);
+    }
+    my $response = $file_chooser->run;
+
+    my $filename;
+    if ($response eq 'ok') {
+        $filename = $file_chooser->get_filename;
+    }
+    $last_dir = $file_chooser->get_current_folder;
+    $file_chooser->destroy;
+    return $filename;
+}
+
+sub open_file {
+    my $filename = choose_file('Select Registry File', 'open');
+    if (defined $filename) {
+        load_file($filename);
+    }
+}
+
+sub save_hbin_map {
+    return if !defined $map_pixbuf;
+
+    my ($model, $iter) = $hbin_selection->get_selected;
+    if (!defined $model || !defined $iter) {
+        return;
+    }
+
+    my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT);
+
+    my $name = sprintf "hbin\@0x%x.jpg", $hbin->get_offset;
+
+    my $filename = choose_file('Save Hbin Map', 'save', $name);
+    if (defined $filename) {
+        my $pixbuf = $map_pixbuf->scale_simple($map_width * MAX_SCALE,
+                                               $map_height * MAX_SCALE,
+                                               'tiles');
+        $pixbuf->save($filename, 'jpeg', quality => 100);
+    }
+}
+
+sub close_file {
+    $hbin_store->clear;
+    $entry_store->clear;
+    $registry = undef;
+    $entry_source = undef;
+    $hbin_image->clear;
+    $text_buffer->set_text('');
+    $statusbar->pop(0);
+    $map_pixbuf = undef;
+    %owners = ();
+    @jump_history = ();
+    $file_actions->set_sensitive(FALSE);
+    $hbin_actions->set_sensitive(FALSE);
+    $owner_actions->set_sensitive(FALSE);
+}
+
+sub quit {
+    $window->destroy;
+}
+
+sub about {
+    Gtk2->show_about_dialog(undef,
+        'program-name' => $script_name,
+        'version' => $Parse::Win32Registry::VERSION,
+        'copyright' => 'Copyright (c) 2009 James Macfarlane',
+        'comments' => 'GTK2 Registry Scope for the Parse::Win32Registry module',
+    );
+}
+
+sub toggle_hbins {
+    my ($toggle_action) = @_;
+    if ($toggle_action->get_active) {
+        $scrolled_hbin_view->show;
+    }
+    else {
+        $scrolled_hbin_view->hide;
+    }
+}
+
+sub toggle_hbin_map {
+    my ($toggle_action) = @_;
+    if ($toggle_action->get_active) {
+        $scrolled_hbin_image->show;
+    }
+    else {
+        $scrolled_hbin_image->hide;
+    }
+}
+
+sub toggle_toolbar {
+    my ($toggle_action) = @_;
+    if ($toggle_action->get_active) {
+        $toolbar->show;
+    }
+    else {
+        $toolbar->hide;
+    }
+}
+
+sub make_hbin_map {
+    my ($model, $iter) = $hbin_selection->get_selected;
+    if (!defined $model || !defined $iter) {
+        return;
+    }
+
+    my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT);
+
+    my $hbin_length = $hbin->get_length;
+
+    # Find the nearest power of 2 larger than hbin length
+    my $n = $hbin_length;
+    if ($n > 0) {
+        $n--;
+        foreach (1..31) {
+            $n |= $n >> $_;
+        }
+        $n++;
+    }
+
+    # Find the squarest map dimensions
+    my $h = 1;
+    my $w = $n;
+    while ($h < $w) {
+        $h *= 2;
+        $w /= 2;
+    }
+
+    $map_width = $w;
+    $map_height = int($hbin_length / $w);
+
+    # Initialise the byte sequence with the hbin header
+    my $data = pack "C*",
+        map { ($_, $_, $_, 255) } unpack("C*", $hbin->get_raw_bytes);
+
+    # Build the hbin map using colorised byte sequences
+    my $entry_iter = $hbin->get_entry_iterator;
+    while (my $entry = $entry_iter->get_next) {
+        my $tag = $entry->get_tag;
+        if ($tag eq 'nk') {
+            $data .= pack "C*",
+                map { ($_, 0, 0, 255) } unpack("C*", $entry->get_raw_bytes);
+        }
+        elsif ($tag eq 'vk') {
+            $data .= pack "C*",
+                map { (0, $_, 0, 255) } unpack("C*", $entry->get_raw_bytes);
+        }
+        elsif ($tag eq 'sk') {
+            $data .= pack "C*",
+                map { (0, $_, $_, 255) } unpack("C*", $entry->get_raw_bytes);
+        }
+        elsif ($tag =~ /(lf|lh|ri|li)/) {
+            $data .= pack "C*",
+                map { ($_, 0, $_, 255) } unpack("C*", $entry->get_raw_bytes);
+        }
+        else {
+            $data .= pack "C*",
+                map { ($_, $_, $_, 255) } unpack("C*", $entry->get_raw_bytes);
+        }
+    }
+
+    my $padding = ($map_width * $map_height) - int(length($data) / 4);
+    $data .= pack "C*", (255, 255, 255, 128) x $padding;
+
+    $map_pixbuf = Gtk2::Gdk::Pixbuf->new_from_data(
+        $data, 'rgb', 1, 8, $map_width, $map_height, $map_width * 4);
+}
+
+sub hbin_map_click {
+    my ($widget, $event) = @_;
+
+    my ($x, $y) = ($event->x, $event->y);
+
+    my @alloc = $hbin_image->allocation->values;
+    my $alloc_width = $alloc[2];
+    my $alloc_height = $alloc[3];
+    my $map_x = $x - ($alloc_width - $map_width*$map_scale) / 2;
+    my $map_y = $y - ($alloc_height - $map_height*$map_scale) / 2;
+    $map_x /= $map_scale;
+    $map_y /= $map_scale;
+    $map_x = int($map_x);
+    $map_y = int($map_y);
+
+    if (($map_x >= 0 && $map_x < $map_width) &&
+        ($map_y >= 0 && $map_y < $map_height)) {
+        my $offset = ($map_y * $map_width) + $map_x;
+
+        my ($model, $iter) = $hbin_selection->get_selected;
+        if (!defined $model || !defined $iter) {
+            return;
+        }
+        my $hbin = $model->get($iter, COLUMN_HBIN_OBJECT);
+        $offset += $hbin->get_offset;
+
+        if ($offset < ($hbin->get_offset + 0x20)) {
+            # First 32 bytes comprise the hbin header
+            go_to_hbin($offset);
+        }
+        else {
+            go_to_entry($offset);
+            $notebook->set_current_page($hbin_map_page);
+        }
+    }
+}
+
+sub show_owner_functions {
+
+}
+
+sub show_hbin_functions {
+    my $state = shift;
+
+    my $show_hbins_toggle
+        = $uimanager->get_widget('/MenuBar/ViewMenu/ShowHbins');
+
+    my $show_hbin_map_toggle
+        = $uimanager->get_widget('/MenuBar/ViewMenu/ShowHbinMap');
+
+    if ($state) {
+        $hbin_actions->set_sensitive(TRUE);
+        $show_hbins_toggle->set_active(TRUE);
+        $show_hbin_map_toggle->set_active(TRUE);
+    }
+    else {
+        $hbin_actions->set_sensitive(FALSE);
+        $show_hbins_toggle->set_active(FALSE);
+        $show_hbin_map_toggle->set_active(FALSE);
+    }
+}
+
+sub draw_scaled_map {
+    return if !defined $map_pixbuf;
+
+    my $scale = shift || $map_scale; # optional override
+
+    my $pixbuf = $map_pixbuf->scale_simple($map_width * $scale,
+                                           $map_height * $scale,
+                                           'tiles');
+    $hbin_image->set_from_pixbuf($pixbuf);
+}
+
+sub zoom_in {
+    return if !defined $map_pixbuf;
+
+    $map_scale++;
+    $map_scale = MAX_SCALE if $map_scale > MAX_SCALE;
+    draw_scaled_map();
+    $notebook->set_current_page($hbin_map_page);
+}
+
+sub zoom_out {
+    return if !defined $map_pixbuf;
+
+    $map_scale--;
+    $map_scale = 1 if $map_scale < 1;
+    draw_scaled_map();
+    $notebook->set_current_page($hbin_map_page);
+}
+
+sub zoom_fit {
+    return if !defined $map_pixbuf;
+
+    my $allocation = $scrolled_hbin_image->allocation;
+    my ($x, $y, $available_width, $available_height) = $allocation->values;
+
+    for (my $scale = MAX_SCALE; $scale > 0; $scale--) {
+        my $width = $map_width * $scale;
+        my $height = $map_height * $scale;
+
+        if ($width < $available_width && $height < $available_height) {
+            $map_scale = $scale;
+            last;
+        }
+    }
+    draw_scaled_map();
+    $notebook->set_current_page($hbin_map_page);
+}
+
+sub go_to_hbin {
+    my ($offset) = @_;
+
+    my $iter = $hbin_store->get_iter_first;
+    while (defined $iter) {
+        my $hbin = $hbin_store->get($iter, COLUMN_HBIN_OBJECT);
+        my $hbin_start = $hbin->get_offset;
+        my $hbin_end = $hbin_start + $hbin->get_length;
+        if ($offset >= $hbin_start && $offset < $hbin_end) {
+            my $tree_path = $hbin_store->get_path($iter);
+            $hbin_view->expand_to_path($tree_path);
+            $hbin_view->scroll_to_cell($tree_path);
+            $hbin_view->set_cursor($tree_path);
+            $window->set_focus($hbin_view);
+            return;
+        }
+        $iter = $hbin_store->iter_next($iter);
+    }
+}
+
+sub go_to_entry {
+    my ($offset) = @_;
+
+    my $iter = $entry_store->get_iter_first;
+    while (defined $iter) {
+        my $entry = $entry_store->get($iter, COLUMN_ENTRY_OBJECT);
+        my $entry_start = $entry->get_offset;
+        my $entry_end = $entry_start + $entry->get_length;
+        if ($offset >= $entry_start && $offset < $entry_end) {
+            my $tree_path = $entry_store->get_path($iter);
+            $entry_view->expand_to_path($tree_path);
+            $entry_view->scroll_to_cell($tree_path);
+            $entry_view->set_cursor($tree_path);
+            $window->set_focus($entry_view);
+            return;
+        }
+        $iter = $entry_store->iter_next($iter);
+    }
+}
+
+sub find_next {
+    if (!defined $find_param || !defined $find_entry_iter) {
+        return;
+    }
+
+    # Build find next dialog
+    my $label = Gtk2::Label->new;
+    $label->set_text("Searching registry entries...");
+    my $dialog = Gtk2::Dialog->new('Find',
+        $window,
+        'modal',
+        'gtk-cancel' => 'cancel',
+    );
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
+    $dialog->set_default_response('cancel');
+    $dialog->show_all;
+
+    my $id = Glib::Idle->add(sub {
+        while (1) {
+            my $entry = $find_entry_iter->get_next;
+            if (defined $entry) {
+                my $found = 0;
+
+                if (index($entry->get_raw_bytes, $find_param) > -1) {
+                    $found = 1;
+                }
+                else {
+                    my $uni_find_param = encode("UCS-2LE", $find_param);
+                    if (index($entry->get_raw_bytes, $uni_find_param) > -1) {
+                        $found = 1;
+                    }
+                }
+
+                if ($found) {
+                    if (defined $find_hbin) {
+                        go_to_hbin($find_hbin->get_offset);
+                    }
+                    go_to_entry($entry->get_offset);
+
+                    $dialog->response(50);
+                    return FALSE;
+                }
+
+                return TRUE; # continue searching...
+            }
+            else { # no more entries...?
+                if (defined $find_hbin_iter) {
+                    $find_hbin = $find_hbin_iter->get_next;
+                    if (defined $find_hbin) {
+                        $find_entry_iter = $find_hbin->get_entry_iterator;
+                        if (!defined $find_entry_iter) {
+                            last; # no entry iterator... (WinNT)
+                        }
+                    }
+                    else {
+                        last; # no more hbins... (WinNT)
+                    }
+                }
+                else {
+                    last; # no more entries... (Win95)
+                }
+            }
+        }
+
+        $dialog->response('ok');
+        return FALSE;
+
+    });
+
+    my $response = $dialog->run;
+    $dialog->destroy;
+
+    if ($response eq 'cancel' || $response eq 'delete-event') {
+        Glib::Source->remove($id);
+    }
+    elsif ($response eq 'ok') {
+        show_message('info', 'Finished searching.');
+    }
+}
+
+sub find {
+    return if !defined $registry;
+
+    my $entry = Gtk2::Entry->new;
+    $entry->set_text($find_param);
+    $entry->set_activates_default(TRUE);
+    my $dialog = Gtk2::Dialog->new('Find',
+        $window,
+        'modal',
+        'gtk-cancel' => 'cancel',
+        'gtk-ok' => 'ok',
+    );
+    $dialog->vbox->pack_start($entry, TRUE, TRUE, 5);
+    $dialog->set_default_response('ok');
+    $dialog->show_all;
+
+    my $response = $dialog->run;
+    $dialog->destroy;
+
+    if ($response eq 'ok') {
+        $find_param = $entry->get_text;
+        if ($find_param ne '') {
+            # WinNT: initialise hbin_iter, hbin, entry_iter
+            # Win95: initialise entry_iter
+            $find_hbin_iter = $registry->get_hbin_iterator;
+            if (defined $find_hbin_iter) { # WinNT
+                $find_hbin = $find_hbin_iter->get_next;
+                $find_entry_iter = $find_hbin->get_entry_iterator;
+            }
+            else { # Win95
+                $find_entry_iter = $registry->get_entry_iterator;
+            }
+            find_next;
+        }
+    }
+}
+
+sub go_to_offset {
+    return if !defined $registry;
+
+    my $entry = Gtk2::Entry->new;
+    $entry->set_activates_default(TRUE);
+    my $dialog = Gtk2::Dialog->new('Go To Offset',
+        $window,
+        'modal',
+        'gtk-cancel' => 'cancel',
+        'gtk-ok' => 'ok',
+    );
+    $dialog->vbox->pack_start($entry, TRUE, TRUE, 5);
+    $dialog->set_default_response('ok');
+    $dialog->show_all;
+
+    $entry->prepend_text("0x");
+    $entry->set_position(-1);
+
+    my $response = $dialog->run;
+    $dialog->destroy;
+
+    if ($response ne 'ok') {
+        return;
+    }
+
+    my $offset;
+    eval {
+        my $answer = $entry->get_text;
+        if ($answer =~ m/^0x[\da-fA-F]+\s*$/) {
+            $offset = int(eval $answer);
+        }
+    };
+
+    if (defined $offset && $offset < $registry->get_length) {
+        go_to_hbin($offset);
+        go_to_entry($offset);
+    }
+}
+
+sub jump_to_owner {
+    my ($model, $iter) = $entry_selection->get_selected;
+    if (!defined $model || !defined $iter) {
+        return;
+    }
+
+    if (!%owners) {
+        show_message('error', "'Identify Entry Owners' has not been run.");
+        return;
+    }
+
+    my $entry = $model->get($iter, COLUMN_ENTRY_OBJECT);
+    my $offset = $entry->get_offset;
+
+    if (exists $owners{$offset}) {
+        my $num_referrers = @{$owners{$offset}};
+        if ($num_referrers >= 1) {
+            my $roffset = $owners{$offset}[0]{offset};
+            if ($roffset != $offset) {
+                push @jump_history, $offset;
+                go_to_hbin($roffset);
+                go_to_entry($roffset);
+            }
+        }
+    }
+}
+
+sub jump_back {
+    if (@jump_history) {
+        my $offset = pop @jump_history;
+        go_to_hbin($offset);
+        go_to_entry($offset);
+    }
+}
+
+###############################################################################
+
+sub scan_entries {
+    return if !defined $registry;
+
+    my $label = Gtk2::Label->new;
+    $label->set_text("Searching registry...");
+    my $dialog = Gtk2::Dialog->new('Find',
+        $window,
+        'modal',
+        'gtk-cancel' => 'cancel',
+    );
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
+    $dialog->set_default_response('cancel');
+    $dialog->show_all;
+
+    my $entry_iter;
+    my $hbin_iter = $registry->get_hbin_iterator;
+    if (defined $hbin_iter) { # WinNT
+        my $hbin = $hbin_iter->get_next;
+        $entry_iter = $hbin->get_entry_iterator;
+    }
+    else { # Win95
+        $entry_iter = $registry->get_entry_iterator;
+    }
+
+    my $id = Glib::Idle->add(sub {
+        while (1) {
+            my $entry = $entry_iter->get_next;
+            if (defined $entry) {
+
+                # do something with entry...
+                printf "processing entry 0x%x...\n", $entry->get_offset;
+
+                return TRUE; # continue searching...
+            }
+            else { # no more entries...?
+                if (defined $hbin_iter) {
+                    my $hbin = $hbin_iter->get_next;
+                    if (defined $hbin) {
+                        $entry_iter = $hbin->get_entry_iterator;
+                        if (!defined $entry_iter) {
+                            last; # no more entries... (WinNT)
+                        }
+                    }
+                    else {
+                        last; # no more hbins... (WinNT)
+                    }
+                }
+                else {
+                    last; # no more entries... (Win95)
+                }
+            }
+        }
+
+        $dialog->response('ok');
+        show_message('info', 'Finished long running process.');
+        return FALSE;
+    });
+
+    my $response = $dialog->run;
+    $dialog->destroy;
+
+    if ($response eq 'cancel' || $response eq 'delete-event') {
+        Glib::Source->remove($id);
+    }
+}
+
+sub scan_tree {
+    return if !defined $registry;
+
+    %owners = ();
+
+    my $label = Gtk2::Label->new;
+    $label->set_text("Scanning registry to identify entry owners...");
+    my $dialog = Gtk2::Dialog->new('Scanning',
+        $window,
+        'modal',
+        'gtk-cancel' => 'cancel',
+    );
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
+    $dialog->set_default_response('cancel');
+    $dialog->show_all;
+
+    my $root_key = $registry->get_root_key;
+    my $subtree_iter = $root_key->get_subtree_iterator;
+    my $value_iter;
+
+    my $id = Glib::Idle->add(sub {
+        if (defined $value_iter) {
+            my $value = $value_iter->get_next;
+            if (defined $value) {
+                my $name = $value->get_name;
+                $name = '(Default)' if $name eq '';
+
+                my $self_offset = $value->get_offset;
+                foreach my $offset ($value->get_associated_offsets) {
+                    push @{$owners{$offset}},
+                        { type => "Value", offset => $self_offset };
+                }
+
+                return TRUE; # continue processing
+            }
+        }
+        if (defined $subtree_iter) {
+            my $key = $subtree_iter->get_next;
+            if (defined $key) {
+                my $self_offset = $key->get_offset;
+                foreach my $offset ($key->get_associated_offsets) {
+                    push @{$owners{$offset}},
+                        { type => "Key", offset => $self_offset };
+                }
+
+                # Fetch new value iterator for new key
+                $value_iter = $key->get_value_iterator;
+                return TRUE; # continue processing
+            }
+        }
+
+        $dialog->response('ok');
+        return FALSE; # stop processing
+    });
+
+    my $response = $dialog->run;
+    $dialog->destroy;
+
+    if ($response eq 'cancel' || $response eq 'delete-event') {
+        Glib::Source->remove($id);
+        %owners = ();
+    }
+    elsif ($response eq 'ok') {
+        $entry_column4->set_visible(TRUE);
+        show_message('info', "Finished identifying entry owners.\n"
+                           . "Check the Owner column for details.");
+        $owner_actions->set_sensitive(TRUE);
+        load_entries();
+    }
+}
+
diff --git a/bin/regview.pl b/bin/regview.pl
index e2cbc54..a5374ba 100755
--- a/bin/regview.pl
+++ b/bin/regview.pl
@@ -5,9 +5,15 @@ use warnings;
 use Glib ':constants';
 use Gtk2 -init;
 
+my $screen = Gtk2::Gdk::Screen->get_default;
+my $window_width = $screen->get_width * 0.9;
+my $window_height = $screen->get_height * 0.8;
+$window_width = 1100 if $window_width > 1100;
+$window_height = 900 if $window_height > 900;
+
 use File::Basename;
 use File::Spec;
-use Parse::Win32Registry 0.50 qw(hexdump);
+use Parse::Win32Registry 0.51 qw(hexdump);
 
 binmode(STDOUT, ':utf8');
 
@@ -37,6 +43,15 @@ for (my $col = 0; $col < @list_column_names; $col++) {
         'text', $col);
     $list_view->append_column($column);
     $column->set_resizable(TRUE);
+    $list_store->set_sort_func($col, sub {
+        my ($model, $itera, $iterb, $col) = @_;
+        my $a = $model->get($itera, $col);
+        my $b = $model->get($iterb, $col);
+        $a = '' if !defined $a;
+        $b = '' if !defined $b;
+        return $a cmp $b;
+    }, $col);
+    $column->set_sort_column_id($col);
 }
 $list_view->set_rules_hint(TRUE);
 
@@ -96,6 +111,15 @@ for (my $col = 0; $col < @tree_column_names; $col++) {
     $column->set_resizable(TRUE);
     $tree_view->append_column($column);
     push @tree_columns, $column;
+    $tree_store->set_sort_func($col, sub {
+        my ($model, $itera, $iterb, $col) = @_;
+        my $a = $model->get($itera, $col);
+        my $b = $model->get($iterb, $col);
+        $a = '' if !defined $a;
+        $b = '' if !defined $b;
+        return $a cmp $b;
+    }, $col);
+    $column->set_sort_column_id($col);
 }
 $tree_view->set_rules_hint(TRUE);
 
@@ -120,7 +144,7 @@ my $hpaned = Gtk2::HPaned->new;
 $hpaned->pack1($scrolled_tree_view, FALSE, FALSE);
 $hpaned->pack2($vpaned, TRUE, FALSE);
 
-$hpaned->set_position(200);
+$hpaned->set_position($window_width * 0.3);
 
 ### MENU
 
@@ -133,15 +157,15 @@ my $accel_group = Gtk2::AccelGroup->new;
 # File Menu
 my $open_menuitem = Gtk2::MenuItem->new('_Open');
 $open_menuitem->signal_connect('activate' => \&open_file);
-$open_menuitem->add_accelerator('activate' => $accel_group,
+$open_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{O}, ['control-mask'], ['visible', 'locked']);
 my $close_menuitem = Gtk2::MenuItem->new('_Close');
 $close_menuitem->signal_connect('activate' => \&close_file);
-$close_menuitem->add_accelerator('activate' => $accel_group,
+$close_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{W}, ['control-mask'], ['visible', 'locked']);
 my $quit_menuitem = Gtk2::MenuItem->new('_Quit');
 $quit_menuitem->signal_connect('activate' => \&quit);
-$quit_menuitem->add_accelerator('activate' => $accel_group,
+$quit_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{Q}, ['control-mask'], ['visible', 'locked']);
 
 my $file_menu = Gtk2::Menu->new;
@@ -156,7 +180,7 @@ my $recent_separator; # placeholder, becomes separator for recent files
 # Edit Menu
 my $copy_menuitem = Gtk2::MenuItem->new('_Copy key path');
 $copy_menuitem->signal_connect('activate' => \&copy_key_path);
-$copy_menuitem->add_accelerator('activate' => $accel_group,
+$copy_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{C}, ['control-mask'], ['visible', 'locked']);
 
 my $edit_menu = Gtk2::Menu->new;
@@ -165,38 +189,27 @@ $edit_menu->append($copy_menuitem);
 # Search Menu
 my $find_menuitem = Gtk2::MenuItem->new('_Find');
 $find_menuitem->signal_connect('activate' => \&find);
-$find_menuitem->add_accelerator('activate' => $accel_group,
+$find_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{F}, ['control-mask'], ['visible', 'locked']);
 my $find_next_menuitem = Gtk2::MenuItem->new('Find Next');
 $find_next_menuitem->signal_connect('activate' => \&find_next);
-$find_next_menuitem->add_accelerator('activate' => $accel_group,
+$find_next_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{G}, ['control-mask'], ['visible', 'locked']);
+$find_next_menuitem->add_accelerator('activate', $accel_group,
+    $Gtk2::Gdk::Keysyms{F3}, [], ['visible', 'locked']);
 
 my $search_menu = Gtk2::Menu->new;
 $search_menu->append($find_menuitem);
 $search_menu->append($find_next_menuitem);
 
-# Test Menu
-my $dump_loaded_keys_menuitem = Gtk2::MenuItem->new('Dump loaded keys');
-$dump_loaded_keys_menuitem->signal_connect('activate' => \&dump_loaded_keys);
-my $dump_settings_menuitem = Gtk2::MenuItem->new('Dump settings');
-$dump_settings_menuitem->signal_connect('activate' => \&dump_settings);
-my $dump_bookmarks_menuitem = Gtk2::MenuItem->new('Dump bookmarks');
-$dump_bookmarks_menuitem->signal_connect('activate' => \&dump_bookmarks);
-
-my $test_menu = Gtk2::Menu->new;
-$test_menu->append($dump_loaded_keys_menuitem);
-$test_menu->append($dump_bookmarks_menuitem);
-$test_menu->append($dump_settings_menuitem);
-
 # Bookmarks Menu
 my $add_bookmark_menuitem = Gtk2::MenuItem->new('_Add Bookmark');
 $add_bookmark_menuitem->signal_connect('activate' => \&add_bookmark);
-$add_bookmark_menuitem->add_accelerator('activate' => $accel_group,
+$add_bookmark_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{D}, ['control-mask'], ['visible', 'locked']);
 my $edit_bookmarks_menuitem = Gtk2::MenuItem->new('_Edit Bookmarks');
 $edit_bookmarks_menuitem->signal_connect('activate' => \&edit_bookmarks);
-$edit_bookmarks_menuitem->add_accelerator('activate' => $accel_group,
+$edit_bookmarks_menuitem->add_accelerator('activate', $accel_group,
     $Gtk2::Gdk::Keysyms{B}, ['control-mask'], ['visible', 'locked']);
 
 my $bookmarks_menu = Gtk2::Menu->new;
@@ -204,6 +217,21 @@ $bookmarks_menu->append($add_bookmark_menuitem);
 $bookmarks_menu->append($edit_bookmarks_menuitem);
 $bookmarks_menu->append(Gtk2::SeparatorMenuItem->new);
 
+# Reports Menu
+my $view_report_menuitem = Gtk2::MenuItem->new('View Bookmark Report');
+$view_report_menuitem->signal_connect('activate' => \&view_report);
+$view_report_menuitem->add_accelerator('activate', $accel_group,
+    $Gtk2::Gdk::Keysyms{R}, ['control-mask'], ['visible', 'locked']);
+#my $dump_loaded_keys_menuitem = Gtk2::MenuItem->new('Dump loaded keys');
+#$dump_loaded_keys_menuitem->signal_connect('activate' => \&dump_loaded_keys);
+#my $dump_settings_menuitem = Gtk2::MenuItem->new('Dump settings');
+#$dump_settings_menuitem->signal_connect('activate' => \&dump_settings);
+
+my $reports_menu = Gtk2::Menu->new;
+$reports_menu->append($view_report_menuitem);
+#$reports_menu->append($dump_loaded_keys_menuitem);
+#$reports_menu->append($dump_settings_menuitem);
+
 # Help Menu
 my $about_menuitem = Gtk2::MenuItem->new('_About');
 $about_menuitem->signal_connect('activate' => \&about);
@@ -224,14 +252,14 @@ my $search_menuitem = Gtk2::MenuItem->new('_Search');
 $search_menuitem->set_submenu($search_menu);
 $menubar->append($search_menuitem);
 
-#my $test_menuitem = Gtk2::MenuItem->new('_Test');
-#$test_menuitem->set_submenu($test_menu);
-#$menubar->append($test_menuitem);
-
 my $bookmarks_menuitem = Gtk2::MenuItem->new('_Bookmarks');
 $bookmarks_menuitem->set_submenu($bookmarks_menu);
 $menubar->append($bookmarks_menuitem);
 
+my $reports_menuitem = Gtk2::MenuItem->new('_Reports');
+$reports_menuitem->set_submenu($reports_menu);
+$menubar->append($reports_menuitem);
+
 my $help_menuitem = Gtk2::MenuItem->new('_Help');
 $help_menuitem->set_submenu($help_menu);
 $menubar->append($help_menuitem);
@@ -242,7 +270,7 @@ my $statusbar = Gtk2::Statusbar->new;
 
 ### VBOX
 
-my $main_vbox = Gtk2::VBox->new;
+my $main_vbox = Gtk2::VBox->new(FALSE, 0);
 $main_vbox->pack_start($menubar, FALSE, FALSE, 0);
 $main_vbox->pack_start($hpaned, TRUE, TRUE, 0);
 $main_vbox->pack_start($statusbar, FALSE, FALSE, 0);
@@ -250,7 +278,7 @@ $main_vbox->pack_start($statusbar, FALSE, FALSE, 0);
 ### WINDOW
 
 my $window = Gtk2::Window->new;
-$window->set_default_size(600, 400);
+$window->set_default_size($window_width, $window_height);
 $window->set_position('center');
 $window->signal_connect(destroy => sub { Gtk2->main_quit });
 $window->signal_connect(delete_event => sub { save_settings(); return FALSE; });
@@ -274,27 +302,37 @@ sub build_bookmarks_dialog {
 
     my $bookmark_column0 = Gtk2::TreeViewColumn->new_with_attributes(
         'Bookmark', Gtk2::CellRendererText->new, 'text', 0);
+    $bookmark_column0->set_resizable(TRUE);
     $bookmark_view->append_column($bookmark_column0);
-    $bookmark_column0->set_resizable(FALSE);
 
+    my $bookmark_location_cell = Gtk2::CellRendererText->new;
     my $bookmark_column1 = Gtk2::TreeViewColumn->new_with_attributes(
-        'Location', Gtk2::CellRendererText->new, 'text', 1);
-    $bookmark_view->append_column($bookmark_column1);
+        'Location', $bookmark_location_cell, 'text', 1);
+    $bookmark_location_cell->set('ellipsize', 'end');
     $bookmark_column1->set_resizable(FALSE);
+    $bookmark_view->append_column($bookmark_column1);
 
     my $scrolled_bookmark_view = Gtk2::ScrolledWindow->new;
     $scrolled_bookmark_view->set_policy('automatic', 'automatic');
     $scrolled_bookmark_view->set_shadow_type('in');
     $scrolled_bookmark_view->add($bookmark_view);
 
+    my $label = Gtk2::Label->new;
+    $label->set_markup('<i>Drag bookmarks to reorder them</i>');
+
     my $dialog = Gtk2::Dialog->new('Edit Bookmarks', $window, 'modal',
         'gtk-remove' => 50,
         'gtk-ok' => 'ok',
     );
-    $dialog->resize(400, 200);
-    $dialog->vbox->add($scrolled_bookmark_view);
+    $dialog->resize($window_width * 0.8, $window_height * 0.8);
+    $dialog->vbox->pack_start($scrolled_bookmark_view, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($label, FALSE, FALSE, 5);
     $dialog->set_default_response('ok');
 
+    $dialog->signal_connect(delete_event => sub {
+        $dialog->hide;
+        return TRUE;
+    });
     $dialog->signal_connect(response => sub {
         my ($dialog, $response) = @_;
         if ($response eq '50') {
@@ -325,9 +363,52 @@ sub build_bookmarks_dialog {
 
 my $bookmarks_dialog = build_bookmarks_dialog;
 
+my $report_view;
+
+sub build_report_dialog {
+    $report_view = Gtk2::TextView->new;
+    $report_view->set_editable(FALSE);
+    $report_view->modify_font(Gtk2::Pango::FontDescription->from_string('monospace'));
+
+    my $text_buffer = $report_view->get_buffer;
+
+    my $scrolled_report_view = Gtk2::ScrolledWindow->new;
+    $scrolled_report_view->set_policy('automatic', 'automatic');
+    $scrolled_report_view->set_shadow_type('in');
+    $scrolled_report_view->add($report_view);
+
+    my $dialog = Gtk2::Dialog->new('Report', $window, 'modal',
+        'gtk-save' => 50,
+        'gtk-ok' => 'ok',
+    );
+    $dialog->resize($window_width * 0.8, $window_height * 0.8);
+    $dialog->vbox->add($scrolled_report_view);
+    $dialog->set_default_response('ok');
+
+    $dialog->signal_connect(delete_event => sub {
+        $dialog->hide;
+        return TRUE;
+    });
+    $dialog->signal_connect(response => sub {
+        my ($dialog, $response) = @_;
+        if ($response eq '50') {
+            save_report();
+        }
+        else {
+            $dialog->hide;
+        }
+    });
+
+    return $dialog;
+}
+
+my $report_dialog = build_report_dialog;
+
 ### GLOBALS
 
-my $find_param;
+my $search_keys = TRUE;
+my $search_values = TRUE;
+my $find_param = '';
 my $find_iter;
 
 my @recent = ();
@@ -564,29 +645,56 @@ sub load_recent {
     load_file($filename);
 }
 
-sub open_file {
+sub choose_file {
+    my ($title, $type, $suggested_name) = @_;
+
     my $file_chooser = Gtk2::FileChooserDialog->new(
-        'Select Registry File',
+        $title,
         undef,
-        'open',
+        $type,
         'gtk-cancel' => 'cancel',
         'gtk-ok' => 'ok',
     );
+    if ($type eq 'save') {
+        $file_chooser->set_current_name($suggested_name);
+    }
     if (defined $last_dir) {
         $file_chooser->set_current_folder($last_dir);
     }
-    my $filename;
     my $response = $file_chooser->run;
+
+    my $filename;
     if ($response eq 'ok') {
         $filename = $file_chooser->get_filename;
     }
     $last_dir = $file_chooser->get_current_folder;
     $file_chooser->destroy;
+    return $filename;
+}
+
+sub open_file {
+    my $filename = choose_file('Select Registry File', 'open');
     if ($filename) {
         load_file($filename);
     }
 }
 
+sub save_report {
+    if (my $filename = choose_file('Save Log File As', 'save', "report.txt")) {
+        my $basename = basename $filename;
+        if (open my $fh, ">", $filename) {
+            my $text_buffer = $report_view->get_buffer;
+            my $start_iter = $text_buffer->get_start_iter;
+            my $end_iter = $text_buffer->get_end_iter;
+            print {$fh} $text_buffer->get_text($start_iter, $end_iter, 0);
+#            show_message("info", "Report saved to '$basename'");
+        }
+        else {
+            show_message("error", "Error saving log to '$basename'");
+        }
+    }
+}
+
 sub close_file {
     $tree_store->clear;
     $list_store->clear;
@@ -621,6 +729,7 @@ sub show_message {
         'ok',
         $message,
     );
+    $dialog->set_title(ucfirst $type);
     $dialog->run;
     $dialog->destroy;
 }
@@ -628,20 +737,23 @@ sub show_message {
 sub create_bookmark_menuitem {
     my ($name, $subkey_path) = @_;
 
-        if (my $menuitem = Gtk2::MenuItem->new($name)) {
-            $bookmarks_menu->append($menuitem);
-            $bookmarks_menu->show_all;
-            if (my $iter = $bookmark_store->append) {
-                $bookmark_store->set($iter,
-                    0, $name,
-                    1, $subkey_path,
-                    2, $menuitem,
-                );
-            }
-            $menuitem->signal_connect('activate' => \&go_to_bookmark,
-                                                     $subkey_path);
+    my $display_name = $name;
+    $display_name =~ s/_/__/g;
+    if (my $menuitem = Gtk2::MenuItem->new($display_name)) {
+        $bookmarks_menu->append($menuitem);
+        $bookmarks_menu->show_all;
+        if (my $iter = $bookmark_store->append) {
+            $bookmark_store->set($iter,
+                0, $name,
+                1, $subkey_path,
+                2, $menuitem,
+            );
         }
+        $menuitem->signal_connect('activate' => \&go_to_bookmark,
+                                                 $subkey_path);
+    }
 }
+
 sub add_bookmark {
     my $iter = $tree_selection->get_selected;
     return if !defined $iter;
@@ -755,8 +867,10 @@ sub go_to_subkey {
         }
 
         if (@path_components == 0) {
+            my $parent_iter = $tree_store->iter_parent($iter);
+            my $parent_path = $tree_store->get_path($parent_iter);
+            $tree_view->expand_to_path($parent_path);
             my $tree_path = $tree_store->get_path($iter);
-            $tree_view->expand_to_path($tree_path);
             $tree_view->scroll_to_cell($tree_path);
             $tree_view->set_cursor($tree_path);
             $window->set_focus($tree_view);
@@ -765,20 +879,33 @@ sub go_to_subkey {
     }
 }
 
+sub get_search_message {
+    my $message;
+    if ($search_keys && $search_values) {
+        $message = "Searching registry keys and values...";
+    }
+    elsif ($search_keys) {
+        $message = "Searching registry keys...";
+    }
+    elsif ($search_values) {
+        $message = "Searching registry values...";
+    }
+    return $message;
+}
+
 sub find_next {
     if (!defined $find_param || !defined $find_iter) {
         return;
     }
 
-    # Build find next dialog
     my $label = Gtk2::Label->new;
-    $label->set_text("Searching registry...");
+    $label->set_text(get_search_message);
     my $dialog = Gtk2::Dialog->new('Find',
         $window,
         'modal',
         'gtk-cancel' => 'cancel',
     );
-    $dialog->vbox->pack_start($label, TRUE, TRUE, 10);
+    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
     $dialog->set_default_response('cancel');
     $dialog->show_all;
 
@@ -787,41 +914,44 @@ sub find_next {
 
         if (!defined $key) {
             $dialog->response('ok');
-            show_message('info', 'Finished searching.');
             return FALSE; # stop searching
         }
 
         # Remove root key name to get subkey path
         my $subkey_path = (split(/\\/, $key->get_path, 2))[1];
         if (!defined $subkey_path) {
-            return FALSE; # stop searching
-            # (currently get_subtree_iterator never returns the root key)
+            # go_to_subkey locates keys based on the subkey path
+            # and does not support going to the root key.
+            # Therefore if the subkey path is not defined,
+            # the subtree iterator has returned the root key,
+            # so searching it should be skipped.
+            return TRUE; # continue searching
         }
 
+        # Check value (if defined) for a match
         if (defined $value) {
-            # Check value for a match
-            my $value_name = $value->get_name;
-            if (index(lc $value_name, lc $find_param) >= 0) {
-                go_to_subkey($subkey_path);
-                go_to_value($value_name);
-                $dialog->response('ok');
-                return FALSE; # stop searching
-            }
-            else {
-                return TRUE; # continue searching
+            if ($search_values) {
+                my $value_name = $value->get_name;
+                if (index(lc $value_name, lc $find_param) >= 0) {
+                    go_to_subkey($subkey_path);
+                    go_to_value($value_name);
+                    $dialog->response(50);
+                    return FALSE; # stop searching
+                }
             }
+            return TRUE; # continue searching
         }
 
         # Check key for a match
-        my $key_name = $key->get_name;
-        if (index(lc $key_name, lc $find_param) >= 0) {
-            go_to_subkey($subkey_path);
-            $dialog->response('ok');
-            return FALSE; # stop searching
-        }
-        else {
-            return TRUE; # continue searching
+        if ($search_keys) {
+            my $key_name = $key->get_name;
+            if (index(lc $key_name, lc $find_param) >= 0) {
+                go_to_subkey($subkey_path);
+                $dialog->response(50);
+                return FALSE; # stop searching
+            }
         }
+        return TRUE; # continue searching
     });
 
     my $response = $dialog->run;
@@ -830,19 +960,40 @@ sub find_next {
     if ($response eq 'cancel' || $response eq 'delete-event') {
         Glib::Source->remove($id);
     }
+    elsif ($response eq 'ok') {
+        show_message('info', 'Finished searching.');
+    }
 }
 
 sub find {
-    # Build find dialog
+    return if !defined $tree_store->get_iter_first;
+
     my $entry = Gtk2::Entry->new;
+    $entry->set_text($find_param);
     $entry->set_activates_default(TRUE);
+    my $check1 = Gtk2::CheckButton->new('Search _Keys');
+    $check1->set_active($search_keys);
+    my $check2 = Gtk2::CheckButton->new('Search _Values');
+    $check2->set_active($search_values);
+    $check1->signal_connect(toggled => sub {
+        if (!$check1->get_active && !$check2->get_active) {
+            $check2->set_active(TRUE);
+        }
+    });
+    $check2->signal_connect(toggled => sub {
+        if (!$check1->get_active && !$check2->get_active) {
+            $check1->set_active(TRUE);
+        }
+    });
     my $dialog = Gtk2::Dialog->new('Find',
         $window,
         'modal',
         'gtk-cancel' => 'cancel',
         'gtk-ok' => 'ok',
     );
-    $dialog->vbox->pack_start($entry, TRUE, TRUE, 10);
+    $dialog->vbox->pack_start($entry, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($check1, TRUE, TRUE, 0);
+    $dialog->vbox->pack_start($check2, TRUE, TRUE, 0);
     $dialog->set_default_response('ok');
     $dialog->show_all;
 
@@ -856,6 +1007,8 @@ sub find {
     return if !defined $root_key;
 
     if ($response eq 'ok') {
+        $search_keys = $check1->get_active;
+        $search_values = $check2->get_active;
         $find_param = $entry->get_text;
         $find_iter = undef;
         if ($find_param ne '') {
@@ -939,13 +1092,16 @@ sub dump_loaded_keys {
     });
 }
 
-sub dump_bookmarks {
-    print "Dumping bookmarks:\n";
+sub view_report {
     my $root_iter = $tree_store->get_iter_first;
     if (!defined $root_iter) {
         print "(no registry file loaded)\n";
         return;
     }
+
+    my $text_buffer = $report_view->get_buffer;
+    $text_buffer->set_text('');
+
     my $root_key = $tree_store->get($root_iter, 3);
     my $iter = $bookmark_store->get_iter_first;
     while (defined $iter) {
@@ -953,8 +1109,21 @@ sub dump_bookmarks {
         my $path = $bookmark_store->get($iter, 1);
 
         if (my $key = $root_key->get_subkey($path)) {
-            print $key->get_path, "\n";
+            my $str = $key->as_string . "\n";
+            $text_buffer->insert_at_cursor($str);
+            foreach my $value ($key->get_list_of_values) {
+                my $value_name = $value->get_name;
+                $value_name = "(Default)" if $value_name eq "";
+                my $value_type = $value->get_type_as_string;
+                my $str = "$value_name ($value_type):\n";
+                $str .= hexdump($value->get_raw_data);
+                $text_buffer->insert_at_cursor($str);
+            }
+            $text_buffer->insert_at_cursor("\n");
         }
         $iter = $bookmark_store->iter_next($iter);
     }
+
+    $report_dialog->show_all;
 }
+
diff --git a/lib/Parse/Win32Registry.pm b/lib/Parse/Win32Registry.pm
index 473d840..dc587ec 100644
--- a/lib/Parse/Win32Registry.pm
+++ b/lib/Parse/Win32Registry.pm
@@ -3,7 +3,7 @@ package Parse::Win32Registry;
 use strict;
 use warnings;
 
-our $VERSION = '0.50';
+our $VERSION = '0.51';
 
 use base qw(Exporter);
 
@@ -359,6 +359,32 @@ The default value (displayed as '(Default)' by REGEDIT)
 does not actually have a name. It can obtained by supplying
 an empty string, e.g. $key->get_value('');
 
+=item $key->get_value_data( 'value name' )
+
+Returns the data for the specified value name.
+If either the value or the value's data does not exist,
+nothing will be returned.
+
+This is simply a shortcut for accessing the data of a value
+without creating an intermediate Value object.
+
+The following code:
+
+    my $value = $key->get_value('value name');
+    if (defined $value) {
+        my $data = $value->get_data;
+        if (defined $data) {
+            ...process data...
+        }
+    }
+
+can be replaced with:
+
+    my $data = $key->get_value_data('value name');
+    if (defined $data) {
+        ...process data...
+    }
+
 =item $key->get_list_of_subkeys
 
 Returns a list of Key objects representing the subkeys of the
@@ -610,7 +636,8 @@ REG_MULTI_SZ values will be returned as a list of strings when
 called in a list context,
 and as a string with each element separated by
 the list separator $" when called in a scalar context.
-(The list separator defaults to the space character.)
+(The list separator defaults to the space character.
+See perlvar for further information.)
 String data will be converted from Unicode (UCS-2LE) for Windows
 NT based registry files.
 
@@ -829,6 +856,13 @@ A SID object represents a Security Identifier.
 
 =over 4
 
+=item $sid->get_name
+
+Returns a string containing a name for the SID
+(e.g. "Administrators" for S-1-5-32-544)
+if it is a "well known" SID.
+See Microsoft Knowledge Base Article KB243330.
+
 =item $sid->as_string
 
 Returns a string containing the SID formatted for presentation.
@@ -1262,7 +1296,8 @@ but with something a little more helpful than a hex editor.
 They are not designed for pulling data out of keys and values.
 They are designed for providing technical information about keys and values.
 
-Most of these methods are demonstrated by the supplied regscan.pl script.
+Most of these methods are demonstrated by the supplied regscan.pl
+and regscope.pl scripts.
 
 =head2 Registry Object Methods
 
@@ -1487,7 +1522,7 @@ Type regclassnames.pl on its own to see the help:
 
 =head2 regcompare.pl
 
-regview.pl is a GTK+ program for comparing multiple registry files.
+regcompare.pl is a GTK+ program for comparing multiple registry files.
 It displays a tree of the registry keys and values
 highlighting the changed keys and values,
 and a table detailing the actual changes.
@@ -1500,6 +1535,8 @@ Filenames of registry files to compare can be supplied on the command line:
 
     regcompare.pl <filename1> <filename2> <filename3> ...
 
+You can of course use wildcards when running from a Unix shell.
+
 =head2 regdump.pl
 
 regdump.pl is used to display the keys and values of a registry file.
@@ -1621,6 +1658,23 @@ Type regscan.pl on its own to see the help:
         -u or --unparsed    show the unparsed on-disk entries as a hex dump
         -w or --warnings    display warnings of invalid keys and values
 
+=head2 regscope.pl
+
+regscope.pl is a GTK+ registry scanner.
+It presents all the entries in a registry file returned by the
+get_hbin_iterator and get_entry_iterator methods.
+When viewing Windows NT registry files, it uses color to highlight
+key, value, security, and subkey list entries, and presents the hbin
+as a colored map.
+
+It requires Gtk2-Perl to be installed.
+Links to Windows binaries can be found via the project home page at
+L<http://gtk2-perl.sourceforge.net/win32/>.
+
+A filename can also be supplied on the command line:
+
+    regscope.pl <filename>
+
 =head2 regsecurity.pl
 
 regsecurity.pl will display the security information
@@ -1708,7 +1762,7 @@ A filename can also be supplied on the command line:
 This would not have been possible without the work of those people who have
 analysed and documented the structure of Windows Registry files, namely:
 the WINE Project (see misc/registry.c in older releases),
-the Samba Project (see utils/editreg.c and utils/profiles.c),
+the Samba Project (see utils/editreg.c and utils/profiles.c in older releases),
 Petter Nordahl-Hagen (see chntpw's ntreg.h),
 and B.D. (see WinReg.txt).
 
diff --git a/lib/Parse/Win32Registry/Base.pm b/lib/Parse/Win32Registry/Base.pm
index eab07cd..840bd7d 100644
--- a/lib/Parse/Win32Registry/Base.pm
+++ b/lib/Parse/Win32Registry/Base.pm
@@ -445,7 +445,11 @@ sub make_multiple_subtree_iterator {
         croak "Usage: make_multiple_subtree_iterator\(\$key1, \$key2, ...\)";
     }
 
-    push my @subkey_iters, make_multiple_subkey_iterator(@keys);
+    my @subkeys_queue = (\@keys);
+    push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub {
+        return shift @subkeys_queue;
+    });
+#    make_multiple_subkey_iterator(@keys);
     my $value_iter;
     my $subkeys;
 
@@ -881,11 +885,14 @@ sub get_trustee {
 sub as_string {
     my $self = shift;
 
+    my $sid = $self->{_trustee};
     my $string = sprintf "%s 0x%02x 0x%08x %s",
         _look_up_ace_type($self->{_type}),
         $self->{_flags},
         $self->{_mask},
-        $self->{_trustee}->as_string;
+        $sid->as_string;
+    my $name = $sid->get_name;
+    $string .= " [$name]" if defined $name;
     return $string;
 }
 
@@ -1087,10 +1094,16 @@ sub as_stanza {
 
     my $stanza = "";
     if (defined(my $owner = $self->{_owner})) {
-        $stanza .= "Owner SID: " . $owner->as_string . "\n";
+        $stanza .= "Owner SID: " . $owner->as_string;
+        my $name = $owner->get_name;
+        $stanza .= " [$name]" if defined $name;
+        $stanza .= "\n";
     }
     if (defined(my $group = $self->{_group})) {
-        $stanza .= "Group SID: " . $group->as_string . "\n";
+        $stanza .= "Group SID: " . $group->as_string;
+        my $name = $group->get_name;
+        $stanza .= " [$name]" if defined $name;
+        $stanza .= "\n";
     }
     if (defined(my $sacl = $self->{_sacl})) {
         foreach my $ace ($sacl->get_list_of_aces) {
diff --git a/lib/Parse/Win32Registry/Key.pm b/lib/Parse/Win32Registry/Key.pm
index c923dce..697c86c 100644
--- a/lib/Parse/Win32Registry/Key.pm
+++ b/lib/Parse/Win32Registry/Key.pm
@@ -172,7 +172,10 @@ sub get_list_of_values {
 sub get_subtree_iterator {
     my $self = shift;
 
-    push my @subkey_iters, $self->get_subkey_iterator;
+    my @start_keys = ($self);
+    push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub {
+        return shift @start_keys;
+    });
     my $value_iter;
     my $key;
 
diff --git a/lib/Parse/Win32Registry/Win95/Key.pm b/lib/Parse/Win32Registry/Win95/Key.pm
index 719cfaf..3ee15bf 100644
--- a/lib/Parse/Win32Registry/Win95/Key.pm
+++ b/lib/Parse/Win32Registry/Win95/Key.pm
@@ -259,4 +259,18 @@ sub get_value_iterator {
     });
 }
 
+sub get_associated_offsets {
+    my $self = shift;
+
+    my @owners = ();
+
+    push @owners, $self->{_offset};
+
+    if (defined $self->{_offset_to_rgdb_entry}) {
+        push @owners, $self->{_offset_to_rgdb_entry};
+    }
+
+    return @owners;
+}
+
 1;
diff --git a/lib/Parse/Win32Registry/Win95/Value.pm b/lib/Parse/Win32Registry/Win95/Value.pm
index a8a5f5d..de08361 100644
--- a/lib/Parse/Win32Registry/Win95/Value.pm
+++ b/lib/Parse/Win32Registry/Win95/Value.pm
@@ -177,4 +177,14 @@ sub parse_info {
     return $info;
 }
 
+sub get_associated_offsets {
+    my $self = shift;
+
+    my @owners = ();
+
+    push @owners, $self->{_offset};
+
+    return @owners;
+}
+
 1;
diff --git a/lib/Parse/Win32Registry/WinNT/Key.pm b/lib/Parse/Win32Registry/WinNT/Key.pm
index dcffdfd..cbd7ec5 100644
--- a/lib/Parse/Win32Registry/WinNT/Key.pm
+++ b/lib/Parse/Win32Registry/WinNT/Key.pm
@@ -480,4 +480,36 @@ sub get_value_iterator {
     });
 }
 
+sub get_associated_offsets {
+    my $self = shift;
+
+    my @owners = ();
+
+    push @owners, $self->{_offset};
+
+    if ($self->{_offset_to_security}) {
+        push @owners, $self->{_offset_to_security};
+    }
+
+    if ($self->{_offset_to_class_name}) {
+        push @owners, $self->{_offset_to_class_name};
+    }
+
+    if ($self->{_num_subkeys}) {
+        push @owners, $self->{_offset_to_subkey_list};
+    }
+
+    # Indirect offsets must be added after _get_offsets_to_subkeys
+    # has been called (as this populates the _indirect_offsets field)
+    if ($self->{_indirect_offsets}) {
+        push @owners, keys %{ $self->{_indirect_offsets} };
+    }
+
+    if ($self->{_num_values}) {
+        push @owners, $self->{_offset_to_value_list};
+    }
+
+    return @owners;
+}
+
 1;
diff --git a/lib/Parse/Win32Registry/WinNT/Value.pm b/lib/Parse/Win32Registry/WinNT/Value.pm
index e36b593..71e9056 100644
--- a/lib/Parse/Win32Registry/WinNT/Value.pm
+++ b/lib/Parse/Win32Registry/WinNT/Value.pm
@@ -237,4 +237,18 @@ sub parse_info {
     return $info;
 }
 
+sub get_associated_offsets {
+    my $self = shift;
+
+    my @owners = ();
+
+    push @owners, $self->{_offset};
+
+    if (!$self->{_data_inline}) {
+        push @owners, $self->{_offset_to_data};
+    }
+
+    return @owners;
+}
+
 1;
diff --git a/t/iterator.t b/t/iterator.t
new file mode 100644
index 0000000..d464557
--- /dev/null
+++ b/t/iterator.t
@@ -0,0 +1,253 @@
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+use Data::Dumper;
+use Parse::Win32Registry 0.51 qw(:REG_ make_multiple_subtree_iterator);
+
+$Data::Dumper::Useqq = 1;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Indent = 0;
+
+Parse::Win32Registry::disable_warnings;
+
+sub find_file
+{
+    my $filename = shift;
+    return -d 't' ? "t/$filename" : $filename;
+}
+
+sub run_subtree_iterator_tests
+{
+    my $key = shift;
+    my @tests = @_;
+
+    my ($os) = ref($key) =~ /Win(NT|95)/;
+
+    # key+value tests
+
+    my $subtree_iter = $key->get_subtree_iterator;
+    ok(defined $subtree_iter, "$os get_subtree_iterator defined");
+    isa_ok($subtree_iter, "Parse::Win32Registry::Iterator");
+    for (my $i = 0; $i < @tests; $i++) {
+        my ($key_path, $value_name) = @{$tests[$i]};
+
+        my ($key, $value) = $subtree_iter->get_next;
+
+        my $desc = "$os (list) TEST" . ($i + 1);
+
+        ok(defined $key, "$desc key defined (valid key)");
+        is($key->get_path, $key_path,
+            "$desc key get_path eq " . Dumper($key_path));
+
+        if (defined $value_name) {
+            ok(defined $value, "$desc value defined (valid value)");
+            is($value->get_name, $value_name,
+                "$desc value get_name eq " . Dumper($value_name));
+        }
+        else {
+            ok(!defined $value, "$desc value undefined (no value)");
+        }
+    }
+    my @final = $subtree_iter->get_next;
+    is(@final, 0, "$os (list) iterator empty");
+
+    # key tests
+
+    @tests = grep { !defined $_->[1] } @tests;
+
+    $subtree_iter = $key->get_subtree_iterator;
+    ok(defined $subtree_iter, "$os get_subtree_iterator defined");
+    isa_ok($subtree_iter, "Parse::Win32Registry::Iterator");
+    for (my $i = 0; $i < @tests; $i++) {
+        my ($key_path, $value_name) = @{$tests[$i]};
+
+        my $key = $subtree_iter->get_next;
+
+        my $desc = "$os (scalar) TEST" . ($i + 1);
+
+        ok(defined $key, "$desc key defined (valid key)");
+        is($key->get_path, $key_path,
+            "$desc key get_path eq " . Dumper($key_path));
+    }
+    my $final = $subtree_iter->get_next;
+    ok(!defined $final, "$os (scalar) iterator empty");
+}
+
+sub run_multiple_subtree_iterator_tests {
+    my $key = shift;
+    my @tests = @_;
+
+    my ($os) = ref($key) =~ /Win(NT|95)/;
+
+    # key+value tests
+
+    my $subtree_iter = make_multiple_subtree_iterator($key);
+    ok(defined $subtree_iter,
+        "$os (list) make_multiple_subtree_iterator defined");
+    isa_ok($subtree_iter, "Parse::Win32Registry::Iterator");
+    for (my $i = 0; $i < @tests; $i++) {
+        my ($key_path, $value_name) = @{$tests[$i]};
+
+        my ($keys_ref, $values_ref) = $subtree_iter->get_next;
+
+        my $desc = "$os (list) TEST" . ($i + 1);
+
+        ok(defined $keys_ref, "$desc keys_ref defined");
+        is(ref $keys_ref, 'ARRAY', "$desc keys_ref array");
+        is($keys_ref->[0]->get_path, $key_path,
+            "$desc keys_ref->[0] get_path eq " . Dumper($key_path));
+
+        if (defined $value_name) {
+            ok(defined $values_ref, "$desc values_ref defined");
+            is(ref $values_ref, 'ARRAY', "$desc values_ref array");
+            is($values_ref->[0]->get_name, $value_name,
+                "$desc values_ref->[0] get_name eq " . Dumper($value_name));
+        }
+        else {
+            ok(!defined $values_ref, "$desc values_ref undefined");
+        }
+    }
+    my @final = $subtree_iter->get_next;
+    is(@final, 0, "$os (list) iterator empty");
+
+    # key tests
+
+    @tests = grep { !defined $_->[1] } @tests;
+
+    $subtree_iter = make_multiple_subtree_iterator($key);
+    ok(defined $subtree_iter,
+        "$os (scalar) make_multiple_subtree_iterator defined");
+    isa_ok($subtree_iter, "Parse::Win32Registry::Iterator");
+    for (my $i = 0; $i < @tests; $i++) {
+        my ($key_path, $value_name) = @{$tests[$i]};
+
+        my $keys_ref = $subtree_iter->get_next;
+
+        my $desc = "$os (scalar) TEST" . ($i + 1);
+
+        ok(defined $keys_ref, "$desc keys_ref defined");
+        is(ref $keys_ref, 'ARRAY', "$desc keys_ref array");
+        is($keys_ref->[0]->get_path, $key_path,
+            "$desc keys_ref->[0] get_path eq " . Dumper($key_path));
+    }
+    my $final = $subtree_iter->get_next;
+    ok(!defined $final, "$os (scalar) iterator empty");
+}
+
+{
+    my $filename = find_file('win95_iter_tests.rf');
+
+    my $registry = Parse::Win32Registry->new($filename);
+
+    my $root_key = $registry->get_root_key;
+
+    my @tests = (
+        [""],
+        ["\\key1"],
+        ["\\key1", "value1"],
+        ["\\key1", "value2"],
+        ["\\key1\\key3"],
+        ["\\key1\\key3", "value5"],
+        ["\\key1\\key3", "value6"],
+        ["\\key1\\key4"],
+        ["\\key1\\key4", "value7"],
+        ["\\key1\\key4", "value8"],
+        ["\\key2"],
+        ["\\key2", "value3"],
+        ["\\key2", "value4"],
+        ["\\key2\\key5"],
+        ["\\key2\\key5", "value9"],
+        ["\\key2\\key5", "value10"],
+        ["\\key2\\key6"],
+        ["\\key2\\key6", "value11"],
+        ["\\key2\\key6", "value12"],
+    );
+
+    run_subtree_iterator_tests($root_key, @tests);
+
+    @tests = (
+        [""],
+        ["\\key1"],
+        ["\\key1", "value1"],
+        ["\\key1", "value2"],
+        ["\\key1\\key3"],
+        ["\\key1\\key3", "value5"],
+        ["\\key1\\key3", "value6"],
+        ["\\key1\\key4"],
+        ["\\key1\\key4", "value7"],
+        ["\\key1\\key4", "value8"],
+        ["\\key2"],
+        ["\\key2", "value3"],
+        ["\\key2", "value4"],
+        ["\\key2\\key5"],
+        ["\\key2\\key5", "value10"],
+        ["\\key2\\key5", "value9"],
+        ["\\key2\\key6"],
+        ["\\key2\\key6", "value11"],
+        ["\\key2\\key6", "value12"],
+    );
+
+    run_multiple_subtree_iterator_tests($root_key, @tests);
+}
+
+
+{
+    my $filename = find_file('winnt_iter_tests.rf');
+
+    my $registry = Parse::Win32Registry->new($filename);
+
+    my $root_key = $registry->get_root_key;
+
+    my @tests = (
+        ["\$\$\$PROTO.HIV"],
+        ["\$\$\$PROTO.HIV", "value1"],
+        ["\$\$\$PROTO.HIV", "value2"],
+        ["\$\$\$PROTO.HIV\\key1"],
+        ["\$\$\$PROTO.HIV\\key1", "value3"],
+        ["\$\$\$PROTO.HIV\\key1", "value4"],
+        ["\$\$\$PROTO.HIV\\key1\\key3"],
+        ["\$\$\$PROTO.HIV\\key1\\key3", "value7"],
+        ["\$\$\$PROTO.HIV\\key1\\key3", "value8"],
+        ["\$\$\$PROTO.HIV\\key1\\key4"],
+        ["\$\$\$PROTO.HIV\\key1\\key4", "value9"],
+        ["\$\$\$PROTO.HIV\\key1\\key4", "value10"],
+        ["\$\$\$PROTO.HIV\\key2"],
+        ["\$\$\$PROTO.HIV\\key2", "value5"],
+        ["\$\$\$PROTO.HIV\\key2", "value6"],
+        ["\$\$\$PROTO.HIV\\key2\\key5"],
+        ["\$\$\$PROTO.HIV\\key2\\key5", "value11"],
+        ["\$\$\$PROTO.HIV\\key2\\key5", "value12"],
+        ["\$\$\$PROTO.HIV\\key2\\key6"],
+        ["\$\$\$PROTO.HIV\\key2\\key6", "value13"],
+        ["\$\$\$PROTO.HIV\\key2\\key6", "value14"],
+    );
+
+    run_subtree_iterator_tests($root_key, @tests);
+
+    @tests = (
+        ["\$\$\$PROTO.HIV"],
+        ["\$\$\$PROTO.HIV", "value1"],
+        ["\$\$\$PROTO.HIV", "value2"],
+        ["\$\$\$PROTO.HIV\\key1"],
+        ["\$\$\$PROTO.HIV\\key1", "value3"],
+        ["\$\$\$PROTO.HIV\\key1", "value4"],
+        ["\$\$\$PROTO.HIV\\key1\\key3"],
+        ["\$\$\$PROTO.HIV\\key1\\key3", "value7"],
+        ["\$\$\$PROTO.HIV\\key1\\key3", "value8"],
+        ["\$\$\$PROTO.HIV\\key1\\key4"],
+        ["\$\$\$PROTO.HIV\\key1\\key4", "value10"],
+        ["\$\$\$PROTO.HIV\\key1\\key4", "value9"],
+        ["\$\$\$PROTO.HIV\\key2"],
+        ["\$\$\$PROTO.HIV\\key2", "value5"],
+        ["\$\$\$PROTO.HIV\\key2", "value6"],
+        ["\$\$\$PROTO.HIV\\key2\\key5"],
+        ["\$\$\$PROTO.HIV\\key2\\key5", "value11"],
+        ["\$\$\$PROTO.HIV\\key2\\key5", "value12"],
+        ["\$\$\$PROTO.HIV\\key2\\key6"],
+        ["\$\$\$PROTO.HIV\\key2\\key6", "value13"],
+        ["\$\$\$PROTO.HIV\\key2\\key6", "value14"],
+    );
+
+    run_multiple_subtree_iterator_tests($root_key, @tests);
+}
diff --git a/t/use.t b/t/use.t
index cc20b7d..195c05f 100644
--- a/t/use.t
+++ b/t/use.t
@@ -5,7 +5,7 @@ use Test::More 'no_plan';
 
 BEGIN { use_ok('Parse::Win32Registry') };
 
-is($Parse::Win32Registry::VERSION, '0.50', 'correct version');
+is($Parse::Win32Registry::VERSION, '0.51', 'correct version');
 can_ok('Parse::Win32Registry', 'new');
 can_ok('Parse::Win32Registry', 'convert_filetime_to_epoch_time');
 can_ok('Parse::Win32Registry', 'iso8601');
diff --git a/t/win95_iter_tests.rf b/t/win95_iter_tests.rf
new file mode 100644
index 0000000..618e8da
Binary files /dev/null and b/t/win95_iter_tests.rf differ
diff --git a/t/winnt_iter_tests.rf b/t/winnt_iter_tests.rf
new file mode 100644
index 0000000..ac56a34
Binary files /dev/null and b/t/winnt_iter_tests.rf differ

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libsendmail-milter-perl.git



More information about the Pkg-perl-cvs-commits mailing list