#!/usr/bin/perl #***************************************************************************** # # Copyright (c) 2002 Guillaume Cottenceau # Copyright (c) 2002-2007 Thierry Vignaud # Copyright (c) 2003, 2004, 2005 MandrakeSoft SA # Copyright (c) 2005-2007 Mandriva SA # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # #***************************************************************************** # # $Id$ use strict; use MDK::Common::Func 'any'; use lib qw(/usr/lib/libDrakX); use common; use utf8; use Rpmdrake::init; use standalone; #- standalone must be loaded very first, for 'explanations', but after rpmdrake::init use rpmdrake; use Rpmdrake::open_db; use Rpmdrake::gui; use Rpmdrake::rpmnew; use Rpmdrake::formatting; use Rpmdrake::pkg; use urpm::media; use mygtk2 qw(gtknew); #- do not import anything else, especially gtkadd() which conflicts with ugtk2 one use ugtk2 qw(:all); use Gtk2::Gdk::Keysyms; use Rpmdrake::widgets; $ugtk2::wm_icon = "title-$MODE"; our $w; our $statusbar; sub do_search($$$$$$$) { my ($find_entry, $tree, $tree_model, $options, $current_search_type, $urpm, $pkgs) = @_; my $entry = $find_entry->get_text or return; my $entry_rx = eval { qr/$entry/i } or return; my $results_ok = N("Search results"); $options->{delete_category}->($_) foreach $results_ok; $options->{state}{flat} and $options->{delete_all}->(); $tree->collapse_all; my @search_results; if ($current_search_type ne 'normal') { my $searchstop; my $searchw = ugtk2->new(N("Software Management"), grab => 1, transient => $w->{real_window}); gtkadd( $searchw->{window}, gtkpack__( gtknew('VBox', spacing => 5), gtknew('Label', text => N("Please wait, searching...")), my $searchprogress = gtknew('ProgressBar', width => 300), gtkpack__( gtknew('HButtonBox', layout => 'spread'), gtksignal_connect( Gtk2::Button->new(but(N("Stop"))), clicked => sub { $searchstop = 1 }, ), ), ), ); $searchw->sync; my @hdlists = map { my $h = urpm::media::any_hdlist($urpm, $_); if_(!$_->{ignore} && ($MODE ne 'update' || $_->{update}) && -r $h, $h); } @{$urpm->{media}}; my $total_size = sum( map { my $pack; eval { require MDV::Packdrakeng; $pack = MDV::Packdrakeng->open(archive => $_, quiet => 1) } ? $pack->{toc_f_count} : 0; } @hdlists ); open my $sf, join(' ', 'parsehdlist', '--name', if_($current_search_type eq 'files', '--files'), if_($current_search_type eq 'descriptions', '--description', '--summary'), if_($current_search_type eq 'summaries', '--summary'), map { "'$_'" } @hdlists) . ' |'; my ($progresscount, $found); local $_; while (<$sf>) { $searchstop and last; if (my ($pkg) = chomp_(/:name:(.+-[^-]+-[^-]+.[^.-]+)$/)) { $progresscount++; $progresscount <= $total_size and $searchprogress->set_fraction($progresscount/$total_size); $searchw->flush; exists $pkgs->{$pkg} and $found and push @search_results, $pkg; undef $found; next; } my (undef, $key, $value) = split ':', $_; if ($current_search_type eq 'descriptions') { $key =~ /^summary|description$/ or next; } elsif ($current_search_type eq 'summaries') { $key eq 'summary' or next; } else { $key eq 'files' or next; } if ($value =~ $entry_rx) { $found = 1; } } close $sf; @search_results = uniq(@search_results); #- there can be multiple packages with same version/release for different arch's $searchw->destroy; } else { my $count; foreach (@filtered_pkgs) { next if !/$entry_rx/; push @search_results, $_; # FIXME: should be done for all research types last if $count++ > 2000; } } if (@search_results) { $options->{add_nodes}->(map { [ $_, N("Search results") . ($options->{tree_mode} eq 'by_presence' ? '|' . ($pkgs->{$_}{pkg}->flag_installed ? N("Upgradable") : N("Addable")) : ($options->{tree_mode} eq 'by_selection' ? '|' . ($pkgs->{$_}{selected} ? N("Selected") : N("Not selected")) : '')) ] } sort { uc($a) cmp uc($b) } @search_results); my $last_iter = $tree_model->iter_nth_child(undef, $tree_model->iter_n_children(undef) - 1); my $path = $tree_model->get_path($last_iter); $tree->expand_row($path, 0); $tree->scroll_to_cell($path, undef, 1, 0.5, 0); } else { my $wait = statusbar_msg(N("Search results (none)")); Glib::Timeout->add(5000, sub { statusbar_msg_remove($wait); 0 }); gtkset_mousecursor_normal($::w->{rwindow}->window); } } sub run_treeview_dialog { my ($callback_action) = @_; my ($options, $compssUsers, $tree, $tree_model, $detail_list, $detail_list_model, %elems); (undef, $size_free) = MDK::Common::System::df('/usr'); $::main_window = $w->{real_window}; $options = { build_tree => sub { build_tree($tree, $tree_model, \%elems, $options, $force_rebuild, $compssUsers, @_) }, partialsel_unsel => sub { my ($unsel, $sel) = @_; @$sel = grep { exists $pkgs->{$_} } @$sel; @$unsel < @$sel; }, get_status => sub { member($default_list_mode, qw(all non_installed)) ? N("Selected: %s / Free disk space: %s", formatXiB($size_selected), formatXiB($size_free*1024)) : N("Selected size: %d MB", $size_selected/(1024*1024)); }, rebuild_tree => sub {}, }; $tree_model = Gtk2::TreeStore->new("Glib::String", "Glib::String", "Gtk2::Gdk::Pixbuf"); $tree_model->set_sort_column_id(0, 'ascending'); $tree = Gtk2::TreeView->new_with_model($tree_model); $tree->get_selection->set_mode('browse'); $tree->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::MDV::CellRendererPixWithLabel->new, 'pixbuf' => $grp_columns{icon}, label => $grp_columns{label})); $tree->set_headers_visible(0); $detail_list_model = Gtk2::ListStore->new("Glib::String", "Gtk2::Gdk::Pixbuf", "Glib::String", "Glib::Boolean", "Glib::String", "Glib::String", "Glib::String"); $detail_list = Gtk2::TreeView->new_with_model($detail_list_model); $detail_list->append_column(my $col1 = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererToggle->new, active => $pkg_columns{selected})); $col1->set_fixed_width(34); # w/o this the toggle cells are not displayed $col1->set_sizing('fixed'); $col1->set_sort_column_id($pkg_columns{selected}); $detail_list->append_column(my $pixcolumn = Gtk2::TreeViewColumn->new_with_attributes(undef, my $rdr = Gtk2::CellRendererPixbuf->new, 'pixbuf' => $pkg_columns{state_icon})); $rdr->set_fixed_size(34, 24); $pixcolumn->set_fixed_width(34); # w/o this the pixbuf cells is empty $pixcolumn->set_sizing('fixed'); my %columns = ( 'name' => { title => N("Package"), markup => $pkg_columns{short_name}, }, 'version' => { title => N("Version"), text => $pkg_columns{version}, }, 'arch' => { title => N("Arch"), text => $pkg_columns{arch}, }, ); foreach my $col (@columns{qw(name version arch)}) { $detail_list->append_column( $col->{widget} = Gtk2::TreeViewColumn->new_with_attributes(' ' . $col->{title} . ' ', $col->{renderer} = Gtk2::CellRendererText->new, ($col->{markup} ? (markup => $col->{markup}) : (text => $col->{text})), ) ); $col->{widget}->set_sort_column_id($col->{markup} ? $col->{markup} : $col->{text}); } $columns{$_}{widget}->set_sizing('autosize') foreach qw(name version arch); $columns{name}{widget}->set_property('expand', '1'); $columns{name}{renderer}->set_property('ellipsize', 'end'); $columns{$_}{renderer}->set_property('xpad', '6') foreach qw(name version arch); $columns{name}{widget}->set_resizable(1); $detail_list_model->set_sort_column_id(0, 'ascending'); $detail_list->set_rules_hint(1); #$detail_list->set_fixed_height_mode(1); compute_main_window_size($w); my $cursor_to_restore; $_->signal_connect( expose_event => sub { $cursor_to_restore or return; gtkset_mousecursor_normal($tree->window); undef $cursor_to_restore; }, ) foreach $tree, $detail_list; $tree->get_selection->signal_connect(changed => sub { my ($model, $iter) = $_[0]->get_selected; return if !$iter; my $current_group if 0; my $new_group = $model->get_path_str($iter); return if $current_group eq $new_group && !$force_displaying_group; $options->{clear_all_caches}->(); undef $force_displaying_group; $current_group = $new_group; $model && $iter or return; my $group = $model->get($iter, 0); my $parent = $iter; while ($parent = $model->iter_parent($parent)) { $group = join('|', $model->get($parent, 0), $group); } $detail_list->window->freeze_updates; slow_func($::main_window->window, sub { $options->{add_nodes}->(@{$elems{$group}}) }); $detail_list->window->thaw_updates; }); $options->{state}{splited} = 1; #$options->{state}{flat} = 1; $compssUsers = parse_compssUsers_flat(); my %modes = (all => N("All"), installed => N("Installed"), non_installed => N("Non installed"), mandrake_choices => $rpmdrake::branded ? N("%s choices", $rpmdrake::distrib{system}) : N("Mandriva Linux choices"), if_(0, # let's keep the translated strings (to be resurected as sorting the treeview): N("All packages, alphabetical"), by_presence => N("All packages, by update availability"), by_size => N("All packages, by size"), by_selection => N("All packages, by selection state"), by_leaves => N("Leaves only, sorted by install date"), by_group => N("All packages, by group"), ), backports => N("Backports"), all_updates => N("All updates"), security => N("Security updates"), bugfix => N("Bugfixes updates"), normal => N("Normal updates") ); my %rmodes = reverse %modes; $options->{rmodes} = \%rmodes; my %default_mode = (install => 'all', # we want the new GUI by default instead of "non_installed" remove => 'installed', update => 'security', ); my %wanted_categories = ( all_updates => [ qw(security bugfix normal) ], security => [ 'security' ], bugfix => [ 'bugfix' ], normal => [ 'normal' ], ); my $old_value; my $cbox = gtksignal_connect(Gtk2::ComboBox->new_with_strings([ @modes{'all', if_($compssUsers, 'mandrake_choices'), qw(installed non_installed all_updates security bugfix normal backports) } ], $modes{$default_mode{$MODE} || 'all'}), changed => sub { my $val = $_[0]->get_text; return if $val eq $old_value; # workarounding gtk+ sending us sometimes twice events $old_value = $val; $default_list_mode = $rmodes{$val}; if (my @cat = $wanted_categories{$rmodes{$val}} && @{$wanted_categories{$rmodes{$val}}}) { @$mandrakeupdate_wanted_categories = @cat; } if (0) { reset_search(); $options->{rebuild_tree}->(); } $options->{state}{flat} = 0; # was 1 only for the "All packages, alphabetical", "All packages, by size", "All packages, by selection state", and "Leaves only, sorted by install date" if ($options->{tree_mode} ne $val) { ($options->{tree_mode}) = $val =~ /^by/ ? $options->{tree_submode} : $rmodes{$val}; $tree_mode->[0] = $options->{tree_mode}; $tree_flat->[0] = $options->{state}{flat}; reset_search(); slow_func($::main_window->window, sub { switch_pkg_list_mode($rmodes{$val}) }); $options->{rebuild_tree}->(); } } ); my $default_radio = $options->{tree_mode} = $default_list_mode; $cbox->set_text($modes{$default_radio}); $options->{tree_submode} ||= $default_radio; $options->{tree_subflat} ||= $options->{state}{flat}; my @search_types = qw(normal descriptions summaries files); my $current_search_type = $search_types[0]; my $search_types_optionmenu = Gtk2::ComboBox->new_with_strings( [ N("in names"), N("in descriptions"), N("in summaries"), N("in file names"), ], N("in names") ); $search_types_optionmenu->signal_connect( changed => sub { $current_search_type = $search_types[$search_types_optionmenu->get_active]; }, ); my $info = Gtk2::Mdv::TextView->new; $info->set_left_margin(2); $info->set_right_margin(15); #- workaround when right elevator of scrolled window appears my $find_callback = sub { $clear_button and $clear_button->set_sensitive(1); do_search($find_entry, $tree, $tree_model, $options, $current_search_type, $urpm, $pkgs); }; my $hpaned = gtknew('HPaned', child1 => gtknew('ScrolledWindow', child => $tree, width => $typical_width*0.9), resize1 => 0, shrink1 => 0, resize2 => 1, shrink2 => 0, child2 => gtknew('VPaned', child1 => gtknew('ScrolledWindow', child => $detail_list), resize1 => 1, shrink1 => 0, child2 => gtknew('ScrolledWindow', child => $info), resize2 => 1, shrink2 => 0 ) ); my $reload_db_and_clear_all = sub { slow_func($::main_window->window, sub { $force_rebuild = 1; pkgs_provider({ skip_updating_mu => 1 }, $options->{tree_mode}); reset_search(); $size_selected = 0; $options->{rebuild_tree}->(); }); }; my $status = gtknew('Label'); my $checkbox_show_autoselect; my %check_boxes; my $auto_string = N("/_Options") . N("/_Select dependencies without asking"); my ($menu, $factory) = create_factory_menu( $w->{real_window}, [ N("/_File"), undef, undef, undef, '' ], if_( ! $>, [ N("/_File") . N("/_Update media"), undef, sub { update_sources_interactive($urpm, transient => $w->{real_window}) and $reload_db_and_clear_all->(); }, undef, '' ] ), [ N("/_File") . N("/_Reset the selection"), undef, sub { if ($MODE ne 'remove') { $urpm->disable_selected( open_rpm_db(), $urpm->{state}, map { if_($pkgs->{$_}{selected}, $pkgs->{$_}{pkg}) } keys %$pkgs, ); } $pkgs->{$_}{selected} = 0 foreach keys %$pkgs; reset_search(); $size_selected = 0; $force_displaying_group = 1; $tree->get_selection->signal_emit('changed'); }, undef, '' ], [ N("/_File") . N("/Reload the _packages list"), undef, $reload_db_and_clear_all, undef, '' ], [ N("/_File") . N("/_Quit"), N("Q"), sub { Gtk2->main_quit }, undef, '', ], #[ N("/_View"), undef, undef, undef, '' ], if_(!$>, [ N("/_Options"), undef, undef, undef, '' ], [ $auto_string, undef, sub { $urpm->{options}{auto} = $::rpmdrake_options{auto} = $check_boxes{$auto_string}->get_active if $check_boxes{$auto_string}; }, undef, '' ], [ N("/_Options") . N("/_Media Manager"), undef, sub { require Rpmdrake::edit_urpm_sources; Rpmdrake::edit_urpm_sources::run() && $reload_db_and_clear_all->(); }, undef, '' ], [ N("/_Options") . N("/_Show automatically selected packages"), undef, sub { $dont_show_selections = !$checkbox_show_autoselect->get_active; }, undef, '' ], ), [ N("/_Help"), undef, undef, undef, '' ], [ N("/_Help") . N("/_Report Bug"), undef, sub { run_program::raw({ detach => 1 }, 'drakbug', '--report', 'rpmdrake') }, undef, '' ], [ N("/_Help") . N("/_Help"), undef, sub { rpmdrake::open_help($MODE) }, undef, '' ], [ N("/_Help") . N("/_About..."), undef, sub { my $license = formatAlaTeX(translate($::license)); $license =~ s/\n/\n\n/sg; # nicer formatting my $w = gtknew('AboutDialog', name => N("Rpmdrake"), version => '2007', copyright => N("Copyright (C) %s by Mandriva", '2002-2007'), license => $license, wrap_license => 1, comments => N("Rpmdrake is Mandriva Linux package management tool."), website => 'http://mandrivalinux.com', website_label => N("Mandriva Linux"), authors => 'Thierry Vignaud ', artists => 'Hélène Durosini ', translator_credits => #-PO: put here name(s) and email(s) of translator(s) (eg: "John Smith ") N("_: Translator(s) name(s) & email(s)\n"), transient_for => $::main_window, modal => 1, position_policy => 'center-on-parent', ); $w->show_all; $w->run; }, undef, '' ] ); # to retrieve a path, one must prevent "accelerators completion": my $get_path = sub { return join('', map { my $i = $_; $i =~ s/_//g; $i } @_) }; %check_boxes = map { $_ => $factory->get_widget("
" . $get_path->($auto_string)); } ($auto_string); $check_boxes{$auto_string}->set_active($::rpmdrake_options{auto}) if !$>; $checkbox_show_autoselect = $factory->get_widget("
" . strip_first_underscore(N("/_Options"), N("/_Show automatically selected packages"))) and $checkbox_show_autoselect->set_active(!$dont_show_selections); gtkadd( $w->{window}, gtkpack_( gtknew('VBox', spacing => 3), 0, $menu, 0, getbanner(), 1, gtkadd( gtknew('Frame', border_width => 3, shadow_type => 'none'), gtkpack_( gtknew('VBox', spacing => 3), 0, gtkpack__( gtknew('HBox', spacing => 10), $cbox, gtknew('Label', text => N("Find:")), $search_types_optionmenu, gtksignal_connect( $find_entry = gtknew('Entry'), key_press_event => sub { $_[1]->keyval == $Gtk2::Gdk::Keysyms{Return} and $find_callback->(); }, ), gtksignal_connect(Gtk2::Button->new(but(N("Search"))), clicked => $find_callback), gtksignal_connect( $clear_button = Gtk2::Button->new(but(N("Clear"))), clicked => sub { reset_search() }, ), ), 1, $hpaned, 0, $status, 0, gtkpack_( gtknew('HBox', spacing => 20), 0, gtksignal_connect( Gtk2::Button->new(but_(N("Select all"))), clicked => sub { toggle_all($options, 1); }, ), 1, gtknew('Label'), 0, my $action_button = gtksignal_connect( Gtk2::Button->new(but_(N("Apply"))), clicked => sub { do_action($options, $callback_action, $info) }, ), 0, gtksignal_connect( Gtk2::Button->new(but_(N("Quit"))), clicked => sub { Gtk2->main_quit }, ), ), ), ), 0, $statusbar = Gtk2::Statusbar->new, ), ); $action_button->set_sensitive(0) if $>; $clear_button->set_sensitive(0); $find_entry->grab_focus; gtktext_insert($info, [ [ $info->render_icon('gtk-dialog-info', 'GTK_ICON_SIZE_DIALOG', undef) ], @{ ugtk2::markup_to_TextView_format( formatAlaTeX(join("\n\n\n", format_header(N("Quick Introduction")), N("You can browse the packages through the categories tree on the left."), N("You can view information about a package by clicking on it on the right list."), N("To install, update or remove a package, just click on its \"checkbox\".")))) } ]); $w->{rwindow}->set_default_size($typical_width*2.7, 500) if !$::isEmbedded; $find_entry->set_text($rpmdrake_options{search}[0]) if $rpmdrake_options{search}; $w->{rwindow}->show_all; $w->{rwindow}->set_sensitive(0); pkgs_provider({}, $default_list_mode); # default mode if (@initial_selection) { $options->{initial_selection} = \@initial_selection; $pkgs->{$_}{selected} = 0 foreach @initial_selection; } $w->{rwindow}->set_sensitive(1); $options->{widgets} = { w => $w, tree => $tree, tree_model => $tree_model, detail_list_model => $detail_list_model, detail_list => $detail_list, info => $info, status => $status, }; $options->{init_callback} = $find_callback if $rpmdrake_options{search}; ask_browse_tree_given_widgets_for_rpmdrake($options); } # -=-=-=---=-=-=---=-=-=-- main -=-=-=---=-=-=---=-=-=- $w = ugtk2->new(N("Software Management")); $w->{rwindow}->show_all if $::isEmbedded; readconf(); warn_about_user_mode(); do_merge_if_needed(); init(); run_treeview_dialog(\&perform_installation); writeconf(); myexit(0);