// resolver.cc // // Copyright 1999-2010 Daniel Burrows // Copyright 2008 Obey Arthur Liu // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // 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; see the file COPYING. If not, write to // the Free Software Foundation, Inc., 59 Temple Place - Suite 330, // Boston, MA 02111-1307, USA. #include "resolver.h" #include "aptitude.h" #include "gui.h" #include "treeview_cell_tooltips.h" #undef OK #include #include #include #include #include #include // For archives_text. #include // For action_type. #include #include #include #include using aptitude::Loggers; namespace gui { namespace { typedef generic_choice choice; typedef generic_choice_set choice_set; void do_start_solution_calculation(resolver_manager *resolver); class gui_resolver_continuation : public resolver_manager::background_continuation { typedef generic_solution aptitude_solution; resolver_manager *manager; // This is static because the continuation will be deleted // before it gets invoked. static void do_error(const std::string &msg, resolver_manager& manager) { _error->Error(_("Error in dependency resolver: %s"), msg.c_str()); std::string notification = ssprintf(_("Fatal error in dependency resolver. You can continue " "searching, but some solutions might be impossible to generate.\n\n%s"), msg.c_str()); pMainWindow->get_notifyview()->add_notification(Gtk::manage(new Notification(notification.c_str()))); manager.state_changed(); } public: gui_resolver_continuation(resolver_manager *_manager) : manager(_manager) { } void success(const aptitude_solution &sol) { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Resolver tab: got solution: " << sol); // Tell the main loop to pretend the state changed. sigc::slot0 state_changed = manager->state_changed.make_slot(); post_event(make_safe_slot(state_changed)); } void no_more_solutions() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Resolver tab: ran out of solutions."); sigc::slot0 state_changed = manager->state_changed.make_slot(); post_event(make_safe_slot(state_changed)); } void no_more_time() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Resolver tab: ran out of time, restarting the calculation."); do_start_solution_calculation(manager); } void interrupted() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Resolver tab: interrupted."); sigc::slot0 state_changed = manager->state_changed.make_slot(); post_event(make_safe_slot(state_changed)); } void aborted(const std::string &errmsg) { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Resolver tab: aborted by exception: " << errmsg); sigc::slot0 do_error_slot = sigc::bind(sigc::ptr_fun(&gui_resolver_continuation::do_error), errmsg, sigc::ref(*manager)); post_event(make_safe_slot(do_error_slot)); } }; // Used by the upgrade resolver to automatically push itself to // the next solution. void do_start_solution_calculation(resolver_manager *resolver) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Restarting the resolver thread if necessary."); boost::shared_ptr k = boost::make_shared(resolver); resolver->maybe_start_solution_calculation(k, post_thunk); } // Start computing the first solution if it's not computed yet. void do_start_first_solution_calculation(resolver_manager *resolver) { if(resolver->generated_solution_count() == 0) { // Note: there is no race condition here. If the background // thread computes a solution before we enqueue the job, // then maybe_start_solution_calculation just won't do // anything (because the selected solution is 0, so there's // nothing to do -- the first solution is already // generated). LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Making sure the first solution is computed."); boost::shared_ptr k = boost::make_shared(resolver); resolver->maybe_start_solution_calculation(k, post_thunk); } } // The global resolver will always be set to start calculating the // first solution as soon as it's created; after that we only // calculate solutions when the user presses the "find new // solution" button. void do_connect_resolver_callback() { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Connecting the callback to compute the first solution when the resolver state changes."); resman->state_changed.connect(sigc::bind(sigc::ptr_fun(&do_start_first_solution_calculation), resman)); // We may have missed a signal before making the connection: do_start_first_solution_calculation(resman); } std::string render_choice_description_markup(const choice &c) { switch(c.get_type()) { case choice::install_version: { aptitude_resolver_version ver(c.get_ver()); pkgCache::VerIterator descver; if(!ver.get_ver().end()) descver = ver.get_ver(); else { aptitude_resolver_version curr_ver(ver.get_package().current_version()); if(!curr_ver.get_ver().end()) descver = curr_ver.get_ver(); else if(!ver.get_pkg().CurrentVer().end()) descver = ver.get_pkg().CurrentVer(); else if(!ver.get_pkg().VersionList().end()) descver = ver.get_pkg().VersionList(); else descver = pkgCache::VerIterator(); } // Note that virtual packages shouldn't happen! This is // just a safeguard. using cwidget::util::transcode; std::string description_markup = descver.end() ? ssprintf("%s", Glib::Markup::escape_text(_("Virtual package")).c_str()).c_str() : Glib::Markup::escape_text(transcode(get_short_description(descver, apt_package_records))).c_str(); return description_markup; } default: return ""; } } /** \brief Render the given choice in "short" form, not including * a description of the choice. * * The output is suitable for use in a list with a heading such * as "Install the following packages". */ std::string render_choice_brief_markup(const choice &c) { switch(c.get_type()) { case choice::install_version: { aptitude_resolver_version ver(c.get_ver()); switch(analyze_action(ver)) { case action_remove: return Glib::Markup::escape_text(ver.get_pkg().Name()); case action_keep: if(ver.get_ver().end()) return ssprintf("%s [%s]", Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(_("Not Installed")).c_str()); else return ssprintf("%s [%s (%s)]", Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_pkg().CurrentVer().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_pkg().CurrentVer())).c_str()); case action_install: return ssprintf("%s [%s (%s)]", Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_ver())).c_str()); case action_downgrade: case action_upgrade: return ssprintf("%s [%s (%s) -> %s (%s)]", Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_pkg().CurrentVer().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_pkg().CurrentVer())).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_ver())).c_str()); default: return "UNKNOWN ACTION TYPE"; } } break; case choice::break_soft_dep: { const std::string dep_render = cwidget::util::transcode(dep_text(c.get_dep().get_dep()), "UTF-8"); return ssprintf("%s", Glib::Markup::escape_text(dep_render).c_str()); } default: return "UNKNOWN CHOICE TYPE"; } } } void init_resolver() { cache_reloaded.connect(sigc::ptr_fun(&do_connect_resolver_callback)); if(apt_cache_file) do_connect_resolver_callback(); } //std::string glade_main_file; //AptitudeWindow * pMainWindow; ResolverColumns::ResolverColumns() { add(PkgIterator); add(VerIterator); add(ActionMarkup); add(BgSet); add(BgColor); add(PreferenceIcon); add(PreferenceIconTooltip); add(Choice); } AlreadyGeneratedSolutionColumns::AlreadyGeneratedSolutionColumns() { add(Index); add(IndexNum); add(Markup); add(TooltipMarkup); add(Solution); } void ResolverView::set_column_properties(Gtk::TreeViewColumn *treeview_column, int size) { Glib::ListHandle renderers = treeview_column->get_cell_renderers(); for(Glib::ListHandle::const_iterator it = renderers.begin(); it != renderers.end(); ++it) { treeview_column->add_attribute((*it)->property_cell_background_set(), resolver_columns.BgSet); treeview_column->add_attribute((*it)->property_cell_background(), resolver_columns.BgColor); } if (size < 0) treeview_column->set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE); else { treeview_column->set_sizing(Gtk::TREE_VIEW_COLUMN_FIXED); treeview_column->set_fixed_width(size); treeview_column->set_resizable(true); } treeview_column->set_reorderable(true); } template int ResolverView::append_column(const Glib::ustring &title, Gtk::TreeViewColumn *out_treeview_column, const Gtk::TreeModelColumn &model_column, int size) { out_treeview_column = manage(new Gtk::TreeViewColumn(title, model_column)); set_column_properties(out_treeview_column, size); return view->append_column(*out_treeview_column); } ResolverView::preference_info ResolverView::get_preference_info(const generic_choice &c, resolver_manager *manager) const { typedef generic_choice choice; using cwidget::util::ssprintf; if(manager == NULL) return preference_info(); else switch(c.get_type()) { case choice::install_version: { pkgCache::VerIterator ver = c.get_ver().get_ver(); pkgCache::PkgIterator pkg = c.get_ver().get_pkg(); if(manager->is_rejected(c.get_ver())) { Glib::ustring icon_tooltip; // Generate a friendly message about what's happening. switch(analyze_action(c.get_ver())) { case action_remove: icon_tooltip = ssprintf(_("Removing %s is rejected."), pkg.Name()); break; case action_keep: if(ver.end()) icon_tooltip = ssprintf(_("Canceling the installation of %s is rejected."), pkg.Name()); else { aptitude_resolver_version previous_resolver_version = c.get_ver().get_package().current_version(); pkgCache::VerIterator prev_ver = previous_resolver_version.get_ver(); if(prev_ver.end()) icon_tooltip = ssprintf(_("Canceling the removal of %s is rejected."), pkg.Name()); else icon_tooltip = ssprintf(_("Keeping %s at version %s is rejected."), pkg.Name(), ver.VerStr()); } break; case action_install: icon_tooltip = ssprintf(_("Installing %s version %s is rejected."), pkg.Name(), ver.VerStr()); break; case action_downgrade: icon_tooltip = ssprintf(_("Downgrading %s to version %s is rejected."), pkg.Name(), ver.VerStr()); break; case action_upgrade: icon_tooltip = ssprintf(_("Upgrading %s to version %s is rejected."), pkg.Name(), ver.VerStr()); break; } return preference_info(Gtk::Stock::CANCEL.id, icon_tooltip, lightred_background_color, true); } else if(manager->is_mandatory(c.get_ver())) { Glib::ustring icon_tooltip; switch(analyze_action(c.get_ver())) { case action_remove: icon_tooltip = ssprintf(_("Removing %s is preferred over all un-accepted alternatives."), pkg.Name()); break; case action_keep: if(ver.end()) icon_tooltip = ssprintf(_("Canceling the installation of %s is preferred over all un-accepted alternatives."), pkg.Name()); else { aptitude_resolver_version previous_resolver_version = c.get_ver().get_package().current_version(); pkgCache::VerIterator prev_ver = previous_resolver_version.get_ver(); if(prev_ver.end()) icon_tooltip = ssprintf(_("Canceling the removal of %s is preferred over all un-accepted alternatives."), pkg.Name()); else icon_tooltip = ssprintf(_("Keeping %s at version %s is preferred over all un-accepted alternatives."), pkg.Name(), ver.VerStr()); } break; case action_install: icon_tooltip = ssprintf(_("Installing %s version %s is preferred over all un-accepted alternatives."), pkg.Name(), ver.VerStr()); break; case action_downgrade: icon_tooltip = ssprintf(_("Downgrading %s to version %s is preferred over all un-accepted alternatives."), pkg.Name(), ver.VerStr()); break; case action_upgrade: icon_tooltip = ssprintf(_("Upgrading %s to version %s is preferred over all un-accepted alternatives."), pkg.Name(), ver.VerStr()); break; } return preference_info(Gtk::Stock::APPLY.id, icon_tooltip, lightgreen_background_color, true); } else return preference_info(); } case choice::break_soft_dep: if(manager->is_hardened(c.get_dep())) return preference_info(Gtk::Stock::CANCEL.id, ssprintf(_("Leaving %ls unresolved is rejected."), dep_text(c.get_dep().get_dep()).c_str()), lightred_background_color, true); else if(manager->is_approved_broken(c.get_dep())) return preference_info(Gtk::Stock::APPLY.id, ssprintf(_("Leaving %ls unresolved is preferred over all un-accepted alternatives."), dep_text(c.get_dep().get_dep()).c_str()), lightgreen_background_color, true); else return preference_info(); default: LOG_ERROR(Loggers::getAptitudeGtkResolver(), "Bad choice type " << c.get_type()); return preference_info(Gtk::Stock::DIALOG_ERROR.id, "", "", false); } } void ResolverView::set_preference_info(Gtk::TreeModel::Row &row, const ResolverView::preference_info &pref_inf) const { row[resolver_columns.PreferenceIcon] = pref_inf.icon; row[resolver_columns.PreferenceIconTooltip] = pref_inf.icon_tooltip; // Even if the color will have no effect because it isn't set, we // need to set a legal color name here; otherwise GTK+ will // complain that it doesn't know about the color "". Black is // picked so it's really obvious when this has happened. if(pref_inf.background.empty()) row[resolver_columns.BgColor] = "black"; else row[resolver_columns.BgColor] = pref_inf.background; row[resolver_columns.BgSet] = pref_inf.background_set; // Sanity-check. if(pref_inf.background.empty() && pref_inf.background_set) LOG_ERROR(Loggers::getAptitudeGtkResolver(), "The row \"" << row[resolver_columns.ActionMarkup] << "\" has no background but its background is set."); } void ResolverView::set_preference_info(Gtk::TreeModel::Row &row, const generic_choice &c, resolver_manager *manager) const { preference_info pref_inf(get_preference_info(c, manager)); set_preference_info(row, pref_inf); } bool ResolverView::update_preference_info(const Gtk::TreeModel::iterator &iter, resolver_manager *manager) { Gtk::TreeModel::Row row(*iter); typedef generic_choice choice; maybe maybe_c(row[resolver_columns.Choice]); choice c; if(maybe_c.extract(c)) set_preference_info(row, c, manager); else set_preference_info(row, preference_info()); return false; } void ResolverView::update_version(const aptitude_resolver_version &version, resolver_manager *manager) { std::map::iterator found = version_backpointers.find(version); if(found != version_backpointers.end()) { typedef generic_choice choice; Gtk::TreeModel::Row row(*found->second); set_preference_info(row, choice::make_install_version(version, -1), manager); } } void ResolverView::update_dep(const aptitude_resolver_dep &dep, resolver_manager *manager) { std::map::iterator found = dep_backpointers.find(dep); if(found != dep_backpointers.end()) { typedef generic_choice choice; Gtk::TreeModel::Row row(*found->second); set_preference_info(row, choice::make_break_soft_dep(dep, -1), manager); } } ResolverView::ResolverView(Gtk::TreeView *_view) : view(_view) { { Gtk::CellRendererPixbuf *preference_icon_renderer = manage(new Gtk::CellRendererPixbuf); PreferenceIcon = manage(new Gtk::TreeViewColumn("", *preference_icon_renderer)); PreferenceIcon->add_attribute(preference_icon_renderer->property_stock_id(), resolver_columns.PreferenceIcon); PreferenceIcon->set_sort_column(resolver_columns.PreferenceIcon); set_column_properties(PreferenceIcon, -1); view->append_column(*PreferenceIcon); } set_text_tooltip(view, PreferenceIcon, resolver_columns.PreferenceIconTooltip); { Gtk::CellRendererText *action_renderer = manage(new Gtk::CellRendererText); ActionMarkup = manage(new Gtk::TreeViewColumn("Action", *action_renderer)); ActionMarkup->add_attribute(action_renderer->property_markup(), resolver_columns.ActionMarkup); set_column_properties(ActionMarkup, -1); ActionMarkup->set_sort_column(resolver_columns.Choice); view->append_column(*ActionMarkup); } set_markup_tooltip(view, ActionMarkup, resolver_columns.ActionMarkup); view->get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); view->set_search_column(resolver_columns.ActionMarkup); } cwidget::util::ref_ptr ResolverView::create(Gtk::TreeView *view) { return new ResolverView(view); } bool ResolverView::add_backpointer(const Gtk::TreeModel::Path &path, const Gtk::TreeModel::iterator &iter) { typedef generic_choice choice; const maybe &maybe_c((*iter)[resolver_columns.Choice]); choice c; if(maybe_c.extract(c)) { switch(c.get_type()) { case choice::install_version: version_backpointers[c.get_ver()] = iter; break; case choice::break_soft_dep: dep_backpointers[c.get_dep()] = iter; break; } } return false; } bool ResolverView::sanity_check_iter(const Gtk::TreeModel::iterator &iter) const { Glib::ustring bgColor = (*iter)[resolver_columns.BgColor]; if((*iter)[resolver_columns.BgSet] && bgColor.empty()) { LOG_ERROR(Loggers::getAptitudeGtkResolver(), "Sanity-check failed: the resolver row \"" << (*iter)[resolver_columns.ActionMarkup] << "\" has an empty background color."); } return false; } void ResolverView::set_model(const Glib::RefPtr &model, resolver_manager *manager) { model->foreach_iter(sigc::bind(sigc::mem_fun(*this, &ResolverView::update_preference_info), manager)); model->foreach_iter(sigc::mem_fun(*this, &ResolverView::sanity_check_iter)); view->set_model(model); version_backpointers.clear(); dep_backpointers.clear(); model->foreach(sigc::mem_fun(*this, &ResolverView::add_backpointer)); } ResolverTab::ResolverTab(const Glib::ustring &label) : Tab(Resolver, label, Gnome::Glade::Xml::create(glade_main_file, "resolver_main"), "resolver_main"), resolver(NULL), using_internal_resolver(false), toggle_signals_suppressed(false) { get_xml()->get_widget("main_resolver_status", pResolverStatus); get_xml()->get_widget("main_resolver_find_next", find_next_solution_button); find_next_solution_button->signal_clicked().connect(sigc::mem_fun(*this, &ResolverTab::do_find_next_solution)); get_xml()->get_widget("main_resolver_apply", pResolverApply); get_xml()->get_widget("resolver_fixing_upgrade_message", resolver_fixing_upgrade_message); get_xml()->get_widget("resolver_fixing_upgrade_progress_bar", resolver_fixing_upgrade_progress_bar); get_xml()->get_widget("resolver_fixing_upgrade_label", resolver_fixing_upgrade_label); pResolverApply->signal_clicked().connect(sigc::mem_fun(*this, &ResolverTab::do_apply_solution)); get_xml()->get_widget("resolver_group_by_action", pButtonGroupByAction); get_xml()->get_widget("resolver_show_explanation", pButtonShowExplanation); get_xml()->get_widget("resolver_acceptreject_label", acceptreject_label); get_xml()->get_widget("resolver_reject_button", reject_button); get_xml()->get_widget("resolver_no_preference_button", no_preference_button); get_xml()->get_widget("resolver_accept_button", accept_button); reject_button->set_image(*new Gtk::Image(Gtk::Stock::CANCEL, Gtk::ICON_SIZE_BUTTON)); accept_button->set_image(*new Gtk::Image(Gtk::Stock::APPLY, Gtk::ICON_SIZE_BUTTON)); // TODO: ideally, instead of rereading the state, we should // trigger an update using the last seen state. Or maybe build two // models and swap between them. pButtonGroupByAction->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &ResolverTab::update_solution_pane), true)); pButtonShowExplanation->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &ResolverTab::update_solution_pane), true)); reject_button->signal_toggled().connect(sigc::mem_fun(*this, &ResolverTab::reject_button_toggled)); no_preference_button->signal_toggled().connect(sigc::mem_fun(*this, &ResolverTab::no_preference_button_toggled)); accept_button->signal_toggled().connect(sigc::mem_fun(*this, &ResolverTab::accept_button_toggled)); Gtk::TreeView *solution_treeview; get_xml()->get_widget("main_resolver_treeview", solution_treeview); solution_view = ResolverView::create(solution_treeview); solution_treeview->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &ResolverTab::update_reject_accept_buttons)); // Set up the list of already-generated solutions. get_xml()->get_widget("already_generated_treeview", already_generated_view); already_generated_model = Gtk::ListStore::create(already_generated_columns); already_generated_view->set_model(already_generated_model); already_generated_view->set_tooltip_column(already_generated_columns.TooltipMarkup.index()); already_generated_view->get_selection()->set_mode(Gtk::SELECTION_BROWSE); already_generated_view->get_selection()->signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &ResolverTab::update_solution_pane), false)); { Gtk::TreeViewColumn *index_column = manage(new Gtk::TreeViewColumn("Index", already_generated_columns.Index)); index_column->set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE); index_column->set_sort_column(already_generated_columns.IndexNum); Gtk::CellRendererText *markup_renderer = manage(new Gtk::CellRendererText); Gtk::TreeViewColumn *markup_column = manage(new Gtk::TreeViewColumn("Solution", *markup_renderer)); markup_column->add_attribute(markup_renderer->property_markup(), already_generated_columns.Markup); markup_column->set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE); markup_column->set_sort_column(already_generated_columns.Markup); already_generated_view->append_column(*index_column); already_generated_view->append_column(*markup_column); } // Initialize the interface with the current state: update(true); get_widget()->show(); cache_closed.connect(sigc::mem_fun(*this, &ResolverTab::handle_cache_closed)); cache_reloaded.connect(sigc::mem_fun(*this, &ResolverTab::setup_resolver_connections)); setup_resolver_connections(); } void ResolverTab::handle_cache_closed() { resolver_state_changed_connection.disconnect(); resolver_version_accept_reject_changed_connection.disconnect(); resolver_break_dep_accept_reject_changed_connection.disconnect(); already_generated_model->clear(); // TODO: what about encapsulated resolvers? } void ResolverTab::setup_resolver_connections() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); if(get_resolver() == NULL) { LOG_TRACE(logger, "Not setting up resolver connections: the resolver is NULL."); return; } LOG_TRACE(logger, "Setting up connections on the resolver manager " << get_resolver()); resolver_state_changed_connection.disconnect(); resolver_version_accept_reject_changed_connection.disconnect(); resolver_break_dep_accept_reject_changed_connection.disconnect(); resolver_state_changed_connection = get_resolver()->state_changed.connect(sigc::bind(sigc::mem_fun(*this, &ResolverTab::update), false)); resolver_version_accept_reject_changed_connection = get_resolver()->version_accept_reject_changed.connect(sigc::mem_fun(*this, &ResolverTab::version_accept_reject_changed)); resolver_break_dep_accept_reject_changed_connection = get_resolver()->break_dep_accept_reject_changed.connect(sigc::mem_fun(*this, &ResolverTab::break_dep_accept_reject_changed)); } void ResolverTab::update(bool force_update) { update_reject_accept_buttons(); if(get_resolver() == NULL) return; resolver_manager::state state = get_resolver()->state_snapshot(); update_from_state(state, force_update); } void ResolverTab::version_accept_reject_changed(const aptitude_resolver_version &ver) { solution_view->update_version(ver, get_resolver()); } void ResolverTab::break_dep_accept_reject_changed(const aptitude_resolver_dep &dep) { solution_view->update_dep(dep, get_resolver()); } class ResolverTab::update_reject_accept_counter { // Counts how many things are selected. int count; bool contains_rejected; bool contains_accepted; bool contains_no_preference; const ResolverTab &parent; const ResolverColumns &columns; public: update_reject_accept_counter(const ResolverTab &_parent, const ResolverColumns &_columns) : count(0), contains_rejected(false), contains_accepted(false), contains_no_preference(false), parent(_parent), columns(_columns) { } void process(const Gtk::TreeModel::iterator &iter) { typedef generic_choice choice; const maybe &maybe_c((*iter)[columns.Choice]); choice c; if(maybe_c.extract(c)) { ++count; switch(c.get_type()) { case choice::install_version: if(parent.get_resolver()->is_rejected(c.get_ver())) contains_rejected = true; else if(parent.get_resolver()->is_mandatory(c.get_ver())) contains_accepted = true; else contains_no_preference = true; break; case choice::break_soft_dep: if(parent.get_resolver()->is_hardened(c.get_dep())) contains_rejected = true; else if(parent.get_resolver()->is_approved_broken(c.get_dep())) contains_accepted = true; else contains_no_preference = true; break; } } } int get_count() const { return count; } bool get_contains_rejected() const { return contains_rejected; } bool get_contains_no_preference() const { return contains_no_preference; } bool get_contains_accepted() const { return contains_accepted; } }; void ResolverTab::update_reject_accept_buttons() { suppress_toggle_signals suppressor(*this); if(get_resolver() == NULL || !get_resolver()->resolver_exists()) { accept_button->set_sensitive(false); no_preference_button->set_sensitive(false); reject_button->set_sensitive(false); accept_button->set_active(false); no_preference_button->set_active(false); reject_button->set_active(false); return; } // Find out what's selected. update_reject_accept_counter counter(*this, solution_view->get_columns());; Glib::RefPtr selection = solution_view->get_treeview()->get_selection(); selection->selected_foreach_iter(sigc::mem_fun(counter, &update_reject_accept_counter::process)); const int count = counter.get_count(); const bool contains_rejected = counter.get_contains_rejected(); const bool contains_no_preference = counter.get_contains_no_preference(); const bool contains_accepted = counter.get_contains_accepted(); using cwidget::util::ssprintf; // TODO: this frame title is lame. acceptreject_label->set_markup(ssprintf("%s", Glib::Markup::escape_text(ngettext("How to treat the selected action", "How to treat the selected actions", count)).c_str())); reject_button->set_tooltip_text(ngettext("Ignore solutions containing this action.", "Ignore solutions containing these actions.", count)); no_preference_button->set_tooltip_text(ngettext("Allow solutions containing this action, but do not prefer them to other solutions.", "Allow solutions containing these actions, but do not prefer them to other solutions.", count)); accept_button->set_tooltip_text(ngettext("Always prefer this action over alternatives that have not been accepted.", "Always prefer these actions over alternatives that have not been accepted.", count)); if(contains_rejected) { reject_button->set_active(true); reject_button->set_inconsistent(contains_accepted || contains_no_preference); } else { reject_button->set_active(false); reject_button->set_inconsistent(false); } if(contains_no_preference) { no_preference_button->set_active(true); no_preference_button->set_inconsistent(contains_rejected || contains_accepted); } else { no_preference_button->set_active(false); no_preference_button->set_inconsistent(false); } if(contains_accepted) { accept_button->set_active(true); accept_button->set_inconsistent(contains_rejected || contains_no_preference); } else { accept_button->set_active(false); accept_button->set_inconsistent(false); } bool sensitive; if(count == 0) { // Maybe hide the buttons and/or show a label saying to select // an action? sensitive = false; accept_button->set_active(false); no_preference_button->set_active(false); reject_button->set_active(false); } else sensitive = true; accept_button->set_sensitive(sensitive); no_preference_button->set_sensitive(sensitive); reject_button->set_sensitive(sensitive); } void ResolverTab::do_reject_choice(const maybe > &maybe_c) { typedef generic_choice choice; choice c; if(maybe_c.extract(c)) { switch(c.get_type()) { case choice::install_version: get_resolver()->reject_version(c.get_ver()); break; case choice::break_soft_dep: get_resolver()->harden_dep(c.get_dep()); break; } } } namespace { struct collect_choices { public: typedef generic_choice choice; private: std::vector > ⌖ cwidget::util::ref_ptr solution_view; public: collect_choices(std::vector > &_target, const cwidget::util::ref_ptr &_solution_view) : target(_target), solution_view(_solution_view) { } void visit(const Gtk::TreeModel::iterator &iter) const { const maybe &maybe_c((*iter)[solution_view->get_columns().Choice]); target.push_back(maybe_c); } }; } void ResolverTab::do_no_preference_choice(const maybe > &maybe_c) { typedef generic_choice choice; choice c; if(maybe_c.extract(c)) { switch(c.get_type()) { case choice::install_version: get_resolver()->unreject_version(c.get_ver()); get_resolver()->unmandate_version(c.get_ver()); break; case choice::break_soft_dep: get_resolver()->unharden_dep(c.get_dep()); get_resolver()->unapprove_broken_dep(c.get_dep()); break; } } } void ResolverTab::do_accept_choice(const maybe > &maybe_c) { typedef generic_choice choice; choice c; if(maybe_c.extract(c)) { switch(c.get_type()) { case choice::install_version: get_resolver()->mandate_version(c.get_ver()); break; case choice::break_soft_dep: get_resolver()->approve_broken_dep(c.get_dep()); break; } } } void ResolverTab::reject_button_toggled() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); if(toggle_signals_suppressed || get_resolver() == NULL || !get_resolver()->resolver_exists()) { LOG_TRACE(logger, "reject_button_toggled: not rejecting packages."); return; } suppress_toggle_signals suppressor(*this); // Don't do anything unless the reject button is newly active. if(!reject_button->get_active()) { LOG_TRACE(logger, "reject_button_toggled: the reject button was un-toggled; not doing anything."); return; } LOG_DEBUG(logger, "reject_button_toggled: rejecting the selected choices."); Glib::RefPtr selection = solution_view->get_treeview()->get_selection(); typedef generic_choice choice; std::vector > selected_choices; collect_choices collector(selected_choices, solution_view); selection->selected_foreach_iter(sigc::mem_fun(collector, &collect_choices::visit)); for(std::vector >::const_iterator it = selected_choices.begin(); it != selected_choices.end(); ++it) do_reject_choice(*it); } void ResolverTab::no_preference_button_toggled() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); if(toggle_signals_suppressed || get_resolver() == NULL || !get_resolver()->resolver_exists()) { LOG_TRACE(logger, "no_preference_button_toggled: not clearing states."); return; } suppress_toggle_signals suppressor(*this); if(!no_preference_button->get_active()) { LOG_TRACE(logger, "no_preference_button_toggled: the no-preference button was un-toggled; not doing anything."); return; } Glib::RefPtr selection = solution_view->get_treeview()->get_selection(); LOG_DEBUG(logger, "no_preference_button_toggled: clearing reject/accept states of the selected choices."); typedef generic_choice choice; std::vector > selected_choices; collect_choices collector(selected_choices, solution_view); selection->selected_foreach_iter(sigc::mem_fun(collector, &collect_choices::visit)); for(std::vector >::const_iterator it = selected_choices.begin(); it != selected_choices.end(); ++it) do_no_preference_choice(*it); } void ResolverTab::accept_button_toggled() { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); if(toggle_signals_suppressed || get_resolver() == NULL || !get_resolver()->resolver_exists()) { LOG_TRACE(logger, "accept_button_toggled: not clearing states."); return; } suppress_toggle_signals suppressor(*this); if(!accept_button->get_active()) { LOG_TRACE(logger, "accept_button_toggled: the accept button was un-toggled; not doing anything."); return; } Glib::RefPtr selection = solution_view->get_treeview()->get_selection(); LOG_DEBUG(logger, "accept_button_toggled: accepting the selected choices."); typedef generic_choice choice; std::vector > selected_choices; collect_choices collector(selected_choices, solution_view); selection->selected_foreach_iter(sigc::mem_fun(collector, &collect_choices::visit)); for(std::vector >::const_iterator it = selected_choices.begin(); it != selected_choices.end(); ++it) do_accept_choice(*it); } string ResolverTab::archives_text(const pkgCache::VerIterator &ver) { string rval; bool is_first = true; for(pkgCache::VerFileIterator vf=ver.FileList(); !vf.end(); ++vf) { if(is_first) is_first = false; else rval += ", "; if(vf.File().Archive()) rval += vf.File().Archive(); else rval += _(""); } return rval; } std::string ResolverTab::dep_targets(const pkgCache::DepIterator &start) const { std::string rval; bool is_first = true; eassert(!start.end()); for(pkgCache::DepIterator d = start; !d.end(); ++d) { if(is_first) is_first = false; else rval += " | "; rval += d.TargetPkg().Name(); if(d.TargetVer()) { rval += " ("; rval += d.CompType(); rval += " "; rval += d.TargetVer(); rval += ")"; } if((d->CompareOp & pkgCache::Dep::Or) == 0) break; } return rval; } std::string ResolverTab::dep_text(const pkgCache::DepIterator &d) const { const char *name = const_cast(d).ParentPkg().Name(); std::string targets = dep_targets(d); switch(d->Type) { case pkgCache::Dep::Depends: return ssprintf(_("%s depends upon %s"), name, targets.c_str()); case pkgCache::Dep::PreDepends: return ssprintf(_("%s pre-depends upon %s"), name, targets.c_str()); case pkgCache::Dep::Suggests: return ssprintf(_("%s suggests %s"), name, targets.c_str()); case pkgCache::Dep::Recommends: return ssprintf(_("%s recommends %s"), name, targets.c_str()); case pkgCache::Dep::Conflicts: return ssprintf(_("%s conflicts with %s"), name, targets.c_str()); case pkgCache::Dep::DpkgBreaks: return ssprintf(_("%s breaks %s"), name, targets.c_str()); case pkgCache::Dep::Replaces: return ssprintf(_("%s replaces %s"), name, targets.c_str()); case pkgCache::Dep::Obsoletes: return ssprintf(_("%s obsoletes %s"), name, targets.c_str()); case pkgCache::Dep::Enhances: return ssprintf(_("%s enhances %s"), name, targets.c_str()); default: abort(); } } void ResolverTab::append_choice(const Glib::RefPtr &store, const cwidget::util::ref_ptr &view, const Gtk::TreeModel::Row &parent_row, const choice &c) const { Gtk::TreeModel::iterator iter = store->append(parent_row.children()); Gtk::TreeModel::Row row(*iter); row[view->get_columns().ActionMarkup] = "" + render_choice_brief_markup(c) + "\n" + render_choice_description_markup(c) + ""; row[view->get_columns().Choice] = c; } Glib::RefPtr ResolverTab::render_as_action_groups(const aptitude_solution &sol) { Glib::RefPtr store(Gtk::TreeStore::create(solution_view->get_columns())); if(sol.get_choices().size() == 0) { Gtk::TreeModel::iterator iter = store->append(); Gtk::TreeModel::Row row = *iter; row[solution_view->get_columns().ActionMarkup] = Glib::Markup::escape_text(_("Internal error: unexpected null solution.")); row[solution_view->get_columns().BgSet] = false; } else { typedef generic_choice choice; // Bin packages according to what will happen to them. vector remove_packages; vector keep_packages; vector install_packages; vector downgrade_packages; vector upgrade_packages; vector unresolved; typedef generic_choice choice; typedef generic_choice_set choice_set; for(choice_set::const_iterator i = sol.get_choices().begin(); i != sol.get_choices().end(); ++i) { switch(i->get_type()) { case choice::install_version: { pkgCache::PkgIterator pkg = i->get_ver().get_pkg(); pkgCache::VerIterator curver=pkg.CurrentVer(); pkgCache::VerIterator newver = i->get_ver().get_ver(); if(curver.end()) { if(newver.end()) keep_packages.push_back(*i); else install_packages.push_back(*i); } else if(newver.end()) remove_packages.push_back(*i); else if(newver == curver) keep_packages.push_back(*i); else { int cmp=_system->VS->CmpVersion(curver.VerStr(), newver.VerStr()); // The versions shouldn't be equal -- otherwise // something is majorly wrong. // eassert(cmp!=0); // // The above is not true: consider, eg, the case of a // locally compiled package and a standard package. /** \todo indicate "sidegrades" separately? */ if(cmp<=0) upgrade_packages.push_back(*i); else if(cmp>0) downgrade_packages.push_back(*i); } } break; case choice::break_soft_dep: unresolved.push_back(*i); break; } } typedef generic_solution::choice_name_lt choice_name_lt; sort(remove_packages.begin(), remove_packages.end(), choice_name_lt()); sort(keep_packages.begin(), keep_packages.end(), choice_name_lt()); sort(install_packages.begin(), install_packages.end(), choice_name_lt()); sort(downgrade_packages.begin(), downgrade_packages.end(), choice_name_lt()); sort(upgrade_packages.begin(), upgrade_packages.end(), choice_name_lt()); sort(unresolved.begin(), unresolved.end(), choice_name_lt()); if(!remove_packages.empty()) { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = ssprintf("%s", Glib::Markup::escape_text(_("Remove the following packages:")).c_str()); parent_row[solution_view->get_columns().BgSet] = false; for(vector::const_iterator i = remove_packages.begin(); i!=remove_packages.end(); ++i) append_choice(store, solution_view, parent_row, *i); } if(!install_packages.empty()) { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = ssprintf("%s", Glib::Markup::escape_text(_("Install the following packages:")).c_str()); parent_row[solution_view->get_columns().BgSet] = false; for(vector::const_iterator i = install_packages.begin(); i!=install_packages.end(); ++i) append_choice(store, solution_view, parent_row, *i); } if(!keep_packages.empty()) { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = ssprintf("%s", Glib::Markup::escape_text(_("Keep the following packages:")).c_str()); parent_row[solution_view->get_columns().BgSet] = false; for(vector::const_iterator i = keep_packages.begin(); i!=keep_packages.end(); ++i) append_choice(store, solution_view, parent_row, *i); } if(!upgrade_packages.empty()) { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = ssprintf("%s", Glib::Markup::escape_text(_("Upgrade the following packages:")).c_str()); parent_row[solution_view->get_columns().BgSet] = false; for(vector::const_iterator i = upgrade_packages.begin(); i!=upgrade_packages.end(); ++i) append_choice(store, solution_view, parent_row, *i); } if(!downgrade_packages.empty()) { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = ssprintf("%s", Glib::Markup::escape_text(_("Downgrade the following packages:")).c_str()); parent_row[solution_view->get_columns().BgSet] = false; for(vector::const_iterator i = downgrade_packages.begin(); i!=downgrade_packages.end(); ++i) append_choice(store, solution_view, parent_row, *i); } if(!unresolved.empty()) { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = ssprintf("%s", Glib::Markup::escape_text(_("Leave the following dependencies unresolved:")).c_str()); parent_row[solution_view->get_columns().BgSet] = false; for(std::vector::const_iterator i = unresolved.begin(); i != unresolved.end(); ++i) append_choice(store, solution_view, parent_row, *i); } } return store; } Glib::RefPtr ResolverTab::render_as_explanation(const aptitude_solution &sol) { Glib::RefPtr store(Gtk::TreeStore::create(solution_view->get_columns())); if(sol.get_choices().size() == 0) { Gtk::TreeModel::iterator iter = store->append(); Gtk::TreeModel::Row row = *iter; row[solution_view->get_columns().ActionMarkup] = Glib::Markup::escape_text(_("Internal error: unexpected null solution.")); row[solution_view->get_columns().BgSet] = false; } else { typedef generic_choice_set choice_set; typedef generic_choice choice; // Store just the choices that modify the state of the world. std::vector actions; for(choice_set::const_iterator it = sol.get_choices().begin(); it != sol.get_choices().end(); ++it) { switch(it->get_type()) { case choice::install_version: actions.push_back(*it); break; default: break; } } std::sort(actions.begin(), actions.end(), aptitude_solution::choice_id_compare()); for(std::vector::const_iterator it = actions.begin(); it != actions.end(); ++it) { switch(it->get_type()) { case choice::install_version: { Gtk::TreeModel::iterator parent_iter = store->append(); Gtk::TreeModel::Row parent_row = *parent_iter; parent_row[solution_view->get_columns().ActionMarkup] = "" + Glib::Markup::escape_text(dep_text((*it).get_dep().get_dep())) + ""; parent_row[solution_view->get_columns().BgSet] = false; Gtk::TreeModel::iterator iter = store->append(parent_row.children()); Gtk::TreeModel::Row row = *iter; Glib::ustring markup; aptitude_resolver_version ver((*it).get_ver()); pkgCache::PkgIterator pkg = ver.get_pkg(); action_type action(analyze_action(ver)); using cwidget::util::ssprintf; switch(action) { case action_remove: markup = ssprintf(_("Remove %s [%s (%s)]"), Glib::Markup::escape_text(pkg.Name()).c_str(), Glib::Markup::escape_text(pkg.CurrentVer().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(pkg.CurrentVer())).c_str()); break; case action_install: markup = ssprintf(_("Install %s [%s (%s)]"), Glib::Markup::escape_text(pkg.Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_ver())).c_str()); break; case action_keep: if(ver.get_ver().end()) markup = ssprintf(_("Cancel the installation of %s"), Glib::Markup::escape_text(pkg.Name()).c_str()); else if(ver.get_package().current_version().get_ver().end()) markup = ssprintf(_("Cancel the removal of %s"), pkg.Name()); else markup = ssprintf(_("Keep %s at version %s (%s)"), Glib::Markup::escape_text(pkg.Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_ver())).c_str()); break; case action_upgrade: markup = ssprintf(_("Upgrade %s [%s (%s) -> %s (%s)]"), Glib::Markup::escape_text(pkg.Name()).c_str(), Glib::Markup::escape_text(pkg.CurrentVer().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(pkg.CurrentVer())).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_ver())).c_str()); break; case action_downgrade: markup = ssprintf(_("Downgrade %s [%s (%s) -> %s (%s)]"), Glib::Markup::escape_text(pkg.Name()).c_str(), Glib::Markup::escape_text(pkg.CurrentVer().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(pkg.CurrentVer())).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str(), Glib::Markup::escape_text(archives_text(ver.get_ver())).c_str()); break; default: markup = "Internal error: bad action type"; break; } std::string description_markup(render_choice_description_markup(*it)); row[solution_view->get_columns().ActionMarkup] = "" + markup + "\n" + description_markup + ""; } break; default: // \todo Add items for breaking soft dependencies? break; } } } return store; } // Helpers to make render_already_generated_row less huge. namespace { std::string largenum(int n) { return cwidget::util::ssprintf("%d", n); } std::string render_solution_single_line(const generic_solution &sol) { std::string list_text; bool first = true; for(choice_set::const_iterator it = sol.get_choices().begin(); it != sol.get_choices().end(); ++it) { if(!first) list_text += ", "; switch(it->get_type()) { case choice::install_version: { aptitude_resolver_version ver(it->get_ver()); switch(analyze_action(ver)) { case action_remove: { const char * const tmpl(first ? _("Remove %s") : _("remove %s")); list_text += ssprintf(tmpl, Glib::Markup::escape_text(ver.get_pkg().Name()).c_str()); } break; case action_keep: if(!ver.get_ver().end()) { const char * const tmpl(first ? _("Keep %s at version %s") : _("keep %s at version %s")); list_text += ssprintf(tmpl, Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str()); } else { const char * const tmpl(first ? _("Cancel the installation of %s") : _("cancel the installation of %s")); list_text += ssprintf(tmpl, Glib::Markup::escape_text(ver.get_pkg().Name()).c_str()); } break; case action_install: { const char * const tmpl(first ? _("Install %s %s") : _("install %s %s")); list_text += ssprintf(tmpl, Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str()); } break; case action_downgrade: { const char * const tmpl(first ? _("Downgrade %s to version %s") : _("downgrade %s to version %s")); list_text += ssprintf(tmpl, Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str()); } break; case action_upgrade: { const char * const tmpl(first ? _("Upgrade %s to version %s") : _("upgrade %s to version %s")); list_text += ssprintf(tmpl, Glib::Markup::escape_text(ver.get_pkg().Name()).c_str(), Glib::Markup::escape_text(ver.get_ver().VerStr()).c_str()); } break; } } break; case choice::break_soft_dep: { const char * const tmpl(first ? _("Leave %s unresolved") : _("leave %s unresolved")); const std::string dep_string = cwidget::util::transcode(dep_text(it->get_dep().get_dep()), "UTF-8"); list_text += ssprintf(tmpl, Glib::Markup::escape_text(dep_string).c_str()); } break; } first = false; } return list_text; } std::string render_solution_summary(const generic_solution &sol, const std::string &separator) { int num_remove = 0; int num_keep = 0; int num_install = 0; int num_downgrade = 0; int num_upgrade = 0; int num_unresolved = 0; for(choice_set::const_iterator it = sol.get_choices().begin(); it != sol.get_choices().end(); ++it) { switch(it->get_type()) { case choice::install_version: { aptitude_resolver_version ver(it->get_ver()); switch(analyze_action(ver)) { case action_remove: ++num_remove; break; case action_keep: ++num_keep; break; case action_install: ++num_install; break; case action_downgrade: ++num_downgrade; break; case action_upgrade: ++num_upgrade; break; } } break; case choice::break_soft_dep: ++num_unresolved; break; } } using cwidget::util::ssprintf; std::vector rval; if(num_install > 0) rval.push_back(ssprintf(ngettext("%s install", "%s installs", num_install), largenum(num_install).c_str())); if(num_remove > 0) rval.push_back(ssprintf(ngettext("%s remove", "%s removes", num_remove), largenum(num_remove).c_str())); if(num_keep > 0) rval.push_back(ssprintf(ngettext("%s keep", "%s keeps", num_keep), largenum(num_keep).c_str())); if(num_upgrade > 0) rval.push_back(ssprintf(ngettext("%s upgrade", "%s upgrades", num_upgrade), largenum(num_upgrade).c_str())); if(num_downgrade > 0) rval.push_back(ssprintf(ngettext("%s downgrade", "%s downgrades", num_downgrade), largenum(num_downgrade).c_str())); if(num_unresolved > 0) rval.push_back(ssprintf(ngettext("%s unresolved recommendation", "%s unresolved recommendations", num_unresolved), largenum(num_unresolved).c_str())); std::string tmp; for(std::vector::const_iterator it = rval.begin(); it != rval.end(); ++it) { if(!tmp.empty()) tmp += separator; tmp += *it; } return tmp; } std::string render_solution_long_markup(const generic_solution &sol) { std::vector remove, keep, install, downgrade, upgrade, unresolved; for(choice_set::const_iterator it = sol.get_choices().begin(); it != sol.get_choices().end(); ++it) { switch(it->get_type()) { case choice::install_version: { aptitude_resolver_version ver(it->get_ver()); switch(analyze_action(ver)) { case action_remove: remove.push_back(*it); break; case action_keep: keep.push_back(*it); break; case action_install: install.push_back(*it); break; case action_downgrade: downgrade.push_back(*it); break; case action_upgrade: upgrade.push_back(*it); break; } } break; case choice::break_soft_dep: unresolved.push_back(*it); break; } } typedef generic_solution::choice_name_lt choice_name_lt; std::sort(remove.begin(), remove.end(), choice_name_lt()); std::sort(keep.begin(), keep.end(), choice_name_lt()); std::sort(install.begin(), install.end(), choice_name_lt()); std::sort(downgrade.begin(), downgrade.end(), choice_name_lt()); std::sort(upgrade.begin(), upgrade.end(), choice_name_lt()); std::sort(unresolved.begin(), unresolved.end(), choice_name_lt()); std::string rval; using cwidget::util::ssprintf; if(!remove.empty()) { rval += ssprintf("%s", Glib::Markup::escape_text(_("Remove the following packages:")).c_str()); for(std::vector::const_iterator it = remove.begin(); it != remove.end(); ++it) rval += ssprintf("\n %s", render_choice_brief_markup(*it).c_str()); } if(!install.empty()) { if(!rval.empty()) rval += "\n\n"; rval += ssprintf("%s", Glib::Markup::escape_text(_("Install the following packages:")).c_str()); for(std::vector::const_iterator it = install.begin(); it != install.end(); ++it) rval += ssprintf("\n %s", render_choice_brief_markup(*it).c_str()); } if(!keep.empty()) { if(!rval.empty()) rval += "\n\n"; rval += ssprintf("%s", Glib::Markup::escape_text(_("Keep the following packages:")).c_str()); for(std::vector::const_iterator it = keep.begin(); it != keep.end(); ++it) rval += ssprintf("\n %s", render_choice_brief_markup(*it).c_str()); } if(!upgrade.empty()) { if(!rval.empty()) rval += "\n\n"; rval += ssprintf("%s", Glib::Markup::escape_text(_("Upgrade the following packages:")).c_str()); for(std::vector::const_iterator it = upgrade.begin(); it != upgrade.end(); ++it) rval += ssprintf("\n %s", render_choice_brief_markup(*it).c_str()); } if(!downgrade.empty()) { if(!rval.empty()) rval += "\n\n"; rval += ssprintf("%s", Glib::Markup::escape_text(_("Downgrade the following packages:")).c_str()); for(std::vector::const_iterator it = downgrade.begin(); it != downgrade.end(); ++it) rval += ssprintf("\n %s", render_choice_brief_markup(*it).c_str()); } if(!unresolved.empty()) { if(!rval.empty()) rval += "\n\n"; rval += ssprintf("%s", Glib::Markup::escape_text(_("Leave the following dependencies unresolved:")).c_str()); for(std::vector::const_iterator it = unresolved.begin(); it != unresolved.end(); ++it) rval += ssprintf("\n %s", render_choice_brief_markup(*it).c_str()); } return rval; } } void ResolverTab::render_already_generated_row(const aptitude_solution &sol, int index, Gtk::TreeModel::Row &row) const { // How large a solution can be before we give up on rendering it // fully in the list. const unsigned int list_render_limit = 3; // How large a solution can be before we give up on rendering it // fully in the tooltip. const unsigned int tooltip_render_limit = 20; row[already_generated_columns.Index] = cwidget::util::ssprintf("%d", index); row[already_generated_columns.IndexNum] = index; if(sol.get_choices().size() <= list_render_limit) row[already_generated_columns.Markup] = render_solution_single_line(sol); else row[already_generated_columns.Markup] = render_solution_summary(sol, ", "); if(sol.get_choices().size() <= tooltip_render_limit) row[already_generated_columns.TooltipMarkup] = render_solution_long_markup(sol); else row[already_generated_columns.TooltipMarkup] = render_solution_summary(sol, "\n"); row[already_generated_columns.Solution] = sol; } // The resolver tab manages the resolver state as follows. // // The resolver state is always set to the most recently generated // solution, or one past that point if a new solution is being // generated. Pressing "find next" moves to the next solution after // the last generated solution, if possible. If a solution is being // calculated, an appropriate message is shown to inform the user of // this fact and the "find next" button is disabled. void ResolverTab::update_from_state(const resolver_manager::state &state, bool force_update) { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Updating the resolver tab (" << (force_update ? "forced" : "not forced") << ")"); // Maybe I should log more information about the resolver state as // we log it? Glib::RefPtr store = Gtk::TreeStore::create(solution_view->get_columns()); bool added_anything = false; while(already_generated_model->children().size() < (unsigned)state.generated_solutions) { const int next_solution_num = already_generated_model->children().size(); aptitude_solution sol = get_resolver()->get_solution(next_solution_num, 0); Gtk::TreeModel::iterator iter = already_generated_model->append(); Gtk::TreeModel::Row row(*iter); LOG_DEBUG(logger, "Resolver tab: adding a solution: " << sol); render_already_generated_row(sol, next_solution_num + 1, row); added_anything = true; } // If we added a row, select the most recent solution (i.e., the // last one in the list). if(added_anything) { Gtk::TreeModel::iterator last = already_generated_model->children().end(); --last; LOG_TRACE(logger, "Resolver tab: selecting the newly added solution."); already_generated_view->get_selection()->select(last); } update_solution_pane(false); find_next_solution_button->set_sensitive(do_find_next_solution_enabled_from_state(state)); // WARNING:Minor hack. // // We should always hide and delete the resolver view when there // isn't a resolver yet. But if one somehow gets created before // the resolver exists, we should show something sensible. And // tab_del will really-and-truly destroy the parts of this tab // when it is invoked (not to mention the Tab object itself), so // we have to do the just-in-case setting up of the view before // calling tab_del. if(!state.resolver_exists) tab_del(this); } void ResolverTab::update_solution_pane(bool force_update) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: updating the solution pane."); resolver_manager::state state(get_resolver()->state_snapshot()); aptitude_solution new_solution; // "index" is the "number" of the solution in the solutions list; // it has a meaningful value whenever new_solution is valid. int index = -1; if(already_generated_view->get_selection()->count_selected_rows() == 1) { Gtk::TreeModel::iterator selected_iter = already_generated_view->get_selection()->get_selected(); // Should never ever happen, but this is an extra // sanity-check. if(selected_iter) { new_solution = (*selected_iter)[already_generated_columns.Solution]; index = (*selected_iter)[already_generated_columns.IndexNum]; } } // If there is no new solution, always update the displayed text // in case the state changed. if(!new_solution.valid()) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: there is no selected solution."); displayed_solution = new_solution; #if 0 /* This code is commented out because the solution treeview doesn't seem to be useful for displaying messages (they get truncated). Maybe we could swap it out for a textview when there's nothing to show? */ // Display some text to let the user know there's nothing // to see. Glib::RefPtr store = Gtk::TreeStore::create(solution_view->get_columns()); Gtk::TreeModel::iterator text_row_iter = store->append(); Gtk::TreeModel::Row text_row(*text_row_iter); if(already_generated_model->children().empty()) { // It could be empty because we haven't found a // solution yet, or because no solutions were found at // all. (if there are solutions that aren't in the // list yet, I assume that we'll add them momentarily // anyway) if(state.generated_solutions > 0 || !state.solutions_exhausted) { LOG_DEBUG(Loggers::getAptitudeGtkResolver(), "Resolver tab: dependencies are still being resolved."); text_row[solution_view->get_columns().Name] = _("Resolving dependencies..."); pResolverStatus->set_text(_("No solutions yet.")); } else { LOG_DEBUG(Loggers::getAptitudeGtkResolver(), "Resolver tab: no dependency solution was found."); text_row[solution_view->get_columns().Name] = _("No dependency solution was found."); pResolverStatus->set_text(_("No solutions.")); } } else { LOG_WARN(Loggers::getAptitudeGtkResolver(), "Resolver tab: there are solutions, but no solution is selected!"); // This message shouldn't appear, but if it does the // user would probably like to know how to make it go // away. :-) text_row[solution_view->get_columns().Name] = _("To view a solution, select it from the list to the right."); pResolverStatus->set_text(ssprintf(_("%u solutions."), already_generated_model->children().size())); } solution_view->set_model(store, get_resolver()); solution_view->get_treeview()->expand_all(); #endif } else if(force_update || new_solution != displayed_solution) { if(Loggers::getAptitudeGtkResolver()->isEnabledFor(logging::DEBUG_LEVEL)) { if(new_solution != displayed_solution) LOG_DEBUG(Loggers::getAptitudeGtkResolver(), "Resolver tab: displaying a different solution: " << new_solution); else LOG_DEBUG(Loggers::getAptitudeGtkResolver(), "Resolver tab: the solution " << new_solution << " is already displayed, but forcing an update as requested."); } displayed_solution = new_solution; Glib::RefPtr store; if(pButtonShowExplanation->get_active()) store = render_as_explanation(displayed_solution); else store = render_as_action_groups(displayed_solution); solution_view->set_model(store, get_resolver()); solution_view->get_treeview()->expand_all(); pResolverStatus->set_markup(ssprintf(_("Solution %s of %s."), largenum(index).c_str(), largenum(state.generated_solutions).c_str())); } else LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: the solution " << new_solution << " is already displayed."); pResolverApply->set_sensitive(displayed_solution.valid()); } bool ResolverTab::do_find_next_solution_enabled_from_state(const resolver_manager::state &state) { if(!state.resolver_exists) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: disabling the find-next-solution button: there are no broken dependencies."); return false; } else if(state.background_thread_active) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: disabling the find-next-solution button: the resolver is already running."); return false; } else if(state.solutions_exhausted) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: disabling the find-next-solution button: there are no more solutions."); return false; } else return true; } bool ResolverTab::do_find_next_solution_enabled() { if (get_resolver() == NULL) return false; resolver_manager::state state = get_resolver()->state_snapshot(); return do_find_next_solution_enabled_from_state(state); } void ResolverTab::do_find_next_solution() { resolver_manager::state state = get_resolver()->state_snapshot(); if(do_find_next_solution_enabled_from_state(state)) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: moving to solution number " << state.generated_solutions); get_resolver()->discard_error_information(); get_resolver()->select_solution(state.generated_solutions); do_start_solution_calculation(get_resolver()); } } void ResolverTab::do_apply_solution() { if (!apt_cache_file || get_resolver() == NULL) return; if (displayed_solution.valid()) { LOG_TRACE(Loggers::getAptitudeGtkResolver(), "Resolver tab: Applying the current solution: " << displayed_solution); const bool safe_using_internal_resolver = using_internal_resolver; // BIG FAT WARNING: applying the solution will destroy this tab // as a side effect! From here on out we can't refer to members // of this instance. undo_group *undo = new apt_undo_group; (*apt_cache_file)->apply_solution(displayed_solution, undo); if (!undo->empty()) apt_undos->add_item(undo); else delete undo; // This is a bit of a hack, to ensure that the user doesn't get // dropped back at the dashboard after fixing an upgrade // manually. if(safe_using_internal_resolver) pMainWindow->do_preview(); // No need to delete the tab manually here: it should already // have been deleted. Deleting it here would introduce a // double-free bug. } } void ResolverTab::set_fix_upgrade_resolver(resolver_manager *manager) { logging::LoggerPtr logger(Loggers::getAptitudeGtkResolver()); LOG_TRACE(logger, "Setting the resolver manager to " << manager); resolver_state_changed_connection.disconnect(); using_internal_resolver = true; resolver = manager; if(manager != NULL) { manager->state_changed.connect(sigc::bind(sigc::ptr_fun(&do_start_first_solution_calculation), manager)); manager->state_changed.connect(sigc::bind(sigc::mem_fun(*this, &ResolverTab::update), false)); get_widget()->set_sensitive(true); boost::shared_ptr k = boost::make_shared(manager); manager->maybe_start_solution_calculation(k, post_thunk); resolver_fixing_upgrade_label->show(); resolver_fixing_upgrade_progress_bar->hide(); } else { get_widget()->set_sensitive(false); resolver_fixing_upgrade_label->hide(); resolver_fixing_upgrade_progress_bar->show(); resolver_fixing_upgrade_progress_bar->pulse(); } resolver_fixing_upgrade_message->show(); setup_resolver_connections(); update(true); } void ResolverTab::pulse_fix_upgrade_resolver_progress() { resolver_fixing_upgrade_progress_bar->pulse(); } }