1#!/usr/bin/perl
2use strict;
3use warnings;
4
5use Glib ':constants';
6use Gtk2 -init;
7
8my $window_width = 600;
9my $window_height = 400;
10
11use File::Basename;
12use File::Spec;
13use Parse::Win32Registry 0.51 qw(hexdump);
14
15binmode(STDOUT, ':utf8');
16
17my $script_name = basename $0;
18
19### LIST VIEW
20
21my $list_store = Gtk2::ListStore->new(
22    'Glib::String', 'Glib::String', 'Glib::String', 'Glib::Scalar',
23);
24# 0 = list store name (value name)
25# 1 = list store type (value timestamp)
26# 2 = list store data (value class name)
27# 3 = list store value (value object)
28
29my $list_view = Gtk2::TreeView->new($list_store);
30
31my @list_column_names = qw(Name Type Data);
32for (my $col = 0; $col < @list_column_names; $col++) {
33    my $text_cell = Gtk2::CellRendererText->new;
34    if ($col == 2) {
35        $text_cell->set('ellipsize', 'end');
36    }
37    my $column = Gtk2::TreeViewColumn->new_with_attributes(
38        $list_column_names[$col],
39        $text_cell,
40        'text', $col);
41    $list_view->append_column($column);
42    $column->set_resizable(TRUE);
43    $list_store->set_sort_func($col, sub {
44        my ($model, $itera, $iterb, $col) = @_;
45        my $a = $model->get($itera, $col);
46        my $b = $model->get($iterb, $col);
47        $a = '' if !defined $a;
48        $b = '' if !defined $b;
49        return $a cmp $b;
50    }, $col);
51    $column->set_sort_column_id($col);
52}
53$list_view->set_rules_hint(TRUE);
54
55my $list_selection = $list_view->get_selection;
56$list_selection->set_mode('browse');
57$list_selection->signal_connect('changed' => \&value_selected);
58
59my $scrolled_list_view = Gtk2::ScrolledWindow->new;
60$scrolled_list_view->set_policy('automatic', 'automatic');
61$scrolled_list_view->set_shadow_type('in');
62$scrolled_list_view->add($list_view);
63
64### TEXT VIEW
65
66my $text_view = Gtk2::TextView->new;
67$text_view->set_editable(FALSE);
68$text_view->modify_font(Gtk2::Pango::FontDescription->from_string('monospace'));
69
70my $text_buffer = $text_view->get_buffer;
71
72my $scrolled_text_view = Gtk2::ScrolledWindow->new;
73$scrolled_text_view->set_policy('automatic', 'automatic');
74$scrolled_text_view->set_shadow_type('in');
75$scrolled_text_view->add($text_view);
76
77### VPANED
78
79my $vpaned = Gtk2::VPaned->new;
80$vpaned->pack1($scrolled_list_view, FALSE, FALSE);
81$vpaned->pack2($scrolled_text_view, FALSE, FALSE);
82
83### TREE VIEW
84
85my $tree_store = Gtk2::TreeStore->new(
86    'Glib::String', 'Glib::String', 'Glib::String', 'Glib::Scalar',
87);
88# 0 = tree store name (key name)
89# 1 = tree store timestamp (key timestamp)
90# 2 = tree store class name (key class name)
91# 3 = tree store key (key object)
92
93my $tree_view = Gtk2::TreeView->new($tree_store);
94
95my @tree_columns;
96my @tree_column_names = ('Name', 'Timestamp', 'Class Name');
97for (my $col = 0; $col < @tree_column_names; $col++) {
98    my $column = Gtk2::TreeViewColumn->new;
99    if ($col == 0) {
100        my $icon_cell = Gtk2::CellRendererPixbuf->new;
101        $icon_cell->set('stock-id', 'gtk-directory');
102        $column->pack_start($icon_cell, FALSE);
103    }
104    my $text_cell = Gtk2::CellRendererText->new;
105    $column->pack_start($text_cell, TRUE);
106    $column->set_attributes($text_cell, 'text', $col);
107    $column->set_title($tree_column_names[$col]);
108    $column->set_resizable(TRUE);
109    $tree_view->append_column($column);
110    push @tree_columns, $column;
111    $tree_store->set_sort_func($col, sub {
112        my ($model, $itera, $iterb, $col) = @_;
113        my $a = $model->get($itera, $col);
114        my $b = $model->get($iterb, $col);
115        $a = '' if !defined $a;
116        $b = '' if !defined $b;
117        return $a cmp $b;
118    }, $col);
119    $column->set_sort_column_id($col);
120}
121$tree_view->set_rules_hint(TRUE);
122
123# row-expanded when row is expanded (e.g. after user clicks on arrow)
124$tree_view->signal_connect('row-expanded' => \&expand_row);
125$tree_view->signal_connect('row-collapsed' => \&collapse_row);
126# row-activated when user double clicks on row
127$tree_view->signal_connect('row-activated' => \&activate_row);
128
129my $tree_selection = $tree_view->get_selection;
130$tree_selection->set_mode('browse');
131$tree_selection->signal_connect('changed' => \&key_selected);
132
133my $scrolled_tree_view = Gtk2::ScrolledWindow->new;
134$scrolled_tree_view->set_policy('automatic', 'automatic');
135$scrolled_tree_view->set_shadow_type('in');
136$scrolled_tree_view->add($tree_view);
137
138### HPANED
139
140my $hpaned = Gtk2::HPaned->new;
141$hpaned->pack1($scrolled_tree_view, FALSE, FALSE);
142$hpaned->pack2($vpaned, TRUE, FALSE);
143
144$hpaned->set_position($window_width * 0.3);
145
146### MENU
147
148use Gtk2::Gdk::Keysyms;
149
150my $menubar = Gtk2::MenuBar->new;
151
152my $accel_group = Gtk2::AccelGroup->new;
153
154# File Menu
155my $open_menuitem = Gtk2::MenuItem->new('_Open...');
156$open_menuitem->signal_connect('activate' => \&open_file);
157$open_menuitem->add_accelerator('activate', $accel_group,
158    $Gtk2::Gdk::Keysyms{O}, ['control-mask'], ['visible', 'locked']);
159my $close_menuitem = Gtk2::MenuItem->new('_Close');
160$close_menuitem->signal_connect('activate' => \&close_file);
161$close_menuitem->add_accelerator('activate', $accel_group,
162    $Gtk2::Gdk::Keysyms{W}, ['control-mask'], ['visible', 'locked']);
163my $quit_menuitem = Gtk2::MenuItem->new('_Quit');
164$quit_menuitem->signal_connect('activate' => \&quit);
165$quit_menuitem->add_accelerator('activate', $accel_group,
166    $Gtk2::Gdk::Keysyms{Q}, ['control-mask'], ['visible', 'locked']);
167
168my $file_menu = Gtk2::Menu->new;
169$file_menu->append($open_menuitem);
170$file_menu->append($close_menuitem);
171$file_menu->append(Gtk2::SeparatorMenuItem->new);
172$file_menu->append($quit_menuitem);
173$file_menu->set_accel_group($accel_group);
174
175# Edit Menu
176my $copy_menuitem = Gtk2::MenuItem->new('_Copy Key Path');
177$copy_menuitem->signal_connect('activate' => \&copy_key_path);
178$copy_menuitem->add_accelerator('activate', $accel_group,
179    $Gtk2::Gdk::Keysyms{C}, ['control-mask'], ['visible', 'locked']);
180
181my $edit_menu = Gtk2::Menu->new;
182$edit_menu->append($copy_menuitem);
183
184# Search Menu
185my $find_menuitem = Gtk2::MenuItem->new('_Find...');
186$find_menuitem->signal_connect('activate' => \&find);
187$find_menuitem->add_accelerator('activate', $accel_group,
188    $Gtk2::Gdk::Keysyms{F}, ['control-mask'], ['visible', 'locked']);
189my $find_next_menuitem = Gtk2::MenuItem->new('Find _Next');
190$find_next_menuitem->signal_connect('activate' => \&find_next);
191$find_next_menuitem->add_accelerator('activate', $accel_group,
192    $Gtk2::Gdk::Keysyms{G}, ['control-mask'], ['visible', 'locked']);
193$find_next_menuitem->add_accelerator('activate', $accel_group,
194    $Gtk2::Gdk::Keysyms{F3}, [], ['visible', 'locked']);
195
196my $search_menu = Gtk2::Menu->new;
197$search_menu->append($find_menuitem);
198$search_menu->append($find_next_menuitem);
199
200# Help Menu
201my $about_menuitem = Gtk2::MenuItem->new('_About...');
202$about_menuitem->signal_connect('activate' => \&about);
203
204my $help_menu = Gtk2::Menu->new;
205$help_menu->append($about_menuitem);
206
207# Menu Bar
208my $file_menuitem = Gtk2::MenuItem->new('_File');
209$file_menuitem->set_submenu($file_menu);
210$menubar->append($file_menuitem);
211
212my $edit_menuitem = Gtk2::MenuItem->new('_Edit');
213$edit_menuitem->set_submenu($edit_menu);
214$menubar->append($edit_menuitem);
215
216my $search_menuitem = Gtk2::MenuItem->new('_Search');
217$search_menuitem->set_submenu($search_menu);
218$menubar->append($search_menuitem);
219
220my $help_menuitem = Gtk2::MenuItem->new('_Help');
221$help_menuitem->set_submenu($help_menu);
222$menubar->append($help_menuitem);
223
224### STATUSBAR
225
226my $statusbar = Gtk2::Statusbar->new;
227
228### VBOX
229
230my $main_vbox = Gtk2::VBox->new(FALSE, 0);
231$main_vbox->pack_start($menubar, FALSE, FALSE, 0);
232$main_vbox->pack_start($hpaned, TRUE, TRUE, 0);
233$main_vbox->pack_start($statusbar, FALSE, FALSE, 0);
234
235### WINDOW
236
237my $window = Gtk2::Window->new;
238$window->set_default_size($window_width, $window_height);
239$window->set_position('center');
240$window->signal_connect(destroy => sub { Gtk2->main_quit });
241$window->add($main_vbox);
242$window->add_accel_group($accel_group);
243$window->set_title($script_name);
244$window->show_all;
245
246### GLOBALS
247
248my $search_keys = TRUE;
249my $search_values = TRUE;
250my $search_selected = 0;
251my $find_param = '';
252my $find_iter;
253
254my $last_dir;
255
256my $filename = shift;
257if (defined $filename && -r $filename) {
258    $filename = File::Spec->rel2abs($filename);
259    load_file($filename);
260}
261
262Gtk2->main;
263
264###############################################################################
265
266sub key_selected {
267    my ($model, $iter) = $tree_selection->get_selected;
268    if (!defined $model || !defined $iter) {
269        return;
270    }
271
272    my $key = $model->get($iter, 3);
273
274    # Fill list with the values of this key
275    $list_store->clear;
276    my @values = $key->get_list_of_values;
277    foreach my $value (@values) {
278        my $name = $value->get_name;
279        $name = '(Default)' if $name eq '';
280        $name =~ s/\0/[NUL]/g;
281        my $type = $value->get_type_as_string;
282        my $data = $value->get_data_as_string;
283        $data =~ s/\0/[NUL]/g;
284        # Abbreviate very long data to avoid a performance hit
285        # from loading large strings into the model
286        $data = substr($data, 0, 500);
287        my $iter = $list_store->append;
288        $list_store->set($iter,
289            0, $name,
290            1, $type,
291            2, $data,
292            3, $value);
293    }
294
295    my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_PRIMARY);
296    $clipboard->set_text($key->get_path);
297
298    # Display key information:
299    my $str = '';
300    my $security = $key->get_security;
301    if (defined $security) {
302        my $sd = $security->get_security_descriptor;
303        $str .= $sd->as_stanza;
304    }
305
306    my $text_buffer = $text_view->get_buffer;
307    $text_buffer->set_text($str);
308    $statusbar->pop(0);
309    my $key_path = $key->get_path;
310    $key_path =~ s/\0/[NUL]/g;
311    $statusbar->push(0, $key_path);
312}
313
314sub value_selected {
315    my ($model, $iter) = $list_selection->get_selected;
316    if (!defined $model || !defined $iter) {
317        return;
318    }
319
320    my $value = $model->get($iter, 3);
321
322    my $name = $value->get_name;
323    $name = '(Default)' if $name eq '';
324    my $type = $value->get_type_as_string;
325
326    my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_PRIMARY);
327    $clipboard->set_text($name);
328
329    # Display value information:
330    my $str = hexdump($value->get_raw_data);
331
332    my $text_buffer = $text_view->get_buffer;
333    $text_buffer->set_text($str);
334}
335
336sub add_root {
337    my ($key, $model, undef) = @_;
338    my $iter = $model->append(undef);
339    my $name = $key->get_name;
340    $name =~ s/\0/[NUL]/g;
341    my $timestamp = defined($key->get_timestamp)
342                  ? $key->get_timestamp_as_string
343                  : '';
344    my $class_name = defined($key->get_class_name)
345                   ? $key->get_class_name
346                   : '';
347    $class_name =~ s/\0/[NUL]/g;
348    $model->set($iter,
349        0, $name,
350        1, $timestamp,
351        2, $class_name,
352        3, $key,
353    );
354    my $dummy = $model->append($iter);
355}
356
357sub add_children {
358    my ($key, $model, $iter) = @_;
359    # my @subkeys = defined $iter ? $key->get_list_of_subkeys : ($key);
360    my @subkeys = $key->get_list_of_subkeys;
361    foreach my $subkey (@subkeys) {
362        my $child_iter = $model->append($iter);
363        my $name = $subkey->get_name;
364        $name =~ s/\0/[NUL]/g;
365        my $timestamp = defined($subkey->get_timestamp)
366                      ? $subkey->get_timestamp_as_string
367                      : '';
368        my $class_name = defined($subkey->get_class_name)
369                       ? $subkey->get_class_name
370                       : '';
371        $class_name =~ s/\0/[NUL]/g;
372        $model->set($child_iter,
373            0, $name,
374            1, $timestamp,
375            2, $class_name,
376            3, $subkey,
377        );
378        my $dummy = $model->append($child_iter); ### load gradually
379        #add_children($subkey, $model, $child_iter); ### load everything
380    }
381}
382
383sub expand_row {
384    my ($view, $iter, $path) = @_;
385
386    my $model = $view->get_model;
387    my $key = $model->get($iter, 3);
388    my $first_child_iter = $model->iter_nth_child($iter, 0);
389    if (!defined $model->get($first_child_iter, 0)) {
390        add_children($key, $model, $iter);
391        $model->remove($first_child_iter);
392    }
393}
394
395sub collapse_row {
396    my ($view, $iter, $path) = @_;
397
398    return; # uncomment to remove children when collapsing
399
400    my $model = $view->get_model;
401    my $child_iter = $model->iter_nth_child($iter, 0);
402    if (!defined $child_iter) {
403        # this key has no children
404        return;
405    }
406
407    my @child_iters = ();
408    while (defined $child_iter) {
409        if (defined $model->get($child_iter, 0)) {
410            push @child_iters, $child_iter;
411        }
412        $child_iter = $tree_store->iter_next($child_iter);
413    }
414    foreach my $child_iter (@child_iters) {
415        $tree_store->remove($child_iter);
416    }
417    my $dummy = $tree_store->append($iter);
418}
419
420sub activate_row {
421    my ($view, $path, $column) = @_;
422    if ($view->row_expanded($path)) {
423        $view->collapse_row($path);
424    }
425    else {
426        # only rows with children will actually be expanded
427        $view->expand_row($path, FALSE);
428    }
429}
430
431sub load_file {
432    my $filename = shift;
433
434    my ($name, $path) = fileparse($filename);
435
436    close_file();
437
438    if (!-r $filename) {
439        show_message('error', "Unable to open '$name'.");
440    }
441    elsif (my $registry = Parse::Win32Registry->new($filename)) {
442        if (my $root_key = $registry->get_root_key) {
443            add_root($root_key, $tree_store, undef);
444            $window->set_title("$name - $script_name");
445            if (defined $root_key->get_timestamp) {
446                $tree_columns[1]->set_visible(TRUE);
447                $tree_columns[2]->set_visible(TRUE);
448            }
449            else {
450                $tree_columns[1]->set_visible(FALSE);
451                $tree_columns[2]->set_visible(FALSE);
452            }
453        }
454    }
455    else {
456        show_message('error', "'$name' is not a registry file.");
457    }
458}
459
460sub choose_file {
461    my ($title, $type, $suggested_name) = @_;
462
463    my $file_chooser = Gtk2::FileChooserDialog->new(
464        $title,
465        undef,
466        $type,
467        'gtk-cancel' => 'cancel',
468        'gtk-ok' => 'ok',
469    );
470    if ($type eq 'save') {
471        $file_chooser->set_current_name($suggested_name);
472    }
473    if (defined $last_dir) {
474        $file_chooser->set_current_folder($last_dir);
475    }
476    my $response = $file_chooser->run;
477
478    my $filename;
479    if ($response eq 'ok') {
480        $filename = $file_chooser->get_filename;
481    }
482    $last_dir = $file_chooser->get_current_folder;
483    $file_chooser->destroy;
484    return $filename;
485}
486
487sub open_file {
488    my $filename = choose_file('Select Registry File', 'open');
489    if ($filename) {
490        load_file($filename);
491    }
492}
493
494sub close_file {
495    $tree_store->clear;
496    $list_store->clear;
497    $text_buffer->set_text('');
498    $find_param = '';
499    $find_iter = undef;
500    $statusbar->pop(0);
501}
502
503sub quit {
504    $window->destroy;
505}
506
507sub about {
508    Gtk2->show_about_dialog(undef,
509        'program-name' => $script_name,
510        'version' => $Parse::Win32Registry::VERSION,
511        'copyright' => 'Copyright (c) 2008-2012 James Macfarlane',
512        'comments' => 'GTK2 Registry Viewer for the Parse::Win32Registry module',
513    );
514}
515
516sub show_message {
517    my $type = shift;
518    my $message = shift;
519
520    my $dialog = Gtk2::MessageDialog->new(
521        $window,
522        'destroy-with-parent',
523        $type,
524        'ok',
525        $message,
526    );
527    $dialog->set_title(ucfirst $type);
528    $dialog->run;
529    $dialog->destroy;
530}
531
532sub copy_key_path {
533    my $tree_iter = $tree_selection->get_selected;
534    my $clip = '';
535    if (defined $tree_iter) {
536        my $key = $tree_store->get($tree_iter, 3);
537        $clip = $key->get_path;
538    }
539    my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_CLIPBOARD);
540    $clipboard->set_text($clip);
541}
542
543sub go_to_value {
544    my $value_name = shift;
545
546    my $iter = $list_store->get_iter_first;
547    while (defined $iter) {
548        my $name = $list_store->get($iter, 0);
549        my $value = $list_store->get($iter, 3);
550
551        if ($value_name eq $value->get_name) {
552            my $tree_path = $list_store->get_path($iter);
553            $list_view->expand_to_path($tree_path);
554            $list_view->scroll_to_cell($tree_path);
555            $list_view->set_cursor($tree_path);
556            $window->set_focus($list_view);
557            return;
558        }
559
560        $iter = $list_store->iter_next($iter);
561    }
562}
563
564sub find_matching_child_iter {
565    my ($iter, $subkey_name) = @_;
566
567    my $child_iter = $tree_store->iter_nth_child($iter, 0);
568    if (!defined $child_iter) {
569        # iter has already been expanded and has no children
570        return;
571    }
572
573    # Check iter's children are real
574    if (!defined $tree_store->get($child_iter, 0)) {
575        my $key = $tree_store->get($iter, 3);
576        add_children($key, $tree_store, $iter);
577        $tree_store->remove($child_iter);
578        # (Need to refetch the first child iter after removing it.)
579        $child_iter = $tree_store->iter_nth_child($iter, 0);
580    }
581
582    while (defined $child_iter) {
583        my $child_key = $tree_store->get($child_iter, 3);
584
585        # $tree_store->get($child_iter, 0) contains the displayed name,
586        # $child_key->get_name is the actual name
587        if ($child_key->get_name eq $subkey_name) {
588            return $child_iter; # match found
589        }
590        $child_iter = $tree_store->iter_next($child_iter);
591    }
592    return; # no match found
593}
594
595sub go_to_subkey {
596    my $subkey_path = shift;
597
598    my @path_components = index($subkey_path, "\\") == -1
599                        ? ($subkey_path)
600                        : split(/\\/, $subkey_path, -1);
601
602    my $iter = $tree_store->get_iter_first;
603    return if !defined $iter; # no registry loaded
604
605    while (defined(my $subkey_name = shift @path_components)) {
606        $iter = find_matching_child_iter($iter, $subkey_name);
607        if (!defined $iter) {
608            return; # subkey cannot be found in/added to the tree store
609        }
610
611        if (@path_components == 0) {
612            my $parent_iter = $tree_store->iter_parent($iter);
613            my $parent_path = $tree_store->get_path($parent_iter);
614            $tree_view->expand_to_path($parent_path);
615            my $tree_path = $tree_store->get_path($iter);
616            $tree_view->scroll_to_cell($tree_path);
617            $tree_view->set_cursor($tree_path);
618            $window->set_focus($tree_view);
619            return; # skip remaining search
620        }
621    }
622}
623
624sub get_search_message {
625    my $message;
626    if ($search_keys && $search_values) {
627        $message = "Searching registry keys and values...";
628    }
629    elsif ($search_keys) {
630        $message = "Searching registry keys...";
631    }
632    elsif ($search_values) {
633        $message = "Searching registry values...";
634    }
635    return $message;
636}
637
638sub find_next {
639    if (!defined $find_param || !defined $find_iter) {
640        return;
641    }
642
643    my $label = Gtk2::Label->new;
644    $label->set_text(get_search_message);
645    my $dialog = Gtk2::Dialog->new('Find',
646        $window,
647        'modal',
648        'gtk-cancel' => 'cancel',
649    );
650    $dialog->vbox->pack_start($label, TRUE, TRUE, 5);
651    $dialog->set_default_response('cancel');
652    $dialog->show_all;
653
654    my $id = Glib::Idle->add(sub {
655        my ($key, $value) = $find_iter->get_next;
656
657        if (!defined $key) {
658            $dialog->response('ok');
659            return FALSE; # stop searching
660        }
661
662        # Remove root key name to get subkey path
663        my $subkey_path = (split(/\\/, $key->get_path, 2))[1];
664
665        if (!defined $subkey_path) {
666            # go_to_subkey locates keys based on the subkey path
667            # and does not support going to the root key.
668            # Therefore if the subkey path is not defined,
669            # the subtree iterator has returned the root key,
670            # so searching it should be skipped.
671            return TRUE; # continue searching
672        }
673
674        # Check value (if defined) for a match
675        if (defined $value) {
676            if ($search_values) {
677                my $value_name = $value->get_name;
678                if (index(lc $value_name, lc $find_param) >= 0) {
679                    go_to_subkey($subkey_path);
680                    go_to_value($value_name);
681                    $dialog->response(50);
682                    return FALSE; # stop searching
683                }
684            }
685            return TRUE; # continue searching
686        }
687
688        # Check key for a match
689        if ($search_keys) {
690            my $key_name = $key->get_name;
691            if (index(lc $key_name, lc $find_param) >= 0) {
692                go_to_subkey($subkey_path);
693                $dialog->response(50);
694                return FALSE; # stop searching
695            }
696        }
697        return TRUE; # continue searching
698    });
699
700    my $response = $dialog->run;
701    $dialog->destroy;
702
703    if ($response eq 'cancel' || $response eq 'delete-event') {
704        Glib::Source->remove($id);
705    }
706    elsif ($response eq 'ok') {
707        show_message('info', 'Finished searching.');
708    }
709}
710
711sub find {
712    return if !defined $tree_store->get_iter_first;
713
714    my $root_iter = $tree_store->get_iter_first;
715    return if !defined $root_iter;
716
717    my $root_key = $tree_store->get($root_iter, 3);
718    return if !defined $root_key;
719
720    my $selected_key;
721    my $iter = $tree_selection->get_selected;
722    if (defined $iter) {
723        $selected_key = $tree_store->get($iter, 3);
724    }
725
726    my $label = Gtk2::Label->new('Enter text to search for:');
727    $label->set_alignment(0, 0);
728    my $entry = Gtk2::Entry->new;
729    $entry->set_text($find_param);
730    $entry->set_activates_default(TRUE);
731    my $check1 = Gtk2::CheckButton->new('Search _keys');
732    $check1->set_active($search_keys);
733    my $check2 = Gtk2::CheckButton->new('Search _values');
734    $check2->set_active($search_values);
735    $check1->signal_connect(toggled => sub {
736        if (!$check1->get_active && !$check2->get_active) {
737            $check2->set_active(TRUE);
738        }
739    });
740    $check2->signal_connect(toggled => sub {
741        if (!$check1->get_active && !$check2->get_active) {
742            $check1->set_active(TRUE);
743        }
744    });
745    my $frame = Gtk2::Frame->new('Start searching');
746    my $vbox = Gtk2::VBox->new(FALSE, 0);
747    $frame->add($vbox);
748    my $radio1 = Gtk2::RadioButton->new(undef, 'from _root key');
749    my $radio2 = Gtk2::RadioButton->new($radio1, 'from c_urrent key');
750    if (!defined $selected_key) {
751        $radio2->set_sensitive(FALSE);
752    }
753    elsif ($search_selected) {
754        $radio2->set_active(TRUE);
755    }
756    $vbox->pack_start($radio1, TRUE, TRUE, 0);
757    $vbox->pack_start($radio2, TRUE, TRUE, 0);
758
759    my $dialog = Gtk2::Dialog->new('Find',
760        $window,
761        'modal',
762        'gtk-cancel' => 'cancel',
763        'gtk-ok' => 'ok',
764    );
765    $dialog->vbox->set_spacing(5);
766    $dialog->vbox->pack_start($label, FALSE, TRUE, 0);
767    $dialog->vbox->pack_start($entry, FALSE, TRUE, 0);
768    $dialog->vbox->pack_start($check1, FALSE, TRUE, 0);
769    $dialog->vbox->pack_start($check2, FALSE, TRUE, 0);
770    $dialog->vbox->pack_start($frame, FALSE, TRUE, 0);
771    $dialog->set_default_response('ok');
772    $dialog->show_all;
773
774    my $response = $dialog->run;
775    if ($response eq 'ok') {
776        $search_keys = $check1->get_active;
777        $search_values = $check2->get_active;
778        $search_selected = $radio2->get_active;
779        $find_param = $entry->get_text;
780        $dialog->destroy;
781        $find_iter = undef;
782        if ($find_param ne '') {
783            $find_iter = $search_selected
784                       ? $selected_key->get_subtree_iterator
785                       : $root_key->get_subtree_iterator;
786            find_next;
787        }
788    }
789    else {
790        $dialog->destroy;
791    }
792}
793