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