// broken_indicator.cc // // Copyright (C) 2005 Daniel Burrows // // 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 "broken_indicator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef generic_solution aptitude_solution; /** A simple indicator, usually placed at the bottom of the screen, * that describes the current state of the problem resolver. Hidden * if no problem resolver is active. * * \todo write a simple table fragment class and use that to * right-justify the text that obviously should be */ class broken_indicator:public vs_text_layout { aptitude_solution last_sol; /** Records whether we had generated all solutions at the time of * the last update. */ bool last_complete : 1; /** Records whether the background thread was active at the time of * the last update. */ bool last_background_active : 1; /** Tracks the phase of the visual "spinner". */ int spin_count; void handle_cache_reload() { if(resman != NULL) resman->state_changed.connect(sigc::mem_fun(*this, &broken_indicator::post_update)); update(); } protected: broken_indicator() :spin_count(0) { if(resman != NULL) resman->state_changed.connect(sigc::mem_fun(*this, &broken_indicator::post_update)); cache_closed.connect(sigc::mem_fun(*this, &broken_indicator::update)); cache_reloaded.connect(sigc::mem_fun(*this, &broken_indicator::handle_cache_reload)); set_bg_style(get_style("Error")); update(); vscreen_addtimeout(new slot_event(sigc::mem_fun(this, &broken_indicator::tick_timeout)), aptcfg->FindI(PACKAGE "::Spin-Interval", 500)); } private: static fragment *key_hint_fragment(const resolver_manager::state &state) { wstring next=global_bindings.readable_keyname("NextSolution"); wstring prev=global_bindings.readable_keyname("PrevSolution"); wstring examine=global_bindings.readable_keyname("ExamineSolution"); wstring apply=global_bindings.readable_keyname("ApplySolution"); style st_normal; style st_disabled; st_disabled.attrs_off(A_BOLD); st_disabled.attrs_on(A_DIM); st_disabled.set_fg(COLOR_BLACK); vector key_hints; key_hints.push_back(fragf(_("%s: Examine"), examine.c_str())); bool can_apply = (state.selected_solution < state.generated_solutions); bool can_next = (state.selected_solution < state.generated_solutions && !(state.selected_solution + 1 == state.generated_solutions && state.solutions_exhausted)); key_hints.push_back(style_fragment(fragf(_("%s: Apply"), apply.c_str()), can_apply ? st_normal : st_disabled)); key_hints.push_back(style_fragment(fragf(_("%s: Next"), next.c_str()), can_next ? st_normal : st_disabled)); bool can_prev = (state.selected_solution > 0); key_hints.push_back(style_fragment(fragf(_("%s: Previous"), prev.c_str()), can_prev ? st_normal : st_disabled)); return join_fragments(key_hints, L" "); } void tick_timeout() { vs_widget_ref tmpref(this); if(resman != NULL && resman->background_thread_active()) { ++spin_count; update(); vscreen_update(); } vscreen_addtimeout(new slot_event(sigc::mem_fun(this, &broken_indicator::tick_timeout)), aptcfg->FindI(PACKAGE "::Spin-Interval", 500)); } std::string spin_string(const resolver_manager::state &state) const { if(!state.background_thread_active) return " "; switch(spin_count % 4) { case 0: return "."; case 1: return "o"; case 2: return "O"; case 3: return "o"; default: return "?"; } } struct update_event : public vscreen_event { broken_indicator *b; public: update_event(broken_indicator *_b) : b(_b) { } void dispatch() { b->update(); } }; /** Post an update to run in the main thread; needed since the * selected_signal_changed signal might theoretically run from a * background thread. (at the moment it shouldn't, but this will * help avoid nasty surprises) */ void post_update() { vscreen_post_event(new update_event(this)); } public: static ref_ptr create() { ref_ptr rval(new broken_indicator); rval->decref(); return rval; } // TODO: split this monster up. void update() { vs_widget_ref tmpref(this); if(resman == NULL || !resman->resolver_exists()) { set_fragment(fragf("")); last_sol.nullify(); hide(); return; } // Take a snapshot of the state. resolver_manager::state state = resman->state_snapshot(); if(state.solutions_exhausted && state.generated_solutions == 0) { set_fragment(fragf(_("Unable to resolve dependencies."))); last_sol.nullify(); show(); return; } // Handle the case where the resolver is churning away. if(state.selected_solution >= state.generated_solutions) { // TODO: add a column-generating fragment that can // left/right justify stuff. vector columns; columns.push_back(fragment_column_entry(true, 1, fragment_column_entry::top, flowbox(text_fragment(ssprintf(_("[%d(%d)/...] Resolving dependencies"), state.selected_solution + 1, state.generated_solutions))))); columns.push_back(fragment_column_entry(false, 1, fragment_column_entry::top, NULL)); columns.push_back(fragment_column_entry(false, 1, fragment_column_entry::top, text_fragment(spin_string(state)))); set_fragment(sequence_fragment(fragment_columns(columns), key_hint_fragment(state), NULL)); last_sol.nullify(); show(); return; } aptitude_solution sol = resman->get_solution(state.selected_solution, 0); // This test always fails the first time update() is called, since // sol is never NULL and last_sol is initialized to NULL. if(sol == last_sol && state.solutions_exhausted == last_complete && // If there's an active thread we need to redraw the widget to // include the spinner. !last_background_active && state.background_thread_active == last_background_active) return; last_sol = sol; last_complete = state.solutions_exhausted; last_background_active = state.background_thread_active; if(sol.get_actions().empty()) { set_fragment(fragf("%s", _("Internal error: unexpected null solution."))); show(); return; } int install_count=0, remove_count=0, keep_count=0, upgrade_count=0, downgrade_count=0; for(imm::map::const_iterator i = sol.get_actions().begin(); i != sol.get_actions().end(); ++i) { pkgCache::PkgIterator pkg=i->first.get_pkg(); pkgCache::VerIterator curver=pkg.CurrentVer(); pkgCache::VerIterator instver=(*apt_cache_file)[pkg].InstVerIter(*apt_cache_file); pkgCache::VerIterator newver=i->second.ver.get_ver(); // If not, we have a problem. eassert(instver!=newver); if(newver == curver) ++keep_count; else if(curver.end()) ++install_count; else if(newver.end()) ++remove_count; 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_count; else if(cmp>0) ++downgrade_count; } } vector fragments; string countstr = ssprintf(state.solutions_exhausted?"[%d/%d]":"[%d(%d)/...]", state.selected_solution + 1, state.generated_solutions); fragments.push_back(fragf("%s ", countstr.c_str())); fragments.push_back(fragf(_("Suggest "))); vector suggestions; if(install_count>1) suggestions.push_back(text_fragment(ssprintf(_("%d installs"), install_count))); else if(install_count == 1) suggestions.push_back(text_fragment(_("1 install"))); if(remove_count>1) suggestions.push_back(text_fragment(ssprintf(_("%d removals"), remove_count))); else if(remove_count == 1) suggestions.push_back(text_fragment(_("1 removal"))); if(keep_count>1) suggestions.push_back(text_fragment(ssprintf(_("%d keeps"), keep_count))); else if(keep_count == 1) suggestions.push_back(text_fragment(_("1 keep"))); if(upgrade_count>1) suggestions.push_back(text_fragment(ssprintf(_("%d upgrades"), upgrade_count))); else if(upgrade_count == 1) suggestions.push_back(text_fragment(_("1 upgrade"))); if(downgrade_count>1) suggestions.push_back(text_fragment(ssprintf(_("%d downgrades"), downgrade_count))); else if(downgrade_count == 1) suggestions.push_back(text_fragment(_("1 downgrade"))); fragments.push_back(join_fragments(suggestions, L",")); if(state.background_thread_active) { vector columns; columns.push_back(fragment_column_entry(true, 1, fragment_column_entry::top, hardwrapbox(sequence_fragment(fragments)))); columns.push_back(fragment_column_entry(false, 1, fragment_column_entry::top, NULL)); columns.push_back(fragment_column_entry(false, 1, fragment_column_entry::top, text_fragment(spin_string(state)))); fragments.clear(); fragments.push_back(fragment_columns(columns)); } else fragments.push_back(newline_fragment()); fragments.push_back(hardwrapbox(key_hint_fragment(state))); fragment *f=sequence_fragment(fragments); set_fragment(f); show(); } }; ref_ptr make_broken_indicator() { return broken_indicator::create(); }