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' => \©_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