00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
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
00191 if(items_.back().fields.empty()) {
00192 items_.back().fields.push_back(" ");
00193 }
00194
00195
00196
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
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
00401 max_items_ = -1;
00402 item_height_ = -1;
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
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
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
00552
00553
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
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
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
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
00641
00642
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
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
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 , 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
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
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;
01019 item_height_ = -1;
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
01117
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
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 }