00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
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
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
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
00235 if(items_.back().fields.empty()) {
00236 items_.back().fields.push_back(" ");
00237 }
00238
00239
00240
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
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
00444 max_items_ = -1;
00445 item_height_ = -1;
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
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
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
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
00599
00600
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
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
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
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
00688
00689
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
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
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 , 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
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
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
01142
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
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 }