menu.cpp

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

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs