widgets/textbox.cpp

Go to the documentation of this file.
00001 /* $Id: textbox.cpp 52533 2012-01-07 02:35:17Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
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     // static const SDL_Rect area = d.screen_area();
00042     // const int height = font::draw_text(NULL,area,font_size,font::NORMAL_COLOR,"ABCD",0,0).h;
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 // set_text does not respect max_size_
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     //disallow adding multi-line text to a single-line text box
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         // Fills the selected area
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     // Re-calculate the position of each glyph. We approximate this by asking the
00248     // width of each substring, but this is a flawed assumption which won't work with
00249     // some more complex scripts (that is, RTL languages). This part of the work should
00250     // actually be done by the font-rendering system.
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         //If this is a space, save copies of the current state so we can roll back
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         //when event is not specified, signal that focus may be desired later
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             //in the future we may need to check for input history or multi-line support
00369             //for now, just return false since these events are not handled.
00370             return false;
00371         default:
00372             return true;
00373         }
00374     }
00375     //mouse events are processed regardless of focus
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     //Sanity check: verify that selection start and end are within text
00391     //boundaries
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             // Check individually each distance (if, one day, we support
00422             // RTL languages, char_x_[c] may not be monotonous.)
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     //if we don't have the focus, then see if we gain the focus,
00451     //otherwise return
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     // ctrl-a, ctrl-e and ctrl-u are readline style shortcuts, even on Macs
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)) { // clear line
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     //movement characters may have a "Unicode" field on some platforms, so ignore it.
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 //on windows SDL fires for AltGr lctrl+ralt (needed to access @ etc on certain keyboards)
00527 #ifdef _WIN32
00528             && !(event.key.keysym.mod & KMOD_ALT)
00529 #endif
00530         ) {
00531             switch(c) {
00532             case SDLK_v: // paste
00533                 {
00534                 changed = true;
00535                 if(is_selection())
00536                     erase_selection();
00537 
00538                 std::string str = copy_from_clipboard(false);
00539 
00540                 //cut off anything after the first newline
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: // copy
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     //since there has been cursor activity, make the cursor appear for
00586     //at least the next 500ms.
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 } //end namespace gui
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:15 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs