#!/usr/bin/perl
#*****************************************************************************
#
#  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
#
#  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: rpmdrake 225322 2007-08-02 20:02:01Z tv $

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::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;
my $treeview_dialog_run = 0;
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, $results_none) = (N("Search results"), N("Search results (none)"));
    $options->{delete_category}->($_) foreach $results_ok, $results_none;
    $options->{state}{flat} and $options->{delete_all}->();
    $tree->collapse_all;
    my @search_results;
    if ($current_search_type ne 'normal') {
	if ($MODE eq 'remove') {
	    if ($current_search_type eq 'descriptions') {
		@search_results = grep { ($pkgs->{$_}{summary} . $pkgs->{$_}{description}) =~ $entry_rx } @filtered_pkgs;
	    } else {
		slow_func_statusbar(
		    N("Please wait, searching..."),
		    $w->{real_window},
		    sub {
			open_rpm_db()->traverse(sub {
			    push @search_results, map { if_($_ =~ $entry_rx, urpm_name($_[0])) } $_[0]->files;
			});
			@search_results = grep { exists $pkgs->{$_} } uniq(@search_results);
		    },
		);
	    }
	} else {
	    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
	    );
	    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;
	    open my $sf, 'parsehdlist --fileswinfo --description --summary ' . join(' ', map { "'$_'" } @hdlists) . ' |';
	    my ($pkg, $progresscount);
	    local $_;
	    while (<$sf>) {
		$searchstop and last;
		if (/^NAME<([^>]+)> VERSION<([^>]+)> RELEASE<([^>]+)> ARCH<([^>]+)>/) {
		    $pkg = "$1-$2-$3.$4";
		    $progresscount++; $progresscount <= $total_size and $searchprogress->set_fraction($progresscount/$total_size);
		    $searchw->flush;
		    next;
		}
		$pkg or next;
		my (undef, $key, $value) = split ':', $_;
		if ($current_search_type eq 'descriptions') {
		    $key =~ /^summary|description$/ or next;
		} else {
		    $key eq 'files' or next;
		}
		if ($value =~ $entry_rx) {
		    exists $pkgs->{$pkg} and push @search_results, $pkg;
		    $pkg = '';
		}
	    }
	    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, $_;
            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 {
	$options->{add_nodes}->([ '', $results_none, { nochild => 1 } ]);
    }
}

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");
    $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');

    $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');
    $detail_list->append_column(my $col = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => $pkg_columns{text}));
    $col->set_sizing('fixed');
    $detail_list_model->set_sort_column_id(0, 'ascending');
    $detail_list->set_headers_visible(0);
    $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"),
                 ),
                 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)
                                                                          } ],
                                                                  $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 files);
    my $current_search_type = $search_types[0];
    my $search_types_optionmenu = Gtk2::ComboBox->new;
    {
	$search_types_optionmenu->set_model(Gtk2::ListStore->new('Glib::String'));
	my $search_types_renderer = Gtk2::CellRendererText->new;
	$search_types_optionmenu->pack_start($search_types_renderer, 0);
	$search_types_optionmenu->set_attributes($search_types_renderer, text => 0);
	my $iter = $search_types_optionmenu->get_model->iter_nth_child(undef, 0);
	$iter = $search_types_optionmenu->get_model->insert(0);
	$search_types_optionmenu->get_model->set($iter, 0, N("in names"));
	$iter = $search_types_optionmenu->get_model->insert(1);
	$search_types_optionmenu->get_model->set($iter, 0, N("in descriptions"));
	$iter = $search_types_optionmenu->get_model->insert(2);
	$search_types_optionmenu->get_model->set($iter, 0, N("in file names"));
	$search_types_optionmenu->set_active(0);
	$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 $status = gtknew('Label');
    my $checkbox_show_autoselect;
    my ($menu, $factory) = create_factory_menu(
	$w->{real_window},
	[ N("/_File"), undef, undef, undef, '<Branch>' ],
	if_(
	    ! $>,
	    [ N("/_File") . N("/_Update media"), undef, sub {
		update_sources_interactive($urpm, transient => $w->{real_window})
		    and do {
			$force_rebuild = 1;
			pkgs_provider({ skip_updating_mu => 1 }, $options->{tree_mode});
			reset_search();
			$size_selected = 0;
			$options->{rebuild_tree}->();
		    };
	    }, undef, '<Item>' ]
	),
	[ 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, '<Item>' ],
	[ N("/_File") . N("/Reload the _packages list"), undef, sub {
	    slow_func($::main_window->window, sub {
                       $force_rebuild = 1;
                       $rmodes{pkgs_provider({ skip_updating_mu => 1 }, $options->{tree_mode})};
                   });
	    reset_search();
	    $size_selected = 0;
	    $options->{rebuild_tree}->();
	}, undef, '<Item>' ],
	[ N("/_File") . N("/_Quit"), N("<control>Q"), sub { Gtk2->main_quit }, undef, '<Item>', ],
	#[ N("/_View"), undef, undef, undef, '<Branch>' ],
	if_(!$>,
	    [ N("/_Options"), undef, undef, undef, '<Branch>' ],
	    [ N("/_Options") . N("/_Media Manager"), undef, sub {
               require Rpmdrake::edit_urpm_sources;
               Rpmdrake::edit_urpm_sources::run() and $rmodes{pkgs_provider({ skip_updating_mu => 1 }, $options->{tree_mode})};
           }, undef, '<Item>' ],
	    [ N("/_Options") . N("/_Show automatically selected packages"), undef, sub {
		$dont_show_selections = !$checkbox_show_autoselect->get_active;
	    }, undef, '<CheckItem>' ],
	),
	[ N("/_Help"), undef, undef, undef, '<Branch>' ],
     [ N("/_Help") . N("/_Report Bug"), undef, sub { run_program::raw({ detach => 1 }, 'drakbug', '--report', 'rpmdrake') }, undef, '<Item>' ],
	[ N("/_Help") . N("/_Help"), undef, sub { rpmdrake::open_help($MODE) }, undef, '<Item>' ],
     [ 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 <vignaud@mandriva.com>',
                        artists => 'Hélène Durosini <ln@mandriva.com>',
                        translator_credits =>
                          #-PO: put here name(s) and email(s) of translator(s) (eg: "John Smith <jsmith@nowhere.com>")
                          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, '<Item>'
     ]

    );
    $checkbox_show_autoselect = $factory->get_widget("<main>" . 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("Help"))),
			    clicked => sub { rpmdrake::open_help($MODE) },
			),
               1, gtknew('Label'),
			0, gtksignal_connect(
			    Gtk2::Button->new(but_(N("Select all"))),
			    clicked => sub {
                       toggle_all($options, 1);
                   },
			),
			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($options{search}[0]) if $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 $options{search};

    $treeview_dialog_run = 1;
    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);
