widgets/menu.cpp

Go to the documentation of this file.
00001 /* $Id: menu.cpp 52533 2012-01-07 02:35:17Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 #define GETTEXT_DOMAIN "wesnoth-lib"
00017 
00018 #include "global.hpp"
00019 
00020 #include "widgets/menu.hpp"
00021 
00022 #include "game_config.hpp"
00023 #include "font.hpp"
00024 #include "language.hpp"
00025 #include "image.hpp"
00026 #include "marked-up_text.hpp"
00027 #include "sound.hpp"
00028 #include "video.hpp"
00029 #include "wml_separators.hpp"
00030 
00031 #include <numeric>
00032 
00033 namespace gui {
00034 
00035 menu::basic_sorter::basic_sorter()
00036     : alpha_sort_()
00037     , numeric_sort_()
00038     , id_sort_()
00039     , xp_sort_()
00040     , level_sort_()
00041     , redirect_sort_()
00042     , pos_sort_()
00043     , xp_col_(-1)
00044 {
00045     set_id_sort(-1);
00046 }
00047 
00048 menu::basic_sorter& menu::basic_sorter::set_alpha_sort(int column)
00049 {
00050     alpha_sort_.insert(column);
00051     return *this;
00052 }
00053 
00054 menu::basic_sorter& menu::basic_sorter::set_numeric_sort(int column)
00055 {
00056     numeric_sort_.insert(column);
00057     return *this;
00058 }
00059 
00060 menu::basic_sorter& menu::basic_sorter::set_xp_sort(int column)
00061 {
00062     xp_sort_.insert(column);
00063     return *this;
00064 }
00065 
00066 menu::basic_sorter& menu::basic_sorter::set_level_sort(int level_column, int xp_column)
00067 {
00068     level_sort_.insert(level_column);
00069     xp_col_ = xp_column;
00070     return *this;
00071 }
00072 
00073 menu::basic_sorter& menu::basic_sorter::set_id_sort(int column)
00074 {
00075     id_sort_.insert(column);
00076     return *this;
00077 }
00078 
00079 menu::basic_sorter& menu::basic_sorter::set_redirect_sort(int column, int to)
00080 {
00081     if(column != to) {
00082         redirect_sort_.insert(std::pair<int,int>(column,to));
00083     }
00084 
00085     return *this;
00086 }
00087 
00088 menu::basic_sorter& menu::basic_sorter::set_position_sort(int column, const std::vector<int>& pos)
00089 {
00090     pos_sort_[column] = pos;
00091     return *this;
00092 }
00093 
00094 bool menu::basic_sorter::column_sortable(int column) const
00095 {
00096     const std::map<int,int>::const_iterator redirect = redirect_sort_.find(column);
00097     if(redirect != redirect_sort_.end()) {
00098         return column_sortable(redirect->second);
00099     }
00100 
00101     return alpha_sort_.count(column) == 1 || numeric_sort_.count(column) == 1 ||
00102            pos_sort_.count(column) == 1 || id_sort_.count(column) == 1 ||
00103             xp_sort_.count(column) == 1 || level_sort_.count(column) == 1;
00104 }
00105 
00106 static std::pair<int, int> parse_fraction(const std::string& s)
00107 {
00108     std::vector<std::string> parts = utils::split(s, '/', 0);
00109     parts.resize(2);
00110     int num = lexical_cast_default<int>(parts[0], 0);
00111     int denom = lexical_cast_default<int>(parts[1], 0);
00112     return std::make_pair(num, denom);
00113 }
00114 
00115 static int xp_to_advance(const std::string& s) {
00116     std::pair<int,int> xp_frac = parse_fraction(s);
00117 
00118     //consider units without AMLA or advancement as having xp_max=1000000
00119     if(xp_frac.second == 0)
00120         xp_frac.second = 1000000;
00121 
00122     return xp_frac.second - xp_frac.first;
00123 }
00124 
00125 bool menu::basic_sorter::less(int column, const item& row1, const item& row2) const
00126 {
00127     const std::map<int,int>::const_iterator redirect = redirect_sort_.find(column);
00128     if(redirect != redirect_sort_.end()) {
00129         return less(redirect->second,row1,row2);
00130     }
00131 
00132     if(id_sort_.count(column) == 1) {
00133         return row1.id < row2.id;
00134     }
00135 
00136     if(column < 0 || column >= int(row2.fields.size())) {
00137         return false;
00138     }
00139 
00140     if(column >= int(row1.fields.size())) {
00141         return true;
00142     }
00143 
00144     const std::string& item1 = font::del_tags(row1.fields[column]);
00145     const std::string& item2 = font::del_tags(row2.fields[column]);
00146 
00147     if(alpha_sort_.count(column) == 1) {
00148         std::string::const_iterator begin1 = item1.begin(), end1 = item1.end(),
00149                                     begin2 = item2.begin(), end2 = item2.end();
00150         while(begin1 != end1 && is_wml_separator(*begin1)) {
00151             ++begin1;
00152         }
00153 
00154         while(begin2 != end2 && is_wml_separator(*begin2)) {
00155             ++begin2;
00156         }
00157 
00158         return std::lexicographical_compare(begin1,end1,begin2,end2,chars_less_insensitive);
00159     } else if(numeric_sort_.count(column) == 1) {
00160         int val_1 = lexical_cast_default<int>(item1, 0);
00161         int val_2 = lexical_cast_default<int>(item2, 0);
00162 
00163         return val_1 > val_2;
00164     } else if(xp_sort_.count(column) == 1) {
00165         return xp_to_advance(item1) < xp_to_advance(item2);
00166     } else if(level_sort_.count(column) == 1) {
00167         int level_1 = lexical_cast_default<int>(item1, 0);
00168         int level_2 = lexical_cast_default<int>(item2, 0);
00169         if (level_1 == level_2) {
00170             //break tie using xp
00171             const std::string& xp_item1 = font::del_tags(row1.fields[xp_col_]);
00172             const std::string& xp_item2 = font::del_tags(row2.fields[xp_col_]);
00173             return xp_to_advance(xp_item1) < xp_to_advance(xp_item2);
00174         }
00175         return level_1 > level_2;
00176     }
00177 
00178     const std::map<int,std::vector<int> >::const_iterator itor = pos_sort_.find(column);
00179     if(itor != pos_sort_.end()) {
00180         const std::vector<int>& pos = itor->second;
00181         if(row1.id >= pos.size()) {
00182             return false;
00183         }
00184 
00185         if(row2.id >= pos.size()) {
00186             return true;
00187         }
00188 
00189         return pos[row1.id] < pos[row2.id];
00190     }
00191 
00192     return false;
00193 }
00194 
00195 menu::menu(CVideo& video, const std::vector<std::string>& items,
00196            bool click_selects, int max_height, int max_width,
00197            const sorter* sorter_obj, style *menu_style, const bool auto_join)
00198         : scrollarea(video, auto_join), silent_(false),
00199           max_height_(max_height), max_width_(max_width), max_items_(-1), item_height_(-1),
00200           heading_height_(-1),
00201       cur_help_(-1,-1), help_string_(-1),
00202       selected_(0), click_selects_(click_selects), out_(false),
00203       previous_button_(true), show_result_(false),
00204       double_clicked_(false),
00205       num_selects_(true),
00206       ignore_next_doubleclick_(false),
00207       last_was_doubleclick_(false), use_ellipsis_(false),
00208       sorter_(sorter_obj), sortby_(-1), sortreversed_(false), highlight_heading_(-1)
00209 {
00210     style_ = (menu_style) ? menu_style : &default_style;
00211     style_->init();
00212     fill_items(items, true);
00213 }
00214 
00215 menu::~menu()
00216 {
00217 }
00218 
00219 void menu::fill_items(const std::vector<std::string>& items, bool strip_spaces)
00220 {
00221     for(std::vector<std::string>::const_iterator itor = items.begin();
00222         itor != items.end(); ++itor) {
00223 
00224         if(itor->empty() == false && (*itor)[0] == HEADING_PREFIX) {
00225             heading_ = utils::quoted_split(itor->substr(1),COLUMN_SEPARATOR, !strip_spaces);
00226             continue;
00227         }
00228 
00229         const size_t id = items_.size();
00230         item_pos_.push_back(id);
00231         const item new_item(utils::quoted_split(*itor, COLUMN_SEPARATOR, !strip_spaces),id);
00232         items_.push_back(new_item);
00233 
00234         //make sure there is always at least one item
00235         if(items_.back().fields.empty()) {
00236             items_.back().fields.push_back(" ");
00237         }
00238 
00239         //if the first character in an item is an asterisk,
00240         //it means this item should be selected by default
00241         std::string& first_item = items_.back().fields.front();
00242         if(first_item.empty() == false && first_item[0] == DEFAULT_ITEM) {
00243             selected_ = id;
00244             first_item.erase(first_item.begin());
00245         }
00246     }
00247 
00248     create_help_strings();
00249 
00250     if(sortby_ >= 0) {
00251         do_sort();
00252     }
00253     update_size();
00254 }
00255 
00256 namespace {
00257 
00258 class sort_func
00259 {
00260 public:
00261     sort_func(const menu::sorter& pred, int column) : pred_(&pred), column_(column)
00262     {}
00263 
00264     bool operator()(const menu::item& a, const menu::item& b) const
00265     {
00266         return pred_->less(column_,a,b);
00267     }
00268 
00269 private:
00270     const menu::sorter* pred_;
00271     int column_;
00272 };
00273 
00274 }
00275 
00276 void menu::do_sort()
00277 {
00278     if(sorter_ == NULL || sorter_->column_sortable(sortby_) == false) {
00279         return;
00280     }
00281 
00282     const int selectid = selection();
00283 
00284     std::stable_sort(items_.begin(), items_.end(), sort_func(*sorter_, sortby_));
00285     if (sortreversed_)
00286         std::reverse(items_.begin(), items_.end());
00287 
00288     recalculate_pos();
00289 
00290     if(selectid >= 0 && selectid < int(item_pos_.size())) {
00291         move_selection_to(selectid, true, NO_MOVE_VIEWPORT);
00292     }
00293 
00294     set_dirty();
00295 }
00296 
00297 void menu::recalculate_pos()
00298 {
00299     size_t sz = items_.size();
00300     item_pos_.resize(sz);
00301     for(size_t i = 0; i != sz; ++i)
00302         item_pos_[items_[i].id] = i;
00303     assert_pos();
00304 }
00305 
00306 void menu::assert_pos()
00307 {
00308     size_t sz = items_.size();
00309     assert(item_pos_.size() == sz);
00310     for(size_t n = 0; n != sz; ++n) {
00311         assert(item_pos_[n] < sz && n == items_[item_pos_[n]].id);
00312     }
00313 }
00314 
00315 void menu::create_help_strings()
00316 {
00317     for(std::vector<item>::iterator i = items_.begin(); i != items_.end(); ++i) {
00318         i->help.clear();
00319         for(std::vector<std::string>::iterator j = i->fields.begin(); j != i->fields.end(); ++j) {
00320             if(std::find(j->begin(),j->end(),static_cast<char>(HELP_STRING_SEPARATOR)) == j->end()) {
00321                 i->help.push_back("");
00322             } else {
00323                 const std::vector<std::string>& items = utils::split(*j, HELP_STRING_SEPARATOR, 0);
00324                 if(items.size() >= 2) {
00325                     *j = items.front();
00326                     i->help.push_back(items.back());
00327                 } else {
00328                     i->help.push_back("");
00329                 }
00330             }
00331         }
00332     }
00333 }
00334 
00335 void menu::update_scrollbar_grip_height()
00336 {
00337     set_full_size(items_.size());
00338     set_shown_size(max_items_onscreen());
00339 }
00340 
00341 void menu::update_size()
00342 {
00343     unsigned int h = heading_height();
00344     for(size_t i = get_position(),
00345         i_end = std::min(items_.size(), i + max_items_onscreen());
00346         i < i_end; ++i)
00347         h += get_item_rect(i).h;
00348     h = std::max(h, height());
00349     if (max_height_ > 0 && h > static_cast<unsigned>(max_height_)) {
00350         h = max_height_;
00351     }
00352 
00353     use_ellipsis_ = false;
00354     std::vector<int> const &widths = column_widths();
00355     unsigned w = std::accumulate(widths.begin(), widths.end(), 0);
00356     if (items_.size() > max_items_onscreen())
00357         w += scrollbar_width();
00358     w = std::max(w, width());
00359     if (max_width_ > 0 && w > static_cast<unsigned>(max_width_)) {
00360         use_ellipsis_ = true;
00361         w = max_width_;
00362     }
00363 
00364     update_scrollbar_grip_height();
00365     set_measurements(w, h);
00366 }
00367 
00368 int menu::selection() const
00369 {
00370     if (selected_ >= items_.size()) {
00371         return -1;
00372     }
00373 
00374     return items_[selected_].id;
00375 }
00376 
00377 void menu::set_inner_location(SDL_Rect const &rect)
00378 {
00379     itemRects_.clear();
00380     update_scrollbar_grip_height();
00381     bg_register(rect);
00382 }
00383 
00384 void menu::change_item(int pos1, int pos2,const std::string& str)
00385 {
00386     if(pos1 < 0 || pos1 >= int(item_pos_.size()) ||
00387         pos2 < 0 || pos2 >= int(items_[item_pos_[pos1]].fields.size())) {
00388         return;
00389     }
00390 
00391     items_[item_pos_[pos1]].fields[pos2] = str;
00392     set_dirty();
00393 }
00394 
00395 void menu::erase_item(size_t index)
00396 {
00397     size_t nb_items = items_.size();
00398     if (index >= nb_items)
00399         return;
00400     --nb_items;
00401 
00402     clear_item(nb_items);
00403 
00404     // fix ordered positions of items
00405     size_t pos = item_pos_[index];
00406     item_pos_.erase(item_pos_.begin() + index);
00407     items_.erase(items_.begin() + pos);
00408     for(size_t i = 0; i != nb_items; ++i) {
00409         size_t &n1 = item_pos_[i], &n2 = items_[i].id;
00410         if (n1 > pos) --n1;
00411         if (n2 > index) --n2;
00412     }
00413     assert_pos();
00414 
00415     if (selected_ >= nb_items)
00416         selected_ = nb_items - 1;
00417 
00418     update_scrollbar_grip_height();
00419     adjust_viewport_to_selection();
00420     itemRects_.clear();
00421     set_dirty();
00422 }
00423 
00424 void menu::set_heading(const std::vector<std::string>& heading)
00425 {
00426     itemRects_.clear();
00427     column_widths_.clear();
00428 
00429     heading_ = heading;
00430     max_items_ = -1;
00431 
00432     set_dirty();
00433 }
00434 
00435 void menu::set_items(const std::vector<std::string>& items, bool strip_spaces, bool keep_viewport)
00436 {
00437 
00438     const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
00439     items_.clear();
00440     item_pos_.clear();
00441     itemRects_.clear();
00442     column_widths_.clear();
00443     //undrawn_items_.clear();
00444     max_items_ = -1; // Force recalculation of the max items.
00445     item_height_ = -1; // Force recalculation of the item height.
00446 
00447     if (!keep_viewport || selected_ >= items.size()) {
00448         selected_ = 0;
00449     }
00450 
00451     fill_items(items, strip_spaces);
00452     if(!keep_viewport) {
00453         set_position(0);
00454     } else if(scrolled_to_max) {
00455         set_position(get_max_position());
00456     }
00457 
00458     update_scrollbar_grip_height();
00459 
00460     if(!keep_viewport) {
00461         adjust_viewport_to_selection();
00462     }
00463     set_dirty();
00464 }
00465 
00466 void menu::set_max_height(const int new_max_height)
00467 {
00468     max_height_ = new_max_height;
00469     itemRects_.clear();
00470     max_items_ = -1;
00471     update_size();
00472 }
00473 
00474 void menu::set_max_width(const int new_max_width)
00475 {
00476     max_width_ = new_max_width;
00477     itemRects_.clear();
00478     column_widths_.clear();
00479     update_size();
00480 }
00481 
00482 size_t menu::max_items_onscreen() const
00483 {
00484     if(max_items_ != -1) {
00485         return size_t(max_items_);
00486     }
00487 
00488     const size_t max_height = (max_height_ == -1 ? (video().gety()*66)/100 : max_height_) - heading_height();
00489 
00490     std::vector<int> heights;
00491     size_t n;
00492     for(n = 0; n != items_.size(); ++n) {
00493         heights.push_back(get_item_height(n));
00494     }
00495 
00496     std::sort(heights.begin(),heights.end(),std::greater<int>());
00497     size_t sum = 0;
00498     for(n = 0; n != items_.size() && sum < max_height; ++n) {
00499         sum += heights[n];
00500     }
00501 
00502     if(sum > max_height && n > 1)
00503         --n;
00504 
00505     return max_items_ = n;
00506 }
00507 
00508 void menu::adjust_viewport_to_selection()
00509 {
00510     if(click_selects_)
00511         return;
00512     adjust_position(selected_);
00513 }
00514 
00515 void menu::set_selection_pos(size_t new_selected, bool silent, SELECTION_MOVE_VIEWPORT move_viewport)
00516 {
00517     if (new_selected >= items_.size())
00518         return;
00519 
00520     bool changed = false;
00521     if (new_selected != selected_) {
00522         invalidate_row_pos(selected_);
00523         invalidate_row_pos(new_selected);
00524         selected_ = new_selected;
00525         changed = true;
00526     }
00527 
00528     if(move_viewport == MOVE_VIEWPORT) {
00529         adjust_viewport_to_selection();
00530         if(!silent_ && !silent && changed) {
00531             sound::play_UI_sound(game_config::sounds::menu_select);
00532         }
00533     }
00534 }
00535 
00536 void menu::move_selection_up(size_t dep)
00537 {
00538     set_selection_pos(selected_ > dep ? selected_ - dep : 0);
00539 }
00540 
00541 void menu::move_selection_down(size_t dep)
00542 {
00543     size_t nb_items = items_.size();
00544     set_selection_pos(selected_ + dep >= nb_items ? nb_items - 1 : selected_ + dep);
00545 }
00546 
00547 // private function with control over sound and viewport
00548 void menu::move_selection_to(size_t id, bool silent, SELECTION_MOVE_VIEWPORT move_viewport)
00549 {
00550     if(id < item_pos_.size()) {
00551         set_selection_pos(item_pos_[id], silent, move_viewport);
00552     }
00553 }
00554 
00555 // public function
00556 void menu::move_selection(size_t id)
00557 {
00558     if(id < item_pos_.size()) {
00559         set_selection_pos(item_pos_[id], true, MOVE_VIEWPORT);
00560     }
00561 }
00562 
00563 // public function
00564 void menu::move_selection_keeping_viewport(size_t id)
00565 {
00566     if(id < item_pos_.size()) {
00567         set_selection_pos(item_pos_[id], true, NO_MOVE_VIEWPORT);
00568     }
00569 }
00570 
00571 void menu::reset_selection()
00572 {
00573     set_selection_pos(0);
00574 }
00575 
00576 void menu::key_press(SDLKey key)
00577 {
00578     if (!click_selects_) {
00579         switch(key) {
00580         case SDLK_UP:
00581             move_selection_up(1);
00582             break;
00583         case SDLK_DOWN:
00584             move_selection_down(1);
00585             break;
00586         case SDLK_PAGEUP:
00587             move_selection_up(max_items_onscreen());
00588             break;
00589         case SDLK_PAGEDOWN:
00590             move_selection_down(max_items_onscreen());
00591             break;
00592         case SDLK_HOME:
00593             set_selection_pos(0);
00594             break;
00595         case SDLK_END:
00596             set_selection_pos(items_.size() - 1);
00597             break;
00598         //case SDLK_RETURN:
00599         //  double_clicked_ = true;
00600         //  break;
00601         default:
00602             break;
00603         }
00604     }
00605 
00606     if (num_selects_ && key >= SDLK_1 && key <= SDLK_9)
00607         set_selection_pos(key - SDLK_1);
00608 }
00609 
00610 bool menu::requires_event_focus(const SDL_Event* event) const
00611 {
00612     if(!focus_ || height() == 0 || hidden()) {
00613         return false;
00614     }
00615     if(event == NULL) {
00616         //when event is not specified, signal that focus may be desired later
00617         return true;
00618     }
00619 
00620     if(event->type == SDL_KEYDOWN) {
00621         SDLKey key = event->key.keysym.sym;
00622         if (!click_selects_) {
00623             switch(key) {
00624             case SDLK_UP:
00625             case SDLK_DOWN:
00626             case SDLK_PAGEUP:
00627             case SDLK_PAGEDOWN:
00628             case SDLK_HOME:
00629             case SDLK_END:
00630                 return true;
00631             default:
00632                 break;
00633             }
00634         }
00635         if (num_selects_ && key >= SDLK_1 && key <= SDLK_9) {
00636             return true;
00637         }
00638     }
00639     //mouse events are processed regardless of focus
00640     return false;
00641 }
00642 
00643 void menu::handle_event(const SDL_Event& event)
00644 {
00645     scrollarea::handle_event(event);
00646     if (height()==0 || hidden())
00647         return;
00648 
00649     if(event.type == SDL_KEYDOWN) {
00650         // Only pass key events if we have the focus
00651         if (focus(&event))
00652             key_press(event.key.keysym.sym);
00653     } else if(!mouse_locked() && ((event.type == SDL_MOUSEBUTTONDOWN &&
00654              (event.button.button == SDL_BUTTON_LEFT || event.button.button == SDL_BUTTON_RIGHT)) ||
00655              event.type == DOUBLE_CLICK_EVENT)) {
00656 
00657         int x = 0;
00658         int y = 0;
00659         if(event.type == SDL_MOUSEBUTTONDOWN) {
00660             x = event.button.x;
00661             y = event.button.y;
00662         } else {
00663             x = reinterpret_cast<long>(event.user.data1);
00664             y = reinterpret_cast<long>(event.user.data2);
00665         }
00666 
00667         const int item = hit(x,y);
00668         if(item != -1) {
00669             set_focus(true);
00670             move_selection_to(item);
00671 
00672             if(click_selects_) {
00673                 show_result_ = true;
00674             }
00675 
00676             if(event.type == DOUBLE_CLICK_EVENT) {
00677                 if (ignore_next_doubleclick_) {
00678                     ignore_next_doubleclick_ = false;
00679                 } else {
00680                     double_clicked_ = true;
00681                     last_was_doubleclick_ = true;
00682                     if(!silent_) {
00683                         sound::play_UI_sound(game_config::sounds::button_press);
00684                     }
00685                 }
00686             } else if (last_was_doubleclick_) {
00687                 // If we have a double click as the next event, it means
00688                 // this double click was generated from a click that
00689                 // already has helped in generating a double click.
00690                 SDL_Event ev;
00691                 SDL_PeepEvents(&ev, 1, SDL_PEEKEVENT,
00692                                SDL_EVENTMASK(DOUBLE_CLICK_EVENT));
00693                 if (ev.type == DOUBLE_CLICK_EVENT) {
00694                     ignore_next_doubleclick_ = true;
00695                 }
00696                 last_was_doubleclick_ = false;
00697             }
00698         }
00699 
00700 
00701         if(sorter_ != NULL) {
00702             const int heading = hit_heading(x,y);
00703             if(heading >= 0 && sorter_->column_sortable(heading)) {
00704                 sort_by(heading);
00705             }
00706         }
00707     } else if(!mouse_locked() && event.type == SDL_MOUSEMOTION) {
00708         if(click_selects_) {
00709             const int item = hit(event.motion.x,event.motion.y);
00710             const bool out = (item == -1);
00711             if (out_ != out) {
00712                     out_ = out;
00713                     invalidate_row_pos(selected_);
00714             }
00715             if (item != -1) {
00716                 move_selection_to(item);
00717             }
00718         }
00719 
00720         const int heading_item = hit_heading(event.motion.x,event.motion.y);
00721         if(heading_item != highlight_heading_) {
00722             highlight_heading_ = heading_item;
00723             invalidate_heading();
00724         }
00725     }
00726 }
00727 
00728 int menu::process()
00729 {
00730     if(show_result_) {
00731         show_result_ = false;
00732         return selected_;
00733     } else {
00734         return -1;
00735     }
00736 }
00737 
00738 bool menu::double_clicked()
00739 {
00740     bool old = double_clicked_;
00741     double_clicked_ = false;
00742     return old;
00743 }
00744 
00745 void menu::set_click_selects(bool value)
00746 {
00747     click_selects_ = value;
00748 }
00749 
00750 void menu::set_numeric_keypress_selection(bool value)
00751 {
00752     num_selects_ = value;
00753 }
00754 
00755 void menu::scroll(unsigned int)
00756 {
00757     itemRects_.clear();
00758     set_dirty();
00759 }
00760 
00761 void menu::set_sorter(sorter *s)
00762 {
00763     if(sortby_ >= 0) {
00764         //clear an existing sort
00765         sort_by(-1);
00766     }
00767     sorter_ = s;
00768     sortreversed_ = false;
00769     sortby_ = -1;
00770 }
00771 
00772 void menu::sort_by(int column)
00773 {
00774     const bool already_sorted = (column == sortby_);
00775 
00776     if(already_sorted) {
00777         if(sortreversed_ == false) {
00778             sortreversed_ = true;
00779         } else {
00780             sortreversed_ = false;
00781             sortby_ = -1;
00782         }
00783     } else {
00784         sortby_ = column;
00785         sortreversed_ = false;
00786     }
00787 
00788     do_sort();
00789     itemRects_.clear();
00790     set_dirty();
00791 }
00792 
00793 SDL_Rect menu::style::item_size(const std::string& item) const {
00794     SDL_Rect res = {0,0,0,0};
00795     std::vector<std::string> img_text_items = utils::split(item, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
00796     for (std::vector<std::string>::const_iterator it = img_text_items.begin();
00797          it != img_text_items.end(); ++it) {
00798         if (res.w > 0 || res.h > 0) {
00799             // Not the first item, add the spacing.
00800             res.w += 5;
00801         }
00802         const std::string str = *it;
00803         if (!str.empty() && str[0] == IMAGE_PREFIX) {
00804             const std::string image_name(str.begin()+1,str.end());
00805             surface const img = get_item_image(image_name);
00806             if(img != NULL) {
00807                 res.w += img->w;
00808                 res.h = std::max<int>(img->h, res.h);
00809             }
00810         } else {
00811             const SDL_Rect area = {0,0,10000,10000};
00812             const SDL_Rect font_size =
00813                 font::draw_text(NULL,area,get_font_size(),font::NORMAL_COLOR,str,0,0);
00814             res.w += font_size.w;
00815             res.h = std::max<int>(font_size.h, res.h);
00816         }
00817     }
00818     return res;
00819 }
00820 
00821 void menu::style::draw_row_bg(menu& menu_ref, const size_t /*row_index*/, const SDL_Rect& rect, ROW_TYPE type)
00822 {
00823     menu_ref.bg_restore(rect);
00824 
00825     int rgb = 0;
00826     double alpha = 0.0;
00827 
00828     switch(type) {
00829     case NORMAL_ROW:
00830         rgb = normal_rgb_;
00831         alpha = normal_alpha_;
00832         break;
00833     case SELECTED_ROW:
00834         rgb = selected_rgb_;
00835         alpha = selected_alpha_;
00836         break;
00837     case HEADING_ROW:
00838         rgb = heading_rgb_;
00839         alpha = heading_alpha_;
00840         break;
00841     }
00842 
00843     draw_solid_tinted_rectangle(rect.x, rect.y, rect.w, rect.h,
00844                     (rgb&0xff0000) >> 16,(rgb&0xff00) >> 8,rgb&0xff,alpha,
00845                     menu_ref.video().getSurface());
00846 }
00847 
00848 void menu::style::draw_row(menu& menu_ref, const size_t row_index, const SDL_Rect& rect, ROW_TYPE type)
00849 {
00850     if(rect.w == 0 || rect.h == 0) {
00851         return;
00852     }
00853     draw_row_bg(menu_ref, row_index, rect, type);
00854 
00855     SDL_Rect minirect = rect;
00856     if(type != HEADING_ROW) {
00857         minirect.x += thickness_;
00858         minirect.y += thickness_;
00859         minirect.w -= 2*thickness_;
00860         minirect.h -= 2*thickness_;
00861     }
00862     menu_ref.draw_row(row_index, minirect, type);
00863 }
00864 
00865 
00866 
00867 void menu::column_widths_item(const std::vector<std::string>& row, std::vector<int>& widths) const
00868 {
00869     for(size_t col = 0; col != row.size(); ++col) {
00870         const SDL_Rect res = style_->item_size(row[col]);
00871         size_t text_trailing_space = (item_ends_with_image(row[col])) ? 0 : style_->get_cell_padding();
00872 
00873         if(col == widths.size()) {
00874             widths.push_back(res.w + text_trailing_space);
00875         } else if(res.w > widths[col] - text_trailing_space) {
00876             widths[col] = res.w + text_trailing_space;
00877         }
00878     }
00879 }
00880 
00881 bool menu::item_ends_with_image(const std::string& item) const
00882 {
00883     std::string::size_type pos = item.find_last_of(IMG_TEXT_SEPARATOR);
00884     pos = (pos == std::string::npos) ? 0 : pos+1;
00885     return(item.size() > pos && item.at(pos) == IMAGE_PREFIX);
00886 }
00887 
00888 const std::vector<int>& menu::column_widths() const
00889 {
00890     if(column_widths_.empty()) {
00891         column_widths_item(heading_,column_widths_);
00892         for(size_t row = 0; row != items_.size(); ++row) {
00893             column_widths_item(items_[row].fields,column_widths_);
00894         }
00895     }
00896 
00897     return column_widths_;
00898 }
00899 
00900 void menu::clear_item(int item)
00901 {
00902     SDL_Rect rect = get_item_rect(item);
00903     if (rect.w == 0)
00904         return;
00905     bg_restore(rect);
00906 }
00907 
00908 void menu::draw_row(const size_t row_index, const SDL_Rect& rect, ROW_TYPE type)
00909 {
00910     //called from style, draws one row's contents in a generic and adaptable way
00911     const std::vector<std::string>& row = (type == HEADING_ROW) ? heading_ : items_[row_index].fields;
00912     SDL_Rect const &area = screen_area();
00913     SDL_Rect const &loc = inner_location();
00914     const std::vector<int>& widths = column_widths();
00915     bool lang_rtl = current_language_rtl();
00916     int dir = (lang_rtl) ? -1 : 1;
00917     SDL_Rect column = loc;
00918 
00919     int xpos = rect.x;
00920     if(lang_rtl)
00921         xpos += rect.w;
00922     for(size_t i = 0; i != row.size(); ++i) {
00923 
00924         if(lang_rtl)
00925             xpos -= widths[i];
00926         if(type == HEADING_ROW && highlight_heading_ == int(i)) {
00927             draw_solid_tinted_rectangle(xpos,rect.y,widths[i],rect.h,255,255,255,0.3,video().getSurface());
00928         }
00929 
00930         const int last_x = xpos;
00931         column.w = widths[i];
00932         std::string str = row[i];
00933         std::vector<std::string> img_text_items = utils::split(str, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
00934         for (std::vector<std::string>::const_iterator it = img_text_items.begin();
00935              it != img_text_items.end(); ++it) {
00936             str = *it;
00937             if (!str.empty() && str[0] == IMAGE_PREFIX) {
00938                 const std::string image_name(str.begin()+1,str.end());
00939                 const surface img = style_->get_item_image(image_name);
00940                 const int remaining_width = max_width_ < 0 ? area.w :
00941                 std::min<int>(max_width_, ((lang_rtl)? xpos - rect.x : rect.x + rect.w - xpos));
00942                 if(img != NULL && img->w <= remaining_width
00943                 && rect.y + img->h < area.h) {
00944                     const size_t y = rect.y + (rect.h - img->h)/2;
00945                     const size_t w = img->w + 5;
00946                     const size_t x = xpos + ((lang_rtl) ? widths[i] - w : 0);
00947                     video().blit_surface(x,y,img);
00948                     if(!lang_rtl)
00949                         xpos += w;
00950                     column.w -= w;
00951                 }
00952             } else {
00953                 column.x = xpos;
00954                 const bool has_wrap = (str.find_first_of("\r\n") != std::string::npos);
00955                 //prevent ellipsis calculation if there is any line wrapping
00956                 std::string to_show = str;
00957                 if (use_ellipsis_ && !has_wrap)
00958                 {
00959                     int fs = style_->get_font_size();
00960                     int style = TTF_STYLE_NORMAL;
00961                     int w = loc.w - (xpos - rect.x) - 2 * style_->get_thickness();
00962                     std::string::const_iterator i_beg = to_show.begin(), i_end = to_show.end(),
00963                         i = font::parse_markup(i_beg, i_end, &fs, NULL, &style);
00964                     if (i != i_end) {
00965                         std::string tmp(i, i_end);
00966                         to_show.erase(i - i_beg, i_end - i_beg);
00967                         to_show += font::make_text_ellipsis(tmp, fs, w, style);
00968                     }
00969                 }
00970                 const SDL_Rect& text_size = font::text_area(str,style_->get_font_size());
00971                 const size_t y = rect.y + (rect.h - text_size.h)/2;
00972                 font::draw_text(&video(),column,style_->get_font_size(),font::NORMAL_COLOR,to_show,xpos,y);
00973 
00974                 if(type == HEADING_ROW && sortby_ == int(i)) {
00975                     const surface sort_img = image::get_image(sortreversed_ ? "misc/sort-arrow.png" :
00976                                                        "misc/sort-arrow-reverse.png");
00977                     if(sort_img != NULL && sort_img->w <= widths[i] && sort_img->h <= rect.h) {
00978                         const size_t sort_x = xpos + widths[i] - sort_img->w;
00979                         const size_t sort_y = rect.y + rect.h/2 - sort_img->h/2;
00980                         video().blit_surface(sort_x,sort_y,sort_img);
00981                     }
00982                 }
00983 
00984                 xpos += dir * (text_size.w + 5);
00985             }
00986         }
00987         if(lang_rtl)
00988             xpos = last_x;
00989         else
00990             xpos = last_x + widths[i];
00991     }
00992 }
00993 
00994 void menu::draw_contents()
00995 {
00996     SDL_Rect heading_rect = inner_location();
00997     heading_rect.h = heading_height();
00998     style_->draw_row(*this,0,heading_rect,HEADING_ROW);
00999 
01000     for(size_t i = 0; i != item_pos_.size(); ++i) {
01001         style_->draw_row(*this,item_pos_[i],get_item_rect(i),
01002              (!out_ && item_pos_[i] == selected_) ? SELECTED_ROW : NORMAL_ROW);
01003     }
01004 }
01005 
01006 void menu::draw()
01007 {
01008     if(hidden()) {
01009         return;
01010     }
01011 
01012     if(!dirty()) {
01013 
01014         for(std::set<int>::const_iterator i = invalid_.begin(); i != invalid_.end(); ++i) {
01015             if(*i == -1) {
01016                 SDL_Rect heading_rect = inner_location();
01017                 heading_rect.h = heading_height();
01018                 bg_restore(heading_rect);
01019                 style_->draw_row(*this,0,heading_rect,HEADING_ROW);
01020                 update_rect(heading_rect);
01021             } else if(*i >= 0 && *i < int(item_pos_.size())) {
01022                 const unsigned int pos = item_pos_[*i];
01023                 const SDL_Rect& rect = get_item_rect(*i);
01024                 bg_restore(rect);
01025                 style_->draw_row(*this,pos,rect,
01026                     (!out_ && pos == selected_) ? SELECTED_ROW : NORMAL_ROW);
01027                 update_rect(rect);
01028             }
01029         }
01030 
01031         invalid_.clear();
01032         return;
01033     }
01034 
01035     invalid_.clear();
01036 
01037     bg_restore();
01038 
01039     clip_rect_setter clipping_rect =
01040             clip_rect_setter(video().getSurface(), clip_rect(), clip_rect() != NULL);
01041 
01042     draw_contents();
01043 
01044     update_rect(location());
01045     set_dirty(false);
01046 }
01047 
01048 int menu::hit(int x, int y) const
01049 {
01050     SDL_Rect const &loc = inner_location();
01051     if (x >= loc.x  && x < loc.x + loc.w && y >= loc.y && y < loc.y + loc.h) {
01052         for(size_t i = 0; i != items_.size(); ++i) {
01053             const SDL_Rect& rect = get_item_rect(i);
01054             if (y >= rect.y && y < rect.y + rect.h)
01055                 return i;
01056         }
01057     }
01058 
01059     return -1;
01060 }
01061 
01062 int menu::hit_column(int x) const
01063 {
01064     std::vector<int> const &widths = column_widths();
01065     int j = -1, j_end = widths.size();
01066     for(x -= location().x; x >= 0; x -= widths[j]) {
01067         if(++j == j_end) {
01068             return -1;
01069         }
01070     }
01071     return j;
01072 }
01073 
01074 std::pair<int,int> menu::hit_cell(int x, int y) const
01075 {
01076     const int row = hit(x, y);
01077     if(row < 0) {
01078         return std::pair<int,int>(-1, -1);
01079     }
01080 
01081     const int col = hit_column(x);
01082     if(col < 0) {
01083         return std::pair<int,int>(-1, -1);
01084     }
01085 
01086     return std::pair<int,int>(x,y);
01087 }
01088 
01089 int menu::hit_heading(int x, int y) const
01090 {
01091     const size_t height = heading_height();
01092     const SDL_Rect& loc = inner_location();
01093     if(y >= loc.y && static_cast<size_t>(y) < loc.y + height) {
01094         return hit_column(x);
01095     } else {
01096         return -1;
01097     }
01098 }
01099 
01100 SDL_Rect menu::get_item_rect(int item) const
01101 {
01102     return get_item_rect_internal(item_pos_[item]);
01103 }
01104 
01105 SDL_Rect menu::get_item_rect_internal(size_t item) const
01106 {
01107     unsigned int first_item_on_screen = get_position();
01108     if (item < first_item_on_screen ||
01109         size_t(item) >= first_item_on_screen + max_items_onscreen()) {
01110         return empty_rect;
01111     }
01112 
01113     const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
01114     if(i != itemRects_.end())
01115         return i->second;
01116 
01117     SDL_Rect const &loc = inner_location();
01118 
01119     int y = loc.y + heading_height();
01120     if (item != first_item_on_screen) {
01121         const SDL_Rect& prev = get_item_rect_internal(item-1);
01122         y = prev.y + prev.h;
01123     }
01124 
01125     SDL_Rect res = create_rect(loc.x, y, loc.w, get_item_height(item));
01126 
01127     SDL_Rect const &screen_area = ::screen_area();
01128 
01129     if(res.x > screen_area.w) {
01130         return empty_rect;
01131     } else if(res.x + res.w > screen_area.w) {
01132         res.w = screen_area.w - res.x;
01133     }
01134 
01135     if(res.y > screen_area.h) {
01136         return empty_rect;
01137     } else if(res.y + res.h > screen_area.h) {
01138         res.h = screen_area.h - res.y;
01139     }
01140 
01141     //only insert into the cache if the menu's co-ordinates have
01142     //been initialized
01143     if (loc.x > 0 && loc.y > 0)
01144         itemRects_.insert(std::pair<int,SDL_Rect>(item,res));
01145 
01146     return res;
01147 }
01148 
01149 size_t menu::get_item_height_internal(const std::vector<std::string>& item) const
01150 {
01151     size_t res = 0;
01152     for(std::vector<std::string>::const_iterator i = item.begin(); i != item.end(); ++i) {
01153         SDL_Rect rect = style_->item_size(*i);
01154         res = std::max<int>(rect.h,res);
01155     }
01156 
01157     return res;
01158 }
01159 
01160 size_t menu::heading_height() const
01161 {
01162     if(heading_height_ == -1) {
01163         heading_height_ = int(get_item_height_internal(heading_));
01164     }
01165 
01166     return std::min<unsigned int>(heading_height_,max_height_);
01167 }
01168 
01169 size_t menu::get_item_height(int) const
01170 {
01171     if(item_height_ != -1)
01172         return size_t(item_height_);
01173 
01174     size_t max_height = 0;
01175     for(size_t n = 0; n != items_.size(); ++n) {
01176         max_height = std::max<int>(max_height,get_item_height_internal(items_[n].fields));
01177     }
01178 
01179     return item_height_ = max_height;
01180 }
01181 
01182 void menu::process_help_string(int mousex, int mousey)
01183 {
01184     const std::pair<int,int> loc(hit(mousex,mousey), hit_column(mousex));
01185     if(loc == cur_help_) {
01186         return;
01187     } else if(loc.first == -1) {
01188         video().clear_help_string(help_string_);
01189         help_string_ = -1;
01190     } else {
01191         if(help_string_ != -1) {
01192             video().clear_help_string(help_string_);
01193             help_string_ = -1;
01194         }
01195         if(size_t(loc.first) < items_.size()) {
01196             const std::vector<std::string>& row = items_[loc.first].help;
01197             if(size_t(loc.second) < row.size()) {
01198                 const std::string& help = row[loc.second];
01199                 if(help.empty() == false) {
01200                     //std::cerr << "setting help string from menu to '" << help << "'\n";
01201                     help_string_ = video().set_help_string(help);
01202                 }
01203             }
01204         }
01205     }
01206 
01207     cur_help_ = loc;
01208 }
01209 
01210 void menu::invalidate_row(size_t id)
01211 {
01212     if(id >= items_.size()) {
01213         return;
01214     }
01215 
01216     invalid_.insert(int(id));
01217 }
01218 
01219 void menu::invalidate_row_pos(size_t pos)
01220 {
01221     if(pos >= items_.size()) {
01222         return;
01223     }
01224 
01225     invalidate_row(items_[pos].id);
01226 }
01227 
01228 void menu::invalidate_heading()
01229 {
01230     invalid_.insert(-1);
01231 }
01232 
01233 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:15 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs