aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ManaTools/Rpmdragora/gui.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ManaTools/Rpmdragora/gui.pm')
-rw-r--r--lib/ManaTools/Rpmdragora/gui.pm1828
1 files changed, 1828 insertions, 0 deletions
diff --git a/lib/ManaTools/Rpmdragora/gui.pm b/lib/ManaTools/Rpmdragora/gui.pm
new file mode 100644
index 0000000..0cf0882
--- /dev/null
+++ b/lib/ManaTools/Rpmdragora/gui.pm
@@ -0,0 +1,1828 @@
+# vim: set et ts=4 sw=4:
+package ManaTools::Rpmdragora::gui;
+#*****************************************************************************
+#
+# Copyright (c) 2002 Guillaume Cottenceau
+# Copyright (c) 2002-2007 Thierry Vignaud <tvignaud@mandriva.com>
+# Copyright (c) 2003, 2004, 2005 MandrakeSoft SA
+# Copyright (c) 2005-2007 Mandriva SA
+# Copyright (c) 2013 - 2015 Matteo Pasotti <matteo.pasotti@gmail.com>
+# Copyright (c) 2014 - 2015 Angelo Naselli <anaselli@linux.it>
+#
+# 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$
+
+############################################################
+# WARNING: do not modify before asking matteo or anaselli
+############################################################
+
+use strict;
+use Sys::Syslog;
+
+use utf8;
+
+use MDK::Common::Func qw(before_leaving find any if_);
+use MDK::Common::DataStructure qw(difference2 member intersection);
+use MDK::Common::Various qw(chomp_ to_bool);
+use MDK::Common::String qw(formatAlaTeX);
+use MDK::Common::Math qw(sum);
+use MDK::Common::System qw(list_passwd);
+
+use ManaTools::rpmdragora;
+use ManaTools::Rpmdragora::open_db;
+use ManaTools::Rpmdragora::formatting;
+use ManaTools::Rpmdragora::init;
+use ManaTools::Rpmdragora::icon qw(get_icon_path);
+use ManaTools::Rpmdragora::pkg;
+use ManaTools::Shared;
+use ManaTools::Shared::GUI;
+use ManaTools::Shared::Locales;
+use ManaTools::Shared::RunProgram qw(get_stdout raw);
+use yui;
+use feature 'state';
+use Carp;
+
+use Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(
+ $descriptions
+ $find_entry
+ $force_displaying_group
+ $force_rebuild
+ $pkgs
+ $results_ok
+ $results_none
+ $size_free
+ $size_selected
+ $urpm
+ %grp_columns
+ %pkg_columns
+ @filtered_pkgs
+ @initial_selection
+ ask_browse_tree_given_widgets_for_rpmdragora
+ build_tree
+ callback_choices
+ compute_main_window_size
+ do_action
+ get_info
+ get_summary
+ group_has_parent
+ group_parent
+ groups_tree
+ is_locale_available
+ node_state
+ pkgs_provider
+ real_quit
+ reset_search
+ set_node_state
+ sort_callback
+ switch_pkg_list_mode
+ toggle_all
+ toggle_nodes
+ fast_toggle
+ setInfoOnWidget
+ );
+
+my $loc = ManaTools::rpmdragora::locale();
+my $shared_gui = ManaTools::Shared::GUI->new() ;
+
+our ($descriptions, %filters, @filtered_pkgs, %filter_methods, $force_displaying_group, $force_rebuild, @initial_selection, $pkgs, $size_free, $size_selected, $urpm);
+our ($results_ok, $results_none) = ($loc->N("Search results"), $loc->N("Search results (none)"));
+
+our %hidden_info = (
+ details => "details",
+ changelog => "changelog",
+ files => "file",
+ new_deps => "new_deps",
+
+);
+our %grp_columns = (
+ label => 0,
+ icon => 2,
+);
+
+our %pkg_columns = (
+ text => 0,
+ state_icon => 1,
+ state => 2,
+ selected => 3,
+ short_name => 4,
+ version => 5,
+ release => 6,
+ 'arch' => 7,
+ selectable => 8,
+);
+
+
+sub compute_main_window_size {
+ my ($w) = @_;
+ ($typical_width) = string_size($w->{real_window}, $loc->N("Graphical Environment") . "xmms-more-vis-plugins");
+ $typical_width > 600 and $typical_width = 600; #- try to not being crazy with a too large value
+ $typical_width < 150 and $typical_width = 150;
+}
+
+sub get_summary {
+ my ($key) = @_;
+ my $summary = $loc->N($pkgs->{$key}{pkg}->summary);
+ require utf8;
+ utf8::valid($summary) ? $summary : @{[]};
+}
+
+sub get_advisory_link {
+ my ($update_descr) = @_;
+
+ my $webref = "<br /><a href=\"". $update_descr->{URL} ."\">". $loc->N("Security advisory") ."</a><br />";
+
+ [ $webref ];
+}
+
+sub get_description {
+ my ($pkg, $update_descr) = @_;
+
+ my $descr = ($pkg->{description} ||
+ $update_descr->{description} ||
+ '<i>'. $loc->N("No description").'</i>');
+ $descr =~ s|\n|<br />|g;
+
+ return $descr . "<br />";
+}
+
+sub get_string_from_keywords {
+ my ($medium, $name) = @_;
+ my @media_types;
+ if ($medium->{mediacfg}) {
+ my ($distribconf, $medium_path) = @{$medium->{mediacfg}};
+ @media_types = split(':', $distribconf->getvalue($medium_path, 'media_type')) if $distribconf;
+ }
+
+ my $unsupported = $loc->N("It is <b>not supported</b> by Mageia.");
+ my $dangerous = $loc->N("It may <b>break</b> your system.");
+ my $s = "";
+ $s .= $loc->N("This package is not free software") . "\n" if member('non-free', @media_types);
+ if ($pkgs->{$name}{is_backport} || member('backport', @media_types)) {
+ return join("\n",
+ $loc->N("This package contains a new version that was backported."),
+ $unsupported, $dangerous, $s);
+ } elsif (member('testing', @media_types)) {
+ return join("\n",
+ $loc->N("This package is a potential candidate for an update."),
+ $unsupported, $dangerous, $s);
+ } elsif (member('updates', @media_types)) {
+ return join("\n",
+ (member('official', @media_types) ?
+ $loc->N("This is an official update which is supported by Mageia.")
+ : ($loc->N("This is an unofficial update."), $unsupported))
+ ,
+ $s);
+ } else {
+ $s .= $loc->N("This is an official package supported by Mageia") . "\n" if member('official', @media_types);
+ return $s if $s;
+ }
+
+ return;
+}
+
+sub get_main_text {
+ my ($medium, $fullname, $name, $summary, $is_update, $update_descr) = @_;
+
+ my $txt = get_string_from_keywords($medium, $fullname);
+ my $notice = format_field($loc->N("Notice: ")) . $txt if $txt;
+ ensure_utf8($notice);
+
+ my $hdr = "<h2>" . format_header(join(' - ', $name, $summary)) . "</h2>\n";
+ ensure_utf8($hdr);
+
+ my $update = MDK::Common::Func::if_($is_update, # is it an update?
+ format_field($loc->N("Importance: ")) . format_update_field($update_descr->{importance}),
+ format_field($loc->N("Reason for update: ")) . format_update_field(rpm_description($update_descr->{pre})),
+ );
+ ensure_utf8($update);
+
+ my $main_text = $hdr;
+ $main_text .= "&nbsp;&nbsp;&nbsp;" . $notice . "<br/>" if $notice;
+ $main_text .= "&nbsp;&nbsp;&nbsp;" . $update . "<br/>" if $update;
+
+ return $main_text;
+}
+
+sub get_details {
+ my ($pkg, $upkg, $installed_version, $raw_medium) = @_;
+ my @details = ();
+ push @details, format_field($loc->N("Version: ")) . $upkg->EVR;
+ push @details, format_field($loc->N("Currently installed version: ")) . $installed_version if($upkg->flag_installed);
+ push @details, format_field($loc->N("Group: ")) . translate_group($upkg->group);
+ push @details, format_field($loc->N("Architecture: ")) . $upkg->arch;
+ push @details, format_field($loc->N("Size: ")) . $loc->N("%s KB", int($upkg->size/1024));
+ push @details, eval { format_field($loc->N("Medium: ")) . $raw_medium->{name} };
+
+ my @link = get_url_link($upkg, $pkg);
+ push @details, join("<br />&nbsp;&nbsp;&nbsp;",@link) if(@link);
+ unshift @details, "<br />&nbsp;&nbsp;&nbsp;";
+ join("<br />&nbsp;&nbsp;&nbsp;", @details);
+}
+
+sub get_new_deps {
+ my ($urpm, $upkg) = @_;
+
+ my $deps = slow_func(sub {
+ my $state = {};
+ my $db = open_rpm_db();
+ my @requested = $urpm->resolve_requested__no_suggests_(
+ $db, $state,
+ { $upkg->id => 1 },
+ );
+ @requested = $urpm->resolve_requested_suggests($db, $state, \@requested);
+ undef $db;
+ my @nodes_with_deps = map { urpm_name($_) } @requested;
+ my @deps = sort { $a cmp $b } difference2(\@nodes_with_deps, [ urpm_name($upkg) ]);
+ @deps = $loc->N("All dependencies installed.") if !@deps;
+ return \@deps;
+ });
+ return $deps;
+}
+
+sub get_url_link {
+ my ($upkg, $pkg) = @_;
+
+ my $url = $upkg->url || $pkg->{url};
+
+ if (!$url) {
+ open_rpm_db()->traverse_tag_find('name', $upkg->name, sub { $url = $_[0]->url; return ($url ? 1 : 0) });
+ }
+
+ return if !$url;
+
+ my @a;
+ push @a, format_field($loc->N("URL: ")) . ${spacing} . "<a href=\"". $url ."\">". $url ."</a>";
+ @a;
+}
+
+sub files_format {
+ my ($files) = @_;
+
+ return '<tt>' . $spacing . "<br />&nbsp;&nbsp;&nbsp;" . join("<br />&nbsp;&nbsp;&nbsp;", @$files) . '</tt>';
+}
+
+#=============================================================
+
+=head2 format_link
+
+=head3 INPUT
+
+ $description: Description to be shown as link
+ $url: to be reach when click on $description link
+
+=head3 OUTPUT
+
+ $webref: href HTML tag
+
+=head3 DESCRIPTION
+
+ This function returns an href string to be published
+
+=cut
+
+#=============================================================
+sub format_link {
+ my ($description, $url) = @_;
+
+ my $webref = "<a href=\"". $url ."\">". $description ."</a>";
+
+ return $webref;
+}
+
+sub _format_pkg_simplifiedinfo {
+ my ($pkgs, $key, $urpm, $descriptions, $options) = @_;
+ my ($name) = split_fullname($key);
+ my $pkg = $pkgs->{$key};
+ my $upkg = $pkg->{pkg};
+ return if !$upkg;
+ my $raw_medium = pkg2medium($upkg, $urpm);
+ my $medium = !$raw_medium->{fake} ? $raw_medium->{name} : undef;
+ my $update_descr = $descriptions->{$medium}{$name};
+ # discard update fields if not matching:
+
+ my $is_update = ($upkg->flag_upgrade && $update_descr && $update_descr->{pre});
+ my $summary = get_summary($key);
+ my $dummy_string = get_main_text($raw_medium, $key, $name, $summary, $is_update, $update_descr);
+ my $s;
+ push @$s, $dummy_string;
+ push @$s, get_advisory_link($update_descr) if $is_update;
+
+ push @$s, get_description($pkg, $update_descr);
+
+ my $installed_version = eval { find_installed_version($upkg) };
+
+ #push @$s, [ gtkadd(gtkshow(my $details_exp = Gtk2::Expander->new(format_field($loc->N("Details:")))),
+ # gtknew('TextView', text => get_details($pkg, $upkg, $installed_version, $raw_medium))) ];
+ my $detail_link = format_link(format_field($loc->N("Details:")), $hidden_info{details} );
+ if ($options->{details}) {
+ my $details = get_details($pkg, $upkg, $installed_version, $raw_medium);
+ utf8::encode($details);
+ $detail_link .= "\n" . $details;
+ }
+ push @$s, join("\n", $detail_link, "\n");
+
+ #push @$s, [ build_expander($pkg, $loc->N("Files:"), 'files', sub { files_format($pkg->{files}) }) ];
+ my $files_link = format_link(format_field($loc->N("Files:")), $hidden_info{files} );
+ if ($options->{files}) {
+ my $wait = ManaTools::rpmdragora::wait_msg();
+ if (!$pkg->{files}) {
+ extract_header($pkg, $urpm, 'files', $installed_version);
+ }
+ my $files = $pkg->{files} ? files_format($pkg->{files}) : $loc->N("(Not available)");
+ utf8::encode($files);
+ $files_link .= "\n\n" . $files;
+ ManaTools::rpmdragora::remove_wait_msg($wait);
+ }
+ push @$s, join("\n", $files_link, "\n");
+
+ #push @$s, [ build_expander($pkg, $loc->N("Changelog:"), 'changelog', sub { $pkg->{changelog} }, $installed_version) ];
+ my $changelog_link = format_link(format_field($loc->N("Changelog:")), $hidden_info{changelog} );
+ if ($options->{changelog}) {
+ my $wait = ManaTools::rpmdragora::wait_msg();
+ my @changelog = $pkg->{changelog} ? @{$pkg->{changelog}} : ( $loc->N("(Not available)") );
+ if (!$pkg->{changelog} || !scalar @{$pkg->{changelog}} ) {
+ # my ($pkg, $label, $type, $get_data, $o_installed_version) = @_;
+ extract_header($pkg, $urpm, 'changelog', $installed_version);
+ @changelog = $pkg->{changelog} ? @{$pkg->{changelog}} : ( $loc->N("(Not available)") );
+ }
+ utf8::encode(\@changelog);
+
+ $changelog_link .= "<br />&nbsp;&nbsp;&nbsp;" . join("<br />&nbsp;&nbsp;&nbsp;", @changelog);
+ $changelog_link =~ s|\n||g;
+ ManaTools::rpmdragora::remove_wait_msg($wait);
+ }
+ push @$s, join("\n\n", $changelog_link, "\n");
+
+ my $deps_link = format_link(format_field($loc->N("New dependencies:")), $hidden_info{new_deps} );
+ if ($options->{new_deps}) {
+ if ($upkg->id) { # If not installed
+ $deps_link .= "<br />&nbsp;&nbsp;&nbsp;" . join("<br />&nbsp;&nbsp;&nbsp;", @{get_new_deps($urpm, $upkg)});
+ # push @$s, get_new_deps($urpm, $upkg);
+ }
+ }
+ push @$s, join("\n", $deps_link, "\n");
+
+ return $s;
+}
+
+sub warn_if_no_pkg {
+ my ($name) = @_;
+ my ($short_name) = split_fullname($name);
+ state $warned;
+ if (!$warned) {
+ $warned = 1;
+ interactive_msg($loc->N("Warning"),
+ join("\n",
+ $loc->N("The package \"%s\" was found.", $name),
+ $loc->N("However this package is not in the package list."),
+ $loc->N("You may want to update your urpmi database."),
+ '',
+ $loc->N("Matching packages:"),
+ '',
+ join("\n", sort map {
+ #-PO: this is list fomatting: "- <package_name> (medium: <medium_name>)"
+ #-PO: eg: "- rpmdragora (medium: "Main Release"
+ $loc->N("- %s (medium: %s)", $_, pkg2medium($pkgs->{$_}{pkg}, $urpm)->{name});
+ } grep { /^$short_name/ } keys %$pkgs),
+ ),
+ scroll => 1,
+ );
+ }
+ return 'XXX';
+}
+
+#
+# @method node_state
+#
+=pod
+
+=head1 node_state(pkgname)
+
+=over 4
+
+=item returns the state of the node (pkg) querying an urpm object from $pkgs->{$pkgname}
+
+=over 6
+
+=item I<to_install>
+
+=item I<to_remove>
+
+=item I<to_update>
+
+=item I<installed>
+
+=item I<uninstalled>
+
+=back
+
+=back
+
+=cut
+
+sub node_state {
+ my ($name) = @_;
+ #- checks $_[0] -> hack for partial tree displaying
+ return 'XXX' if !$name;
+ my $pkg = $pkgs->{$name};
+ my $urpm_obj = $pkg->{pkg};
+ return warn_if_no_pkg($name) if !$urpm_obj;
+ $pkg->{selected} ?
+ ($urpm_obj->flag_installed ?
+ ($urpm_obj->flag_upgrade ? 'to_install' : 'to_remove')
+ : 'to_install')
+ : ($urpm_obj->flag_installed ?
+ ($pkgs->{$name}{is_backport} ? 'backport' :
+ ($urpm_obj->flag_upgrade ? 'to_update'
+ : ($urpm_obj->flag_base ? 'base' : 'installed')))
+ : 'uninstalled');
+}
+
+my ($common, $w, %wtree, %ptree, %pix, @table_item_list);
+
+#
+# @method set_node_state
+#
+
+=pod
+
+=head1 set_node_state($tblItem, $state, $detail_list)
+
+=over 4
+
+=item setup the table row by adding a cell representing the state of the package
+
+=item see node_state
+
+=over 6
+
+=item B<$tblItem> , YTableItem instance
+
+=item B<$state> , string containing the state of the package from node_state
+
+=item B<$detail_list> , reference to the YCBTable
+
+=back
+
+=back
+
+=cut
+
+sub set_node_state {
+ my ($tblItem, $state, $detail_list) = @_;
+ return if $state eq 'XXX' || !$state;
+
+ if ($detail_list) {
+ $detail_list->parent()->parent()->startMultipleChanges();
+ $tblItem->addCell($state,"/usr/share/rpmdrake/icons/state_$state.png") if(ref $tblItem eq "yui::YCBTableItem");
+ if(to_bool(member($state, qw(base installed to_install)))){
+ # it should be parent()->setChecked(1)
+ $detail_list->checkItem($tblItem, 1);
+ # $tblItem->setSelected(1);
+ }else{
+ $detail_list->checkItem($tblItem, 0);
+ # $tblItem->setSelected(0);
+ }
+# if(!to_bool($state ne 'base')){
+# #$iter->cell(0)->setLabel('-');
+# $tblItem->cell(0)->setLabel('-');
+# }
+ $detail_list->parent()->parent()->doneMultipleChanges();
+ }
+ else {
+ # no item list means we use just the item to add state information
+ $tblItem->addCell($state,"/usr/share/rpmdrake/icons/state_$state.png") if(ref $tblItem eq "yui::YCBTableItem");
+ $tblItem->check(to_bool(member($state, qw(base installed to_install))));
+# $tblItem->cell(0)->setLabel('-') if !to_bool($state ne 'base');
+ }
+}
+
+sub set_leaf_state {
+ my ($leaf, $state, $detail_list) = @_;
+ # %ptree is a hash using the pkg name as key and a monodimensional array (?) as value
+ # were it is stored the index of the item into the table
+ my $nodeIndex = $ptree{$leaf}[0];
+ my $node = itemAt($detail_list,$nodeIndex);
+ set_node_state($node, $state, $detail_list);
+}
+
+#=============================================================
+
+=head2 grep_unselected
+
+=head3 INPUT
+
+ $l: ARRAY reference containing the list of package fullnames
+
+=head3 OUTPUT
+
+ \@result: ARRAY reference containing the list of packages
+ that are not selected
+
+=head3 DESCRIPTION
+
+ Function returning the list of not selected packages
+
+=cut
+
+#=============================================================
+sub grep_unselected {
+ my $l = shift;
+
+ my @result = grep { exists $pkgs->{$_} && !$pkgs->{$_}{selected} } @{$l} ;
+ return \@result;
+}
+
+my %groups_tree = ();
+
+#
+# @method add_parent
+#
+
+=pod
+
+=head1 add_parent($tree, $root, $state)
+
+=over 4
+
+=item populates the treeview with the rpm package groups
+
+=over 6
+
+=item B<$tree> , YTree for the group of the rpm packages
+
+=item B<$root> , string containing a path-like sequence (e.g. "foo|bar")
+
+=item B<$state> , not used currently (from the old impl.)
+
+=back
+
+=back
+
+=cut
+
+sub add_parent {
+ my ($tree, $root, $state) = @_;
+ $tree or return undef;
+ #$root or return undef;
+ my $parent = 0;
+
+ carp "WARNING TODO: add_parent to be removed (called with " . $root . ")\n";
+
+ my @items = split('\|', $root);
+ my $i = 0;
+ my $parentItem = undef;
+ my $rootItem = undef;
+ for my $item (@items) {
+ chomp $item;
+ $item = trim($item);
+ my $treeItem;
+ if($i == 0){
+ $parent = $item;
+ $treeItem = new yui::YTreeItem($item,get_icon_path($item,0),0);
+ $rootItem = $treeItem;
+ $parentItem = $treeItem;
+ if(!defined($groups_tree{$parent})) {
+ $groups_tree{$parent}{parent} = $treeItem;
+ $groups_tree{$parent}{children} = ();
+# $tree->addItem($groups_tree{$parent}{'parent'});
+ }
+ }else{
+ #if(any { $_ ne $item } @{$groups_tree{$parent}{'children'}}){
+ # push @{$groups_tree{$parent}{'children'}}, $item;
+ #}
+ $parentItem = new yui::YTreeItem($parentItem, $item,get_icon_path($item,$parent),0);
+ if(!defined($groups_tree{$parent}{'children'}{$item})){
+# $treeItem = new yui::YTreeItem($treeItem, $item,get_icon_path($item,$parent),0);
+ $treeItem = $parentItem;
+ $groups_tree{$parent}{'children'}{$item} = $treeItem;
+ $groups_tree{$parent}{'parent'}->addChild($treeItem);
+ }
+ }
+ $i++;
+ }
+ $tree->addItem($rootItem) if $rootItem;
+ $tree->rebuildTree();
+}
+
+
+#=============================================================
+
+=head2 add_tree_item
+
+=head3 INPUT
+
+=item B<$tree> YTree for the group of the rpm packages
+
+=item B<$item> string containing a group (to be added as leaf node)
+
+=item B<$selected> if the new item is selected
+
+
+=head3 DESCRIPTION
+
+ This function add a group to the tree view
+
+=cut
+
+#=============================================================
+sub add_tree_item {
+ my ($tree, $item, $selected) = @_;
+
+ $tree or return undef;
+
+ $tree->startMultipleChanges();
+ my $treeItem = new yui::YTreeItem($item, get_icon_path($item, 0), 0);
+ $treeItem->setSelected($selected);
+
+ $tree->addItem($treeItem);
+ $tree->rebuildTree();
+ $tree->doneMultipleChanges();
+}
+
+#=============================================================
+
+=head2 add_package_item
+
+=head3 INPUT
+
+=item B<$item_list>: reference to a YItemCollection
+
+=item B<$pkg_name>: package name
+
+=item B<$select>: select given package
+
+=head3 DESCRIPTION
+
+ populates the item list for the table view with the given rpm package
+
+=cut
+
+#=============================================================
+sub add_package_item {
+ my ($item_list, $pkg_name, $select) = @_;
+
+ return if !$pkg_name;
+
+ return if ref($item_list) ne "yui::YItemCollection";
+
+ my $state = node_state($pkg_name) or return;
+
+ my $iter;
+ if (is_a_package($pkg_name)) {
+ my ($name, $version, $release, $arch) = split_fullname($pkg_name);
+
+ $name = "" if !defined($name);
+ $version = "" if !defined($version);
+ $release = "" if !defined($release);
+ $arch = "" if !defined($arch);
+
+ my $newTableItem = new yui::YCBTableItem(
+ $name,
+ get_summary($pkg_name),
+ $version,
+ $release,
+ $arch
+ );
+
+ $newTableItem->setSelected($select);
+
+ set_node_state($newTableItem, $state);
+
+ $item_list->push($newTableItem);
+
+ $newTableItem->DISOWN();
+ }
+ else {
+ carp $pkg_name . " is not a leaf package and that is not managed!";
+ }
+
+}
+
+#
+# @method add_node
+#
+
+=pod
+
+=head1 add_node($leaf, $root, $options)
+
+=over 4
+
+=item populates the tableview with the rpm packages or the treeview with the package groups
+
+=over 6
+
+=item B<$leaf> , could be the name of a package or the name of a group o packages
+
+=item B<$root> , string containing a path-like sequence (e.g. "foo|bar")
+
+=item B<$state> , the string with the state of the package if leaf is the name of a package
+
+=back
+
+=back
+
+=cut
+
+sub add_node {
+ my ($leaf, $root, $o_options) = @_;
+ my $state = node_state($leaf) or return;
+ if ($leaf) {
+ my $iter;
+ if (is_a_package($leaf)) {
+carp "TODO: add_node is_a_package(\$leaf)" . $leaf . "\n";
+
+ my ($name, $version, $release, $arch) = split_fullname($leaf);
+ #OLD $iter = $w->{detail_list_model}->append_set([ $pkg_columns{text} => $leaf,
+ # $pkg_columns{short_name} => format_name_n_summary($name, get_summary($leaf)),
+ # $pkg_columns{version} => $version,
+ # $pkg_columns{release} => $release,
+ # $pkg_columns{arch} => $arch,
+ # ]);
+ $name = "" if(!defined($name));
+ $version = "" if(!defined($version));
+ $release = "" if(!defined($release));
+ $arch = "" if(!defined($arch));
+ #my $newTableItem = new yui::YTableItem(format_name_n_summary($name, get_summary($leaf)),
+ my $newTableItem = new yui::YCBTableItem($name,
+ get_summary($leaf),
+ $version,
+ $release,
+ $arch);
+ $w->{detail_list}->addItem($newTableItem);
+ set_node_state($newTableItem, $state, $w->{detail_list});
+ # $ptree{$leaf} = [ $newTableItem->label() ];
+ $ptree{$leaf} = [ $newTableItem->index() ];
+ $table_item_list[$newTableItem->index()] = $leaf;
+ $newTableItem->DISOWN();
+ } else {
+ carp "TODO: add_node !is_a_package(\$leaf) not MANAGED\n";
+# $iter = $w->{tree_model}->append_set(add_parent($w->{tree},$root, $state), [ $grp_columns{label} => $leaf ]);
+ #push @{$wtree{$leaf}}, $iter;
+ }
+ } else {
+ carp "TODO: add_node !\$leaf not MANAGED\n";
+
+# my $parent = add_parent($w->{tree}, $root, $state);
+ #- hackery for partial displaying of trees, used in rpmdragora:
+ #- if leaf is void, we may create the parent and one child (to have the [+] in front of the parent in the ctree)
+ #- though we use '' as the label of the child; then rpmdragora will connect on tree_expand, and whenever
+ #- the first child has '' as the label, it will remove the child and add all the "right" children
+# $o_options->{nochild} or $w->{tree_model}->append_set($parent, [ $grp_columns{label} => '' ]); # test $leaf?
+ }
+}
+
+my ($prev_label);
+sub update_size {
+ my ($common) = shift @_;
+ if ($w->{status}) {
+ my $new_label = $common->{get_status}();
+ $prev_label="" if(!defined($prev_label));
+ $prev_label ne $new_label and $w->{status}->setText($prev_label = $new_label);
+ }
+}
+
+#=============================================================
+
+=head2 treeview_children
+
+=head3 INPUT
+
+ $tbl: YCBTable widget containing the package list shown
+
+=head3 OUTPUT
+
+ \@l: ARRAY reference containing the list
+ of YItem contained into YCBTable
+
+=head3 DESCRIPTION
+
+ This functions just returns the YCBTable content such as all the
+ YItem objects
+
+=cut
+
+#=============================================================
+sub treeview_children {
+ my ($tbl) = @_;
+ my $it;
+ my @l;
+ my $i=0;
+ # using iterators
+ for ($it = $tbl->itemsBegin(); $it != $tbl->itemsEnd(); ) {
+ my $item = $tbl->YItemIteratorToYItem($it);
+ push @l, $item;
+ $it = $tbl->nextItem($it);
+ $i++;
+ if ($i == $tbl->itemsCount()) {
+ last;
+ }
+ }
+
+ return \@l;
+}
+
+#=============================================================
+
+=head2 children
+
+=head3 INPUT
+
+ $tbl: YCBTable object
+ @table_item_list: array containing package fullnames
+
+=head3 OUTPUT
+
+ \@result: ARRAY reference containing package fullnames
+
+=head3 DESCRIPTION
+
+ This Function return the list of package fullnames
+
+=cut
+
+#=============================================================
+
+sub children {
+ my ($tbl, $table_item_list) = @_;
+ # map { $w->{detail_list}->get($_, $pkg_columns{text}) } treeview_children($w->{detail_list});
+ # map { $table_item_list[$_->index()] } treeview_children($w->{detail_list});
+ my $children_list = treeview_children($tbl);
+ my @result;
+ for my $child(@{$children_list}){
+ push @result, $table_item_list->[$child->index()];
+ }
+ return \@result;
+}
+
+sub itemAt {
+ my ($table, $index) = @_;
+ return $table->item($index);
+ #return bless ($table->item($index),'yui::YTableItem');
+ #foreach my $item(treeview_children($table)){
+ # if($item->index() == $index){
+ # print "\n== item label ".$item->label()."\n";
+ # return bless ($item, 'yui::YTableItem');
+ # }
+ #}
+}
+
+
+#=============================================================
+
+=head2 toggle_all
+
+=head3 INPUT
+
+ $common: HASH reference containing (### TODO ###)
+ widgets => {
+ detail_list: YTable reference (?)
+ }
+ table_item_list => array containing package fullnames
+ partialsel_unsel => (?)
+
+ set_state_callback => set state callback invoked by
+ toggle_nodes if needed. if undef
+ set_leaf_state is used.
+ $_val: value to be set (so it seems not a toggle! unused?)
+
+=head3 DESCRIPTION
+
+This method (should) check -or un-check if already checked- all
+the packages
+
+=cut
+
+#=============================================================
+sub toggle_all {
+ my ($common, $_val) = @_;
+ my $w = $common->{widgets};
+
+ my $l = children($w->{detail_list}, $common->{table_item_list}) or return;
+
+ my $set_state = $common->{set_state_callback} ? $common->{set_state_callback} : \&set_leaf_state;
+ my $unsel = grep_unselected($l);
+ my $p = $unsel ?
+ #- not all is selected, select all if no option to potentially override
+ (exists $common->{partialsel_unsel} && $common->{partialsel_unsel}->($unsel, $l) ? difference2($l, $unsel) : $unsel)
+ : $l;
+ # toggle_nodes($w->{detail_list}, $w->{detail_list_model}, \&set_leaf_state, node_state($p[0]), @p);
+
+ toggle_nodes($w->{detail_list}, $w->{detail_list}, $set_state, node_state($p->[0]), @{$p});
+ update_size($common);
+}
+
+sub fast_toggle {
+ my ($item) = @_;
+ #gtkset_mousecursor_wait($w->{w}{rwindow}->window);
+ #my $_cleaner = before_leaving { gtkset_mousecursor_normal($w->{w}{rwindow}->window) };
+ my $name = $common->{table_item_list}[$item->index()];
+ my $urpm_obj = $pkgs->{$name}{pkg};
+ if ($urpm_obj->flag_base) {
+ interactive_msg($loc->N("Warning"), $loc->N("Removing package %s would break your system", $name));
+ return '';
+ }
+ if ($urpm_obj->flag_skip) {
+ interactive_msg($loc->N("Warning"), $loc->N("The \"%s\" package is in urpmi skip list.\nDo you want to select it anyway?", $name), yesno => 1) or return '';
+ $urpm_obj->set_flag_skip(0);
+ }
+ if ($ManaTools::Rpmdragora::pkg::need_restart && !$priority_up_alread_warned) {
+ $priority_up_alread_warned = 1;
+ interactive_msg($loc->N("Warning"), '<b>' . $loc->N("Rpmdragora or one of its priority dependencies needs to be updated first. Rpmdragora will then restart.") . '</b>' . "\n\n");
+ }
+ # toggle_nodes($w->{tree}->window, $w->{detail_list_model}, \&set_leaf_state, $w->{detail_list_model}->get($iter, $pkg_columns{state}),
+
+ my $old_status = node_state($name);
+
+ #$DB::single = 1;
+
+# my $old_state;
+# if($item->checked()){
+# $old_state = "to_install";
+# }else{
+# $old_state = "to_remove";
+# }
+ toggle_nodes($w->{tree}, $w->{detail_list}, \&set_leaf_state, $old_status, $name);
+ update_size($common);
+};
+
+# ask_browse_tree_given_widgets_for_rpmdragora will run gtk+ loop. its main parameter "common" is a hash containing:
+# - a "widgets" subhash which holds:
+# o a "w" reference on a ugtk2 object
+# o "tree" & "info" references a TreeView
+# o "info" is a TextView
+# o "tree_model" is the associated model of "tree"
+# o "status" references a Label
+# - some methods: get_info, node_state, build_tree, partialsel_unsel, grep_unselected, rebuild_tree, toggle_nodes, get_status
+# - "tree_submode": the default mode (by group, ...), ...
+# - "state": a hash of misc flags: => { flat => '0' },
+# o "flat": is the tree flat or not
+# - "tree_mode": mode of the tree ("gui_pkgs", "by_group", ...) (mainly used by rpmdragora)
+
+sub ask_browse_tree_given_widgets_for_rpmdragora {
+ ($common) = @_;
+ $w = $common->{widgets};
+
+ $common->{table_item_list} = \@table_item_list;
+
+ $w->{detail_list} ||= $w->{tree};
+ #$w->{detail_list_model} ||= $w->{tree_model};
+
+ $common->{add_parent} = \&add_parent;
+ my $clear_all_caches = sub {
+ %ptree = %wtree = ();
+ @table_item_list = ();
+ };
+ $common->{clear_all_caches} = $clear_all_caches;
+ $common->{delete_all} = sub {
+ $clear_all_caches->();
+ %groups_tree = ();
+ };
+ $common->{rebuild_tree} = sub {
+ $common->{delete_all}->();
+ $common->{build_tree}($common->{state}{flat}, $common->{tree_mode});
+ update_size($common);
+ };
+ $common->{delete_category} = sub {
+ my ($cat) = @_;
+ carp "WARNING TODO delete_category to be removed!";
+ exists $wtree{$cat} or return;
+ %ptree = ();
+ update_size($common);
+ return;
+ };
+ $common->{add_nodes} = sub {
+ my (@nodes) = @_;
+ carp "TODO ==================> ADD NODES - add packages (" . scalar(@nodes) . ") \n";
+ yui::YUI::app()->busyCursor();
+
+ my $lastItem = $w->{detail_list}->selectedItem() ? $w->{detail_list}->selectedItem()->label() : "";
+ $w->{detail_list}->startMultipleChanges();
+ $w->{detail_list}->deleteAllItems();
+ my $itemColl = new yui::YItemCollection;
+
+ @table_item_list = ();
+ my $index = 0;
+ foreach(@nodes){
+ add_package_item($itemColl, $_->[0], ($lastItem eq $_->[0]));
+ warn "Unmanaged param " . $_->[2] if defined $_->[2];
+ $ptree{$_->[0]} = [ $index ];
+ $index++;
+ push @table_item_list, $_->[0];
+ }
+
+ update_size($common);
+ $w->{detail_list}->addItems($itemColl);
+ $w->{detail_list}->doneMultipleChanges();
+ yui::YUI::app()->normalCursor();
+ };
+
+ $common->{rebuild_tree}->();
+ update_size($common);
+ $common->{initial_selection} and toggle_nodes($w->{tree}->window, $w->{detail_list}, \&set_leaf_state, undef, @{$common->{initial_selection}});
+ my $_b = before_leaving { $clear_all_caches->() };
+ $common->{init_callback}->() if $common->{init_callback};
+
+ $w->{w};
+}
+
+our $find_entry;
+
+sub reset_search() {
+ return if !$common;
+
+# TODO $common->{delete_category}->($_) foreach $results_ok, $results_none;
+ # clear package list:
+ yui::YUI::app()->busyCursor();
+ my $wdgt = $common->{widgets};
+ $wdgt->{detail_list}->startMultipleChanges();
+ $wdgt->{detail_list}->deleteAllItems();
+ $wdgt->{detail_list}->doneMultipleChanges();
+ yui::YUI::app()->normalCursor();
+}
+
+sub is_a_package {
+ my ($pkg) = @_;
+ return exists $pkgs->{$pkg};
+}
+
+sub switch_pkg_list_mode {
+ my ($mode) = @_;
+ return if !$mode;
+ return if !$filter_methods{$mode};
+ $force_displaying_group = 1;
+ $filter_methods{$mode}->();
+}
+
+sub is_updatable {
+ my $p = $pkgs->{$_[0]};
+ $p->{pkg} && !$p->{selected} && $p->{pkg}->flag_installed && $p->{pkg}->flag_upgrade;
+}
+
+sub pkgs_provider {
+ my ($mode, %options) = @_;
+ return if !$mode;
+ my $h = &get_pkgs(%options);
+ ($urpm, $descriptions) = @$h{qw(urpm update_descr)};
+ $pkgs = $h->{all_pkgs};
+ %filters = (
+ non_installed => $h->{installable},
+ installed => $h->{installed},
+ all => [ keys %$pkgs ],
+ );
+ my %tmp_filter_methods = (
+ all => sub {
+ [ difference2([ keys %$pkgs ], $h->{inactive_backports}) ];
+ },
+ all_updates => sub {
+ # potential "updates" from media not tagged as updates:
+ if (!$options{pure_updates} && !$ManaTools::Rpmdragora::pkg::need_restart) {
+ [ @{$h->{updates}},
+ difference2([ grep { is_updatable($_) } @{$h->{installable}} ], $h->{backports}) ];
+ } else {
+ [ difference2($h->{updates}, $h->{inactive_backports}) ];
+ }
+ },
+ backports => sub { $h->{backports} },
+ meta_pkgs => sub {
+ [ difference2($h->{meta_pkgs}, $h->{inactive_backports}) ];
+ },
+ gui_pkgs => sub {
+ [ difference2($h->{gui_pkgs}, $h->{inactive_backports}) ];
+ },
+ );
+ foreach my $importance (qw(bugfix security normal)) {
+ $tmp_filter_methods{$importance} = sub {
+ my @media = keys %$descriptions;
+ [ grep {
+ my ($name) = split_fullname($_);
+ my $medium = MDK::Common::Func::find { $descriptions->{$_}{$name} } @media;
+ $medium && $descriptions->{$medium}{$name}{importance} eq $importance } @{$h->{updates}} ];
+ };
+ }
+
+ undef %filter_methods;
+ foreach my $type (keys %tmp_filter_methods) {
+ $filter_methods{$type} = sub {
+ $force_rebuild = 1; # force rebuilding tree since we changed filter (FIXME: switch to SortModel)
+ @filtered_pkgs = intersection($filters{$filter->[0]}, $tmp_filter_methods{$type}->());
+ };
+ }
+
+ switch_pkg_list_mode($mode);
+}
+
+sub closure_removal {
+ local $urpm->{state} = {};
+ urpm::select::find_packages_to_remove($urpm, $urpm->{state}, \@_);
+}
+
+sub is_locale_available {
+ my ($name) = @_;
+ any { $urpm->{depslist}[$_]->flag_selected } keys %{$urpm->{provides}{$name} || {}} and return 1;
+ my $found;
+ open_rpm_db()->traverse_tag_find('name', $name, sub { $found = 1 });
+ return $found;
+}
+
+sub callback_choices {
+ my (undef, undef, undef, $choices) = @_;
+ return $choices->[0] if $::rpmdragora_options{auto};
+ foreach my $pkg (@$choices) {
+ foreach ($pkg->requires_nosense) {
+ /locales-/ or next;
+ is_locale_available($_) and return $pkg;
+ }
+ }
+ my $callback = sub { interactive_msg($loc->N("More information on package..."), get_info($_[0]), scroll => 1) };
+ $choices = [ sort { $a->name cmp $b->name } @$choices ];
+ my @choices = interactive_list_($loc->N("Please choose"), (scalar(@$choices) == 1 ?
+ $loc->N("The following package is needed:") : $loc->N("One of the following packages is needed:")),
+ [ map { urpm_name($_) } @$choices ], $callback, nocancel => 1);
+ defined $choices[0] ? $choices->[$choices[0]] : undef;
+}
+
+#=============================================================
+
+=head2 info_details
+
+=head3 INPUT
+
+ $info_detail_selected: string to get more info details
+ (see %hidden_info)
+ $info_options: reference to info options that are going to changed
+ based on passed $info_detail_selected
+
+=head3 OUTPUT
+
+ [0, 1]: 0 if $info_detail_selected not valid, 1 otherwise
+
+=head3 DESCRIPTION
+
+ This function change the info_options accordingly to the string passed
+ returning 0 if the string is not managed (see %hidden_info)
+
+=cut
+
+#=============================================================
+sub info_details {
+ my ($info_detail_selected, $info_options) = @_;
+
+ foreach my $k (keys %hidden_info) {
+ if ($info_detail_selected eq $hidden_info{$k}) {
+ $info_options->{$k} = $info_options->{$k} ? 0 : 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#=============================================================
+
+=head2 setInfoOnWidget
+
+=head3 INPUT
+
+ $pckgname: full name of the package
+ $infoWidget: YRichText object to fill
+
+=head3 DESCRIPTION
+
+ This function writes on a YRichText object package info
+
+=cut
+
+#=============================================================
+sub setInfoOnWidget {
+ my ($pkgname, $infoWidget, $options) = @_;
+
+ return if( ref $infoWidget ne "yui::YRichText");
+
+ $infoWidget->setValue("");
+
+ my $info_text = "";
+
+ my @data = get_info($pkgname, $options);
+ for(@{$data[0]}){
+ if(ref $_ ne "ARRAY"){
+ $info_text .= $_ . "<br />&nbsp;&nbsp;&nbsp;";
+ }else{
+ for my $subitem(@{$_}) {
+ $info_text .= "<br />" . "<br />&nbsp;&nbsp;&nbsp;" . $subitem;
+ }
+ }
+ }
+ # change \n to <br/>
+ $info_text =~ s|\n|<br/>|g;
+
+ $infoWidget->setValue($info_text);
+}
+
+
+sub deps_msg {
+ return 1 if $dont_show_selections->[0];
+ my ($title, $msg, $nodes, $nodes_with_deps) = @_;
+
+ my @deps = sort { $a cmp $b } difference2($nodes_with_deps, $nodes);
+ @deps > 0 or return 1;
+
+ my $appTitle = yui::YUI::app()->applicationTitle();
+
+ ## set new title to get it in dialog
+ yui::YUI::app()->setApplicationTitle($title);
+# TODO icon if needed
+# yui::YUI::app()->setApplicationIcon($which_icon);
+
+ my $factory = yui::YUI::widgetFactory;
+
+ ## | [msg-label] |
+ ## | |
+ ## | pkg-list | info on selected pkg |(1)
+ ## | |
+ ## | [cancel] [ok] |
+ ####
+ # (1) info on pkg list:
+ # [ label info ]
+ # sub info on click (Details, Files, Changelog, New dependencies)
+
+ my $dialog = $factory->createPopupDialog;
+ my $vbox = $factory->createVBox( $dialog );
+ my $msgBox = $factory->createLabel($vbox, $msg, 1);
+# my $hbox = $factory->createHBox( $vbox );
+ my $pkgList = $factory->createSelectionBox( $vbox, $loc->N("Select package") );
+ $factory->createVSpacing($vbox, 1);
+
+# my $frame = $factory->createFrame ($hbox, $loc->N("Information on packages"));
+# my $frmVbox = $factory->createVBox( $frame );
+ my $infoBox = $factory->createRichText($vbox, "", 0);
+ $pkgList->setWeight($yui::YD_HORIZ, 2);
+ $infoBox->setWeight($yui::YD_HORIZ, 3);
+ $factory->createVSpacing($vbox, 1);
+ my $hbox = $factory->createHBox( $vbox );
+ my $align = $factory->createRight($hbox);
+ my $cancelButton = $factory->createPushButton($align, $loc->N("&Cancel"));
+ my $okButton = $factory->createPushButton($hbox, $loc->N("&Ok"));
+
+ # adding packages to the list
+ my $itemColl = new yui::YItemCollection;
+ foreach my $p (map { scalar(urpm::select::translate_why_removed_one($urpm, $urpm->{state}, $_)) } @deps) {
+ my $item = new yui::YTableItem ("$p");
+ $item->setLabel( $p );
+ $itemColl->push($item);
+ $item->DISOWN();
+ }
+ $pkgList->addItems($itemColl);
+ $pkgList->setImmediateMode(1);
+ my $item = $pkgList->selectedItem();
+ if ($item) {
+ my $pkg = $item->label();
+ setInfoOnWidget($pkg, $infoBox);
+ }
+
+ my $retval = 0;
+ my $info_options = {};
+ while(1) {
+ my $event = $dialog->waitForEvent();
+ my $eventType = $event->eventType();
+
+ #event type checking
+ if ($eventType == $yui::YEvent::CancelEvent) {
+ last;
+ }
+ elsif ($eventType == $yui::YEvent::MenuEvent) {
+ my $item = $event->item();
+ if (!$item) {
+ #URL emitted or at least a ref into RichText widget
+ my $url = yui::toYMenuEvent($event)->id ();
+ if (ManaTools::Rpmdragora::gui::info_details($url, $info_options) ) {
+ $item = $pkgList->selectedItem();
+ my $pkg = $item->label();
+ ManaTools::Rpmdragora::gui::setInfoOnWidget($pkg, $infoBox, $info_options);
+ }
+ else {
+ # default it's really a URL
+ ManaTools::Rpmdragora::gui::run_browser($url);
+ }
+ }
+ }
+ elsif ($eventType == $yui::YEvent::WidgetEvent) {
+ ### widget
+ my $widget = $event->widget();
+ if ($widget == $pkgList) {
+ #change info
+ $item = $pkgList->selectedItem();
+ $info_options = {};
+ if ( $item ) {
+ my $pkg = $item->label();
+ setInfoOnWidget($pkg, $infoBox);
+ }
+ }
+ elsif ($widget == $okButton) {
+ $retval = 1;
+ last;
+ }
+ elsif ($widget == $cancelButton) {
+ last;
+ }
+ }
+ }
+
+ destroy $dialog;
+
+ # restore original title
+ yui::YUI::app()->setApplicationTitle($appTitle) if $appTitle;
+
+ return $retval;
+}
+
+# set_state <- package-fullname, node_state = {to_install, to_remove,...}, list=YTable
+sub toggle_nodes {
+ my ($widget, $detail_list, $set_state, $old_state, @nodes) = @_;
+
+ @nodes = grep { exists $pkgs->{$_} } @nodes
+ or return;
+ #- avoid selecting too many packages at once
+ return if !$dont_show_selections->[0] && @nodes > 2000;
+ my $new_state = !$pkgs->{$nodes[0]}{selected};
+
+ my @nodes_with_deps;
+
+ my $bar_id = statusbar_msg($loc->N("Checking dependencies of package..."), 0);
+
+ my $warn_about_additional_packages_to_remove = sub {
+ my ($msg) = @_;
+ statusbar_msg_remove($bar_id);
+ deps_msg($loc->N("Some additional packages need to be removed"),
+ MDK::Common::String::formatAlaTeX($msg) . "\n\n",
+ \@nodes, \@nodes_with_deps) or @nodes_with_deps = ();
+ };
+
+ if (member($old_state, qw(to_remove installed))) { # remove pacckages
+ if ($new_state) {
+ my @remove;
+ slow_func(sub { @remove = closure_removal(@nodes) });
+ @nodes_with_deps = grep { !$pkgs->{$_}{selected} && !/^basesystem/ } @remove;
+ $warn_about_additional_packages_to_remove->(
+ $loc->N("Because of their dependencies, the following package(s) also need to be removed:"));
+ my @impossible_to_remove;
+ foreach (grep { exists $pkgs->{$_}{base} } @remove) {
+ ${$pkgs->{$_}{base}} == 1 ? push @impossible_to_remove, $_ : ${$pkgs->{$_}{base}}--;
+ }
+ @impossible_to_remove and interactive_msg($loc->N("Some packages cannot be removed"),
+ $loc->N("Removing these packages would break your system, sorry:\n\n") .
+ format_list(@impossible_to_remove));
+ @nodes_with_deps = difference2(\@nodes_with_deps, \@impossible_to_remove);
+ } else {
+ @nodes_with_deps = grep { intersection(\@nodes, [ closure_removal($_) ]) }
+ grep { $pkgs->{$_}{selected} && !member($_, @nodes) } keys %$pkgs;
+ push @nodes_with_deps, @nodes;
+ $warn_about_additional_packages_to_remove->(
+ $loc->N("Because of their dependencies, the following package(s) must be unselected now:\n\n"));
+ $pkgs->{$_}{base} && ${$pkgs->{$_}{base}}++ foreach @nodes_with_deps;
+ }
+ } else {
+ if ($new_state) {
+ if (@nodes > 1) {
+ #- unselect i18n packages of which locales is not already present (happens when user clicks on KDE group)
+ my @bad_i18n_pkgs;
+ foreach my $sel (@nodes) {
+ foreach ($pkgs->{$sel}{pkg}->requires_nosense) {
+ /locales-([^-]+)/ or next;
+ $sel =~ /-$1[-_]/ && !is_locale_available($_) and push @bad_i18n_pkgs, $sel;
+ }
+ }
+ @nodes = difference2(\@nodes, \@bad_i18n_pkgs);
+ }
+ my @requested;
+ @requested = $urpm->resolve_requested(
+ open_rpm_db(), $urpm->{state},
+ { map { $pkgs->{$_}{pkg}->id => 1 } @nodes },
+ callback_choices => \&callback_choices,
+ );
+ @nodes_with_deps = map { urpm_name($_) } @requested;
+ statusbar_msg_remove($bar_id);
+ if (!deps_msg($loc->N("Additional packages needed"),
+ MDK::Common::String::formatAlaTeX($loc->N("To satisfy dependencies, the following package(s) also need to be installed:\n\n")) . "\n\n",
+ \@nodes, \@nodes_with_deps)) {
+ @nodes_with_deps = ();
+ $urpm->disable_selected(open_rpm_db(), $urpm->{state}, @requested);
+ goto packages_selection_ok;
+ }
+
+ if (my $conflicting_msg = urpm::select::conflicting_packages_msg($urpm, $urpm->{state})) {
+ if (!interactive_msg($loc->N("Conflicting Packages"), $conflicting_msg, yesno => 1, scroll => 1)) {
+ @nodes_with_deps = ();
+ $urpm->disable_selected(open_rpm_db(), $urpm->{state}, @requested);
+ goto packages_selection_ok;
+ }
+ }
+
+ if (my @cant = sort(difference2(\@nodes, \@nodes_with_deps))) {
+ my @ask_unselect = urpm::select::unselected_packages($urpm->{state});
+ my @reasons = map {
+ my $cant = $_;
+ my $unsel = MDK::Common::Func::find { $_ eq $cant } @ask_unselect;
+ $unsel
+ ? join("\n", urpm::select::translate_why_unselected($urpm, $urpm->{state}, $unsel))
+ : ($pkgs->{$_}{pkg}->flag_skip ? $loc->N("%s (belongs to the skip list)", $cant) : $cant);
+ } @cant;
+ my $count = @reasons;
+ interactive_msg(
+ ($count == 1 ? $loc->N("One package cannot be installed") : $loc->N("Some packages cannot be installed")),
+ ($count == 1 ?
+ $loc->N("Sorry, the following package cannot be selected:\n\n%s", format_list(@reasons))
+ : $loc->N("Sorry, the following packages cannot be selected:\n\n%s", format_list(@reasons))),
+ scroll => 1,
+ );
+ foreach (@cant) {
+ next unless $pkgs->{$_}{pkg};
+ $pkgs->{$_}{pkg}->set_flag_requested(0);
+ $pkgs->{$_}{pkg}->set_flag_required(0);
+ }
+ }
+ packages_selection_ok:
+ } else {
+ my @unrequested;
+ @unrequested = $urpm->disable_selected(open_rpm_db(), $urpm->{state},
+ map { $pkgs->{$_}{pkg} } @nodes);
+ @nodes_with_deps = map { urpm_name($_) } @unrequested;
+ statusbar_msg_remove($bar_id);
+ if (!deps_msg($loc->N("Some packages need to be removed"),
+ $loc->N("Because of their dependencies, the following package(s) must be unselected now:\n\n"),
+ \@nodes, \@nodes_with_deps)) {
+ @nodes_with_deps = ();
+ $urpm->resolve_requested(open_rpm_db(), $urpm->{state}, { map { $_->id => 1 } @unrequested });
+ goto packages_unselection_ok;
+ }
+ packages_unselection_ok:
+ }
+ }
+
+ foreach (@nodes_with_deps) {
+ #- some deps may exist on some packages which aren't listed because
+ #- not upgradable (older than what currently installed)
+ exists $pkgs->{$_} or next;
+ if (!$pkgs->{$_}{pkg}) { #- can't be removed # FIXME; what about next packages in the loop?
+ undef $pkgs->{$_}{selected};
+ Sys::Syslog::syslog('info|local1', "can't be removed: $_");
+ } else {
+ $pkgs->{$_}{selected} = $new_state;
+ }
+ # invoke set_leaf_state($pkgname, node_state, )
+ # node_state = {to_install, to_remove,...}
+
+ $set_state->($_, node_state($_), $detail_list);
+ if (my $pkg = $pkgs->{$_}{pkg}) {
+ # FIXME: shouldn't we threat all of them as POSITIVE (as selected size)
+ $size_selected += $pkg->size * ($pkg->flag_installed && !$pkg->flag_upgrade ? ($new_state ? -1 : 1) : ($new_state ? 1 : -1));
+ }
+ }
+}
+
+sub is_there_selected_packages() {
+ int(grep { $pkgs->{$_}{selected} } keys %$pkgs);
+}
+
+sub real_quit() {
+ if (is_there_selected_packages()) {
+ return interactive_msg($loc->N("Some packages are selected."), $loc->N("Some packages are selected.") . "\n" . $loc->N("Do you really want to quit?"), yesno => 1);
+ }
+
+ return 1;
+}
+
+sub do_action__real {
+ my ($options, $callback_action, $o_info) = @_;
+ require urpm::sys;
+ if (!urpm::sys::check_fs_writable()) {
+ $urpm->{fatal}(1, $loc->N("Error: %s appears to be mounted read-only.", $urpm::sys::mountpoint));
+ return 1;
+ }
+ if (!$ManaTools::Rpmdragora::pkg::need_restart && !is_there_selected_packages()) {
+ interactive_msg($loc->N("You need to select some packages first."), $loc->N("You need to select some packages first."));
+ return 1;
+ }
+ my $size_added = MDK::Common::Math::sum(map { MDK::Common::Func::if_($_->flag_selected && !$_->flag_installed, $_->size) } @{$urpm->{depslist}});
+ if ($MODE eq 'install' && $size_free - $size_added/1024 < 50*1024) {
+ interactive_msg($loc->N("Too many packages are selected"),
+ $loc->N("Warning: it seems that you are attempting to add so many
+packages that your filesystem may run out of free diskspace,
+during or after package installation ; this is particularly
+dangerous and should be considered with care.
+
+Do you really want to install all the selected packages?"), yesno => 1)
+ or return 1;
+ }
+ my $res = $callback_action->($urpm, $pkgs);
+ if (!$res) {
+ $force_rebuild = 1;
+ pkgs_provider($options->{tree_mode}, MDK::Common::Func::if_($ManaTools::Rpmdragora::pkg::probe_only_for_updates, pure_updates => 1), skip_updating_mu => 1);
+ reset_search();
+ $size_selected = 0;
+ (undef, $size_free) = MDK::Common::System::df('/usr');
+ $options->{rebuild_tree}->() if $options->{rebuild_tree};
+ $o_info->setValue("") if $o_info;
+ }
+ $res;
+}
+
+sub do_action {
+ my ($options, $callback_action, $o_info) = @_;
+ my $res = eval { do_action__real($options, $callback_action, $o_info) };
+ my $err = $@;
+ # FIXME: offer to report the problem into bugzilla:
+ if ($err && $err !~ /cancel_perform/) {
+ interactive_msg($loc->N("Fatal error"),
+ $loc->N("A fatal error occurred: %s.", $err));
+ }
+ $res;
+}
+
+sub translate_group {
+ join('/', map { $loc->N($_) } split m|/|, $_[0]);
+}
+
+sub ctreefy {
+ join('|', map { $loc->N($_) } split m|/|, $_[0]);
+}
+
+sub _build_tree {
+ my ($tree, $pkg_by_group_hash, @pkg_name_and_group_list) = @_;
+
+ print "TODO ====> BUILD TREE\n";
+
+ yui::YUI::app()->busyCursor();
+
+ #- we populate all the groups tree at first
+ %{$pkg_by_group_hash} = ();
+ # better loop on packages, create groups tree and push packages in the proper place:
+ my @groups = ();
+ foreach my $pkg (@pkg_name_and_group_list) {
+ my $grp = $pkg->[1];
+ # no state for groups (they're not packages and thus have no state)
+ push @groups, $grp;
+
+ $pkg_by_group_hash->{$grp} ||= [];
+ push @{$pkg_by_group_hash->{$grp}}, $pkg;
+ }
+
+ my $tree_hash = ManaTools::Shared::pathList2hash({
+ paths => \@groups,
+ separator => '|',
+ });
+
+ $tree->startMultipleChanges();
+ my $selected = $tree->selectedItem();
+ my $groupname = group_path_name($selected) if $selected;
+
+ $tree->deleteAllItems() if $tree->hasItems();
+
+ # TODO fixing geti icon api to get a better hash from the module
+ my %icons = ();
+ foreach my $group (@groups) {
+ next if defined($icons{$group});
+ my @items = split('\|', $group);
+ if (scalar(@items) > 1) {
+ $icons{$items[0]} = get_icon_path($items[0], 0);
+ $icons{$group} = get_icon_path($items[1], $items[0])
+ }
+ else {
+ $icons{$group} = get_icon_path($group, 0);
+ }
+ }
+
+ my $itemColl = new yui::YItemCollection;
+ $shared_gui->hashTreeToYItemCollection({
+ collection => $itemColl,
+ hash_tree => $tree_hash,
+ icons => \%icons,
+ default_item_separator => '|',
+ default_item => $groupname,
+ });
+
+ $tree->addItems($itemColl);
+ $tree->doneMultipleChanges();
+ $tree->rebuildTree();
+ yui::YUI::app()->normalCursor();
+}
+
+
+sub build_tree {
+ my ($tree, $pkg_by_group_hash, $options, $force_rebuild, $flat, $mode) = @_;
+ state $old_mode;
+ $mode = $options->{rmodes}{$mode} || $mode;
+ $old_mode = '' if(!defined($old_mode));
+ return if $old_mode eq $mode && !$force_rebuild;
+ $old_mode = $mode;
+ undef $force_rebuild;
+ my @elems;
+ my $wait; $wait = statusbar_msg($loc->N("Please wait, listing packages...")) if $MODE ne 'update';
+
+ my @keys = @filtered_pkgs;
+ if (member($mode, qw(all_updates security bugfix normal))) {
+ @keys = grep {
+ my ($name) = split_fullname($_);
+ member($descriptions->{$name}{importance}, @$mandrakeupdate_wanted_categories)
+ || ! $descriptions->{$name}{importance};
+ } @keys;
+ if (@keys == 0) {
+ _build_tree($tree, $pkg_by_group_hash, ['', $loc->N("(none)")]);
+# add_node('', $loc->N("(none)"), { nochild => 1 });
+ state $explanation_only_once;
+ $explanation_only_once or interactive_msg($loc->N("No update"),
+ $loc->N("The list of updates is empty. This means that either there is
+no available update for the packages installed on your computer,
+or you already installed all of them."));
+ $explanation_only_once = 1;
+ }
+ }
+ if (scalar @keys) {
+ # FIXME: better do this on first group access for faster startup...
+ @elems = map { [ $_, !$flat && ctreefy($pkgs->{$_}{pkg}->group) ] } sort_packages(@keys);
+
+ my %sortmethods = (
+ by_size => sub { sort { $pkgs->{$b->[0]}{pkg}->size <=> $pkgs->{$a->[0]}{pkg}->size } @_ },
+ by_selection => sub { sort { $pkgs->{$b->[0]}{selected} <=> $pkgs->{$a->[0]}{selected}
+ || uc($a->[0]) cmp uc($b->[0]) } @_ },
+ by_leaves => sub {
+ # inlining part of MDK::Common::Data::difference2():
+ my %l; @l{map { $_->[0] } @_} = ();
+ my @pkgs_times = ('rpm', '-q', '--qf', '%{name}-%{version}-%{release}.%{arch} %{installtime}\n',
+ map { chomp_($_) } ManaTools::Shared::RunProgram::get_stdout('urpmi_rpm-find-leaves'));
+ sort { $b->[1] <=> $a->[1] } grep { exists $l{$_->[0]} } map { chomp; [ split ] } run_rpm(@pkgs_times);
+ },
+ flat => sub { no locale; sort { uc($a->[0]) cmp uc($b->[0]) } @_ },
+ by_medium => sub { sort { $a->[2] <=> $b->[2] || uc($a->[0]) cmp uc($b->[0]) } @_ },
+ );
+ if ($flat) {
+ carp "WARNING: TODO \$flat not tested\n";
+ _build_tree($tree, $pkg_by_group_hash, map {[$_->[0], '']} $sortmethods{$::mode->[0] || 'flat'}->(@elems));
+# add_node($_->[0], '') foreach $sortmethods{$::mode->[0] || 'flat'}->(@elems);
+ }
+ else {
+ if ($::mode->[0] eq 'by_source') {
+ _build_tree($tree, $pkg_by_group_hash, $sortmethods{by_medium}->(map {
+ my $m = pkg2medium($pkgs->{$_->[0]}{pkg}, $urpm); [ $_->[0], $m->{name}, $m->{priority} ];
+ } @elems));
+ }
+ elsif ($::mode->[0] eq 'by_presence') {
+ _build_tree($tree, $pkg_by_group_hash, map {
+ my $pkg = $pkgs->{$_->[0]}{pkg};
+ [ $_->[0], $pkg->flag_installed ?
+ (!$pkg->flag_skip && $pkg->flag_upgrade ? $loc->N("Upgradable") : $loc->N("Installed"))
+ : $loc->N("Addable") ];
+ } $sortmethods{flat}->(@elems));
+ }
+ else {
+ _build_tree($tree, $pkg_by_group_hash, @elems);
+ }
+ }
+ }
+ statusbar_msg_remove($wait) if defined $wait;
+}
+
+#=============================================================
+
+=head2 get_info
+
+=head3 INPUT
+
+ $key: package full name
+ $options: HASH reference containing:
+ details => show details
+ changelog => show changelog
+ files => show files
+ new_deps => show new dependencies
+
+=head3 DESCRIPTION
+
+ return a string with all the package info
+
+=cut
+
+#=============================================================
+sub get_info {
+ my ($key, $options) = @_;
+ #- the package information hasn't been loaded. Instead of rescanning the media, just give up.
+ exists $pkgs->{$key} or return [ [ $loc->N("Description not available for this package\n") ] ];
+ #- get the description if needed:
+ exists $pkgs->{$key}{description} or slow_func(sub { extract_header($pkgs->{$key}, $urpm, 'info', find_installed_version($pkgs->{$key}{pkg})) });
+ _format_pkg_simplifiedinfo($pkgs, $key, $urpm, $descriptions, $options);
+}
+
+sub sort_callback {
+ my ($store, $treeiter1, $treeiter2) = @_;
+ URPM::rpmvercmp(map { $store->get_value($_, $pkg_columns{version}) } $treeiter1, $treeiter2);
+}
+
+sub run_help_callback {
+ my (undef, $url) = @_;
+ my ($user) = grep { $_->[2] eq $ENV{USERHELPER_UID} } list_passwd();
+ local $ENV{HOME} = $user->[7] if $user && $ENV{USERHELPER_UID};
+ ManaTools::Shared::RunProgram::raw({ detach => 1, as_user => 1 }, 'www-browser', $url);
+}
+
+#=============================================================
+
+=head2 run_browser
+
+=head3 INPUT
+
+ $url: url to be passed to the configured browser
+
+=head3 DESCRIPTION
+
+ This function calls the browser with the given URL
+
+=cut
+
+#=============================================================
+sub run_browser {
+ my $url = shift;
+
+ my ($user) = grep { $_->[2] eq $ENV{USERHELPER_UID} } MDK::Common::System::list_passwd();
+ local $ENV{HOME} = $user->[7] if $user && $ENV{USERHELPER_UID};
+ ManaTools::Shared::RunProgram::raw({ detach => 1, as_user => 1 }, 'www-browser', $url);
+}
+
+#=============================================================
+
+=head2 group_path_name
+
+=head3 INPUT
+
+ $treeItem: YTreeItem object
+
+=head3 OUTPUT
+
+ $fullname: completed path group name
+
+=head3 DESCRIPTION
+
+ This function returns the path name treeItem group name (e.g. foo|bar)
+
+=cut
+
+#=============================================================
+sub group_path_name {
+ my $treeItem = shift;
+
+ my $fullname = $treeItem->label();
+ my $it = $treeItem;
+ while ($it = $it->parent()) {
+ $fullname = join("|", $it->label(), $fullname);
+ }
+
+ return $fullname;
+}
+
+sub groups_tree {
+ carp "DEPRECATED groups_tree: do not use it any more!";
+
+ return %groups_tree;
+}
+
+sub group_has_parent {
+ my ($group) = shift;
+ carp "DEPRECATED group_has_parent: do not use it any more!";
+ return 0 if(!defined($group));
+ return defined($groups_tree{$group}{parent});
+}
+
+sub group_parent {
+ my ($group) = shift;
+
+ carp "DEPRECATED group_parent: do not use it any more!";
+ # if group is a parent itself return it
+ # who use group_parent have to take care of the comparison
+ # between a group and its parent
+ # e.g. group System has groups_tree{'System'}{parent}->label() = 'System'
+ return $groups_tree{$group}{parent} if(group_has_parent($group));
+ for my $sup (keys %groups_tree){
+ for my $item(keys %{$groups_tree{$sup}{children}}){
+ if(defined($group) && ($item eq $group)){
+ return $groups_tree{$sup}{parent};
+ }
+ }
+ }
+ return undef;
+}
+
+1;