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/textbox.hpp"
00021 #include "clipboard.hpp"
00022 #include "log.hpp"
00023 #include "video.hpp"
00024
00025 static lg::log_domain log_display("display");
00026 #define WRN_DP LOG_STREAM(warn, log_display)
00027 #define DBG_G LOG_STREAM(debug, lg::general)
00028
00029 namespace gui {
00030
00031 const int font_size = font::SIZE_PLUS;
00032
00033 textbox::textbox(CVideo &video, int width, const std::string& text, bool editable, size_t max_size, double alpha, double alpha_focus, const bool auto_join)
00034 : scrollarea(video, auto_join), max_size_(max_size), text_(utils::string_to_wstring(text)),
00035 cursor_(text_.size()), selstart_(-1), selend_(-1),
00036 grabmouse_(false), text_pos_(0), editable_(editable),
00037 show_cursor_(true), show_cursor_at_(0), text_image_(NULL),
00038 wrap_(false), line_height_(0), yscroll_(0), alpha_(alpha),
00039 alpha_focus_(alpha_focus)
00040 {
00041
00042
00043 set_measurements(width, font::get_max_height(font_size));
00044 set_scroll_rate(font::get_max_height(font_size) / 2);
00045 update_text_cache(true);
00046 }
00047
00048 textbox::~textbox()
00049 {
00050 }
00051
00052 void textbox::set_inner_location(SDL_Rect const &rect)
00053 {
00054 bg_register(rect);
00055 if (text_image_.null()) return;
00056 text_pos_ = 0;
00057 update_text_cache(false);
00058 }
00059
00060 const std::string textbox::text() const
00061 {
00062 const std::string &ret = utils::wstring_to_string(text_);
00063 return ret;
00064 }
00065
00066
00067 void textbox::set_text(const std::string& text, const SDL_Color& color)
00068 {
00069 text_ = utils::string_to_wstring(text);
00070 cursor_ = text_.size();
00071 text_pos_ = 0;
00072 selstart_ = -1;
00073 selend_ = -1;
00074 set_dirty(true);
00075 update_text_cache(true, color);
00076 handle_text_changed(text_);
00077 }
00078
00079 void textbox::append_text(const std::string& text, bool auto_scroll, const SDL_Color& color)
00080 {
00081 if(text_image_.get() == NULL) {
00082 set_text(text, color);
00083 return;
00084 }
00085
00086
00087 if(wrap_ == false && std::find_if(text.begin(),text.end(),utils::isnewline) != text.end()) {
00088 return;
00089 }
00090 const bool is_at_bottom = get_position() == get_max_position();
00091 const wide_string& wtext = utils::string_to_wstring(text);
00092
00093 const surface new_text = add_text_line(wtext, color);
00094 surface new_surface = create_compatible_surface(text_image_,std::max<size_t>(text_image_->w,new_text->w),text_image_->h+new_text->h);
00095
00096 SDL_SetAlpha(new_text.get(),0,0);
00097 SDL_SetAlpha(text_image_.get(),0,0);
00098
00099 sdl_blit(text_image_,NULL,new_surface,NULL);
00100
00101 SDL_Rect target = create_rect(0
00102 , text_image_->h
00103 , new_text->w
00104 , new_text->h);
00105
00106 sdl_blit(new_text,NULL,new_surface,&target);
00107 text_image_.assign(new_surface);
00108
00109 text_.resize(text_.size() + wtext.size());
00110 std::copy(wtext.begin(),wtext.end(),text_.end()-wtext.size());
00111
00112 set_dirty(true);
00113 update_text_cache(false);
00114 if(auto_scroll && is_at_bottom) scroll_to_bottom();
00115 handle_text_changed(text_);
00116 }
00117
00118 void textbox::clear()
00119 {
00120 text_.clear();
00121 cursor_ = 0;
00122 cursor_pos_ = 0;
00123 text_pos_ = 0;
00124 selstart_ = -1;
00125 selend_ = -1;
00126 set_dirty(true);
00127 update_text_cache(true);
00128 handle_text_changed(text_);
00129 }
00130
00131 void textbox::draw_cursor(int pos, CVideo &video) const
00132 {
00133 if(show_cursor_ && editable_) {
00134 SDL_Rect rect = create_rect(location().x + pos
00135 , location().y
00136 , 1
00137 , location().h);
00138
00139 surface frame_buffer = video.getSurface();
00140 sdl_fill_rect(frame_buffer,&rect,SDL_MapRGB(frame_buffer->format,255,255,255));
00141 }
00142 }
00143
00144 void textbox::draw_contents()
00145 {
00146 SDL_Rect const &loc = inner_location();
00147
00148 surface surf = video().getSurface();
00149 draw_solid_tinted_rectangle(loc.x,loc.y,loc.w,loc.h,0,0,0,
00150 focus(NULL) ? alpha_focus_ : alpha_, surf);
00151
00152 SDL_Rect src;
00153
00154 if(text_image_ == NULL) {
00155 update_text_cache(true);
00156 }
00157
00158 if(text_image_ != NULL) {
00159 src.y = yscroll_;
00160 src.w = std::min<size_t>(loc.w,text_image_->w);
00161 src.h = std::min<size_t>(loc.h,text_image_->h);
00162 src.x = text_pos_;
00163 SDL_Rect dest = screen_area();
00164 dest.x = loc.x;
00165 dest.y = loc.y;
00166
00167
00168 if(is_selection()) {
00169 const int start = std::min<int>(selstart_,selend_);
00170 const int end = std::max<int>(selstart_,selend_);
00171 int startx = char_x_[start];
00172 int starty = char_y_[start];
00173 const int endx = char_x_[end];
00174 const int endy = char_y_[end];
00175
00176 while(starty <= endy) {
00177 const size_t right = starty == endy ? endx : text_image_->w;
00178 if(right <= size_t(startx)) {
00179 break;
00180 }
00181
00182 SDL_Rect rect = create_rect(loc.x + startx
00183 , loc.y + starty - src.y
00184 , right - startx
00185 , line_height_);
00186
00187 const clip_rect_setter clipper(surf, &loc);
00188
00189 Uint32 color = SDL_MapRGB(surf->format, 0, 0, 160);
00190 fill_rect_alpha(rect, color, 140, surf);
00191
00192 starty += int(line_height_);
00193 startx = 0;
00194 }
00195 }
00196
00197 sdl_blit(text_image_, &src, surf, &dest);
00198 }
00199
00200 draw_cursor((cursor_pos_ == 0 ? 0 : cursor_pos_ - 1), video());
00201
00202 update_rect(loc);
00203 }
00204
00205 void textbox::set_editable(bool value)
00206 {
00207 editable_ = value;
00208 }
00209
00210 bool textbox::editable() const
00211 {
00212 return editable_;
00213 }
00214
00215 void textbox::scroll_to_bottom()
00216 {
00217 set_position(get_max_position());
00218 }
00219
00220 void textbox::set_wrap(bool val)
00221 {
00222 if(wrap_ != val) {
00223 wrap_ = val;
00224 update_text_cache(true);
00225 set_dirty(true);
00226 }
00227 }
00228
00229 void textbox::scroll(unsigned int pos)
00230 {
00231 yscroll_ = pos;
00232 set_dirty(true);
00233 }
00234
00235 surface textbox::add_text_line(const wide_string& text, const SDL_Color& color)
00236 {
00237 line_height_ = font::get_max_height(font_size);
00238
00239 if(char_y_.empty()) {
00240 char_y_.push_back(0);
00241 } else {
00242 char_y_.push_back(char_y_.back() + line_height_);
00243 }
00244
00245 char_x_.push_back(0);
00246
00247
00248
00249
00250
00251 std::string visible_string;
00252 wide_string wrapped_text;
00253
00254 wide_string::const_iterator backup_itor = text.end();
00255
00256 wide_string::const_iterator itor = text.begin();
00257 while(itor != text.end()) {
00258
00259 if(char(*itor) == ' ') {
00260 backup_itor = itor;
00261 }
00262 visible_string.append(utils::wchar_to_string(*itor));
00263
00264 if(char(*itor) == '\n') {
00265 backup_itor = text.end();
00266 visible_string = "";
00267 }
00268
00269 int w = font::line_width(visible_string, font_size);
00270
00271 if(wrap_ && w >= inner_location().w) {
00272 if(backup_itor != text.end()) {
00273 int backup = itor - backup_itor;
00274 itor = backup_itor + 1;
00275 if(backup > 0) {
00276 char_x_.erase(char_x_.end()-backup, char_x_.end());
00277 char_y_.erase(char_y_.end()-backup, char_y_.end());
00278 wrapped_text.erase(wrapped_text.end()-backup, wrapped_text.end());
00279 }
00280 }
00281 backup_itor = text.end();
00282 wrapped_text.push_back(wchar_t('\n'));
00283 char_x_.push_back(0);
00284 char_y_.push_back(char_y_.back() + line_height_);
00285 visible_string = "";
00286 } else {
00287 wrapped_text.push_back(*itor);
00288 char_x_.push_back(w);
00289 char_y_.push_back(char_y_.back() + (char(*itor) == '\n' ? line_height_ : 0));
00290 ++itor;
00291 }
00292 }
00293
00294 const std::string s = utils::wstring_to_string(wrapped_text);
00295 const surface res(font::get_rendered_text(s, font_size, color));
00296
00297 return res;
00298 }
00299
00300
00301 void textbox::update_text_cache(bool changed, const SDL_Color& color)
00302 {
00303 if(changed) {
00304 char_x_.clear();
00305 char_y_.clear();
00306
00307 text_image_.assign(add_text_line(text_, color));
00308 }
00309
00310 int cursor_x = char_x_[cursor_];
00311
00312 if(cursor_x - text_pos_ > location().w) {
00313 text_pos_ = cursor_x - location().w;
00314 } else if(cursor_x - text_pos_ < 0) {
00315 text_pos_ = cursor_x;
00316 }
00317 cursor_pos_ = cursor_x - text_pos_;
00318
00319 if (!text_image_.null()) {
00320 set_full_size(text_image_->h);
00321 set_shown_size(location().h);
00322 }
00323 }
00324
00325 bool textbox::is_selection()
00326 {
00327 return (selstart_ != -1) && (selend_ != -1) && (selstart_ != selend_);
00328 }
00329
00330 void textbox::erase_selection()
00331 {
00332 if(!is_selection())
00333 return;
00334
00335 wide_string::iterator itor = text_.begin() + std::min(selstart_, selend_);
00336 text_.erase(itor, itor + abs(selend_ - selstart_));
00337 cursor_ = std::min(selstart_, selend_);
00338 selstart_ = selend_ = -1;
00339 }
00340
00341 namespace {
00342 const unsigned int copypaste_modifier =
00343 #ifdef __APPLE__
00344 KMOD_LMETA | KMOD_RMETA
00345 #else
00346 KMOD_CTRL
00347 #endif
00348 ;
00349 }
00350
00351 bool textbox::requires_event_focus(const SDL_Event* event) const
00352 {
00353 if(!focus_ || !editable_ || hidden()) {
00354 return false;
00355 }
00356 if(event == NULL) {
00357
00358 return true;
00359 }
00360
00361 if(event->type == SDL_KEYDOWN) {
00362 SDLKey key = event->key.keysym.sym;
00363 switch(key) {
00364 case SDLK_UP:
00365 case SDLK_DOWN:
00366 case SDLK_PAGEUP:
00367 case SDLK_PAGEDOWN:
00368
00369
00370 return false;
00371 default:
00372 return true;
00373 }
00374 }
00375
00376 return false;
00377 }
00378
00379 void textbox::handle_event(const SDL_Event& event)
00380 {
00381 scrollarea::handle_event(event);
00382 if(hidden())
00383 return;
00384
00385 bool changed = false;
00386
00387 const int old_selstart = selstart_;
00388 const int old_selend = selend_;
00389
00390
00391
00392 if(is_selection() && !(size_t(selstart_) <= text_.size() && size_t(selend_) <= text_.size())) {
00393 WRN_DP << "out-of-boundary selection\n";
00394 selstart_ = selend_ = -1;
00395 }
00396
00397 int mousex, mousey;
00398 const Uint8 mousebuttons = SDL_GetMouseState(&mousex,&mousey);
00399 if(!(mousebuttons & SDL_BUTTON(1))) {
00400 grabmouse_ = false;
00401 }
00402
00403 SDL_Rect const &loc = inner_location();
00404 bool clicked_inside = !mouse_locked() && (event.type == SDL_MOUSEBUTTONDOWN
00405 && (mousebuttons & SDL_BUTTON(1))
00406 && point_in_rect(mousex, mousey, loc));
00407 if(clicked_inside) {
00408 set_focus(true);
00409 }
00410 if ((grabmouse_ && (!mouse_locked() && event.type == SDL_MOUSEMOTION)) || clicked_inside) {
00411 const int x = mousex - loc.x + text_pos_;
00412 const int y = mousey - loc.y;
00413 int pos = 0;
00414 int distance = x;
00415
00416 for(unsigned int i = 1; i < char_x_.size(); ++i) {
00417 if(static_cast<int>(yscroll_) + y < char_y_[i]) {
00418 break;
00419 }
00420
00421
00422
00423 if(abs(x - char_x_[i]) < distance && yscroll_ + y < char_y_[i] + line_height_) {
00424 pos = i;
00425 distance = abs(x - char_x_[i]);
00426 }
00427 }
00428
00429 cursor_ = pos;
00430
00431 if(grabmouse_)
00432 selend_ = cursor_;
00433
00434 update_text_cache(false);
00435
00436 if(!grabmouse_ && mousebuttons & SDL_BUTTON(1)) {
00437 grabmouse_ = true;
00438 selstart_ = selend_ = cursor_;
00439 } else if (! (mousebuttons & SDL_BUTTON(1))) {
00440 grabmouse_ = false;
00441 }
00442
00443 set_dirty();
00444 }
00445
00446 if(editable_ == false) {
00447 return;
00448 }
00449
00450
00451
00452 if(focus(&event) == false) {
00453 if (!mouse_locked() && event.type == SDL_MOUSEMOTION && point_in_rect(mousex, mousey, loc))
00454 events::focus_handler(this);
00455
00456 return;
00457 }
00458
00459 if(event.type != SDL_KEYDOWN || focus(&event) != true) {
00460 draw();
00461 return;
00462 }
00463
00464 const SDL_keysym& key = reinterpret_cast<const SDL_KeyboardEvent&>(event).keysym;
00465 const SDLMod modifiers = SDL_GetModState();
00466
00467 const int c = key.sym;
00468 const int old_cursor = cursor_;
00469
00470 if(c == SDLK_LEFT && cursor_ > 0)
00471 --cursor_;
00472
00473 if(c == SDLK_RIGHT && cursor_ < static_cast<int>(text_.size()))
00474 ++cursor_;
00475
00476
00477 if(c == SDLK_END || (c == SDLK_e && (modifiers & KMOD_CTRL)))
00478 cursor_ = text_.size();
00479
00480 if(c == SDLK_HOME || (c == SDLK_a && (modifiers & KMOD_CTRL)))
00481 cursor_ = 0;
00482
00483 if((old_cursor != cursor_) && (modifiers & KMOD_SHIFT)) {
00484 if(selstart_ == -1)
00485 selstart_ = old_cursor;
00486 selend_ = cursor_;
00487 }
00488
00489 if(c == SDLK_BACKSPACE) {
00490 changed = true;
00491 if(is_selection()) {
00492 erase_selection();
00493 } else if(cursor_ > 0) {
00494 --cursor_;
00495 text_.erase(text_.begin()+cursor_);
00496 }
00497 }
00498
00499 if(c == SDLK_u && (modifiers & KMOD_CTRL)) {
00500 changed = true;
00501 cursor_ = 0;
00502 text_.resize(0);
00503 }
00504
00505 if(c == SDLK_DELETE && !text_.empty()) {
00506 changed = true;
00507 if(is_selection()) {
00508 erase_selection();
00509 } else {
00510 if(cursor_ < static_cast<int>(text_.size())) {
00511 text_.erase(text_.begin()+cursor_);
00512 }
00513 }
00514 }
00515
00516 wchar_t character = key.unicode;
00517
00518
00519 if(!(c == SDLK_UP || c == SDLK_DOWN || c == SDLK_LEFT || c == SDLK_RIGHT ||
00520 c == SDLK_DELETE || c == SDLK_BACKSPACE || c == SDLK_END || c == SDLK_HOME ||
00521 c == SDLK_PAGEUP || c == SDLK_PAGEDOWN)) {
00522 if(character != 0) {
00523 DBG_G << "Char: " << character << ", c = " << c << "\n";
00524 }
00525 if((event.key.keysym.mod & copypaste_modifier)
00526
00527 #ifdef _WIN32
00528 && !(event.key.keysym.mod & KMOD_ALT)
00529 #endif
00530 ) {
00531 switch(c) {
00532 case SDLK_v:
00533 {
00534 changed = true;
00535 if(is_selection())
00536 erase_selection();
00537
00538 std::string str = copy_from_clipboard(false);
00539
00540
00541 str.erase(std::find_if(str.begin(),str.end(),utils::isnewline),str.end());
00542
00543 wide_string s = utils::string_to_wstring(str);
00544
00545 if(text_.size() < max_size_) {
00546 if(s.size() + text_.size() > max_size_) {
00547 s.resize(max_size_ - text_.size());
00548 }
00549 text_.insert(text_.begin()+cursor_, s.begin(), s.end());
00550 cursor_ += s.size();
00551 }
00552
00553 }
00554
00555 break;
00556
00557 case SDLK_c:
00558 {
00559 const size_t beg = std::min<size_t>(size_t(selstart_),size_t(selend_));
00560 const size_t end = std::max<size_t>(size_t(selstart_),size_t(selend_));
00561
00562 wide_string ws = wide_string(text_.begin() + beg, text_.begin() + end);
00563 std::string s = utils::wstring_to_string(ws);
00564 copy_to_clipboard(s, false);
00565 }
00566 break;
00567 }
00568 } else {
00569 if(character >= 32 && character != 127) {
00570 changed = true;
00571 if(is_selection())
00572 erase_selection();
00573
00574 if(text_.size() + 1 <= max_size_) {
00575 text_.insert(text_.begin()+cursor_,character);
00576 ++cursor_;
00577 }
00578 }
00579 }
00580 }
00581
00582 if(is_selection() && (selend_ != cursor_))
00583 selstart_ = selend_ = -1;
00584
00585
00586
00587 show_cursor_ = true;
00588 show_cursor_at_ = SDL_GetTicks();
00589
00590 if(changed || old_cursor != cursor_ || old_selstart != selstart_ || old_selend != selend_) {
00591 text_image_ = NULL;
00592 handle_text_changed(text_);
00593 }
00594
00595 set_dirty(true);
00596 }
00597
00598 }