listbox.cpp

Go to the documentation of this file.
00001 /* $Id: listbox.cpp 26706 2008-05-18 17:28:31Z mordante $ */
00002 /*
00003    copyright (C) 2008 by mark de wever <koraq@xs4all.nl>
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 version 2
00008    or at your option any later version.
00009    this program is distributed in the hope that it will be useful,
00010    but without any warranty.
00011 
00012    see the copying file for more details.
00013 */
00014 
00015 #include "gui/widgets/listbox.hpp"
00016 
00017 #include "foreach.hpp"
00018 #include "gui/widgets/button.hpp"
00019 #include "gui/widgets/helper.hpp"
00020 #include "gui/widgets/scrollbar.hpp"
00021 #include "gui/widgets/spacer.hpp"
00022 #include "gui/widgets/toggle_button.hpp"
00023 #include "log.hpp"
00024 
00025 #define DBG_G LOG_STREAM_INDENT(debug, gui)
00026 #define LOG_G LOG_STREAM_INDENT(info, gui)
00027 #define WRN_G LOG_STREAM_INDENT(warn, gui)
00028 #define ERR_G LOG_STREAM_INDENT(err, gui)
00029 
00030 #define DBG_G_D LOG_STREAM_INDENT(debug, gui_draw)
00031 #define LOG_G_D LOG_STREAM_INDENT(info, gui_draw)
00032 #define WRN_G_D LOG_STREAM_INDENT(warn, gui_draw)
00033 #define ERR_G_D LOG_STREAM_INDENT(err, gui_draw)
00034 
00035 #define DBG_G_E LOG_STREAM_INDENT(debug, gui_event)
00036 #define LOG_G_E LOG_STREAM_INDENT(info, gui_event)
00037 #define WRN_G_E LOG_STREAM_INDENT(warn, gui_event)
00038 #define ERR_G_E LOG_STREAM_INDENT(err, gui_event)
00039 
00040 #define DBG_G_P LOG_STREAM_INDENT(debug, gui_parse)
00041 #define LOG_G_P LOG_STREAM_INDENT(info, gui_parse)
00042 #define WRN_G_P LOG_STREAM_INDENT(warn, gui_parse)
00043 #define ERR_G_P LOG_STREAM_INDENT(err, gui_parse)
00044 
00045 namespace gui2 {
00046 
00047 static tlistbox* get_listbox(twidget* widget)
00048 {
00049     do {
00050         widget = widget->parent();
00051 
00052     } while (widget && !dynamic_cast<tlistbox*>(widget));
00053     
00054     tlistbox* listbox = dynamic_cast<tlistbox*>(widget);
00055     assert(listbox);
00056     return listbox;
00057 }
00058 
00059 static void callback_select_list_item(twidget* caller)
00060 {
00061     get_listbox(caller)->list_item_selected(caller);
00062 }
00063 
00064 static void callback_scrollbar(twidget* caller)
00065 {
00066     get_listbox(caller)->scrollbar_moved(caller);
00067 }
00068 
00069 static void callback_scrollbar_button(twidget* caller)
00070 {
00071     get_listbox(caller)->scrollbar_click(caller);
00072 }
00073 
00074 tlistbox::tlistbox() :
00075     tcontainer_(COUNT),
00076     state_(ENABLED),
00077     list_builder_(0),
00078     assume_fixed_row_size_(true),
00079     selected_row_(-1),
00080     selection_count_(0),
00081     row_select_(true),
00082     must_select_(true),
00083     multi_select_(false),
00084     list_rect_(),
00085     list_background_(),
00086     best_spacer_size_(0, 0),
00087     rows_()
00088 {
00089 }
00090 
00091 void tlistbox::list_item_selected(twidget* caller)
00092 {
00093     for(unsigned i = 0; i < scrollbar()->get_visible_items(); ++i) {
00094 
00095         const unsigned row = i + scrollbar()->get_item_position();
00096 
00097         assert(rows_[row].grid());
00098         if(rows_[row].grid()->has_widget(caller)) {
00099 
00100             if(!select_row(row, !rows_[row].get_selected())) {
00101                 // if not allowed to deselect reselect.
00102                 tselectable_* selectable = dynamic_cast<tselectable_*>(caller);
00103                 assert(selectable);
00104                 selectable->set_selected(); 
00105             }
00106 
00107             return;
00108         }
00109     }
00110 
00111     // we aren't supposed to get here.
00112     assert(false);
00113 }
00114 
00115 void tlistbox::scrollbar_click(twidget* caller)
00116 {
00117     if(caller->id() == "_begin") {
00118         scrollbar()->scroll(tscrollbar_::BEGIN);
00119     } else if(caller->id() == "_line_up") {
00120         scrollbar()->scroll(tscrollbar_::ITEM_BACKWARDS);
00121     } else if(caller->id() == "_half_page_up") {
00122         scrollbar()->scroll(tscrollbar_::HALF_JUMP_BACKWARDS);
00123     } else if(caller->id() == "_page_up") {
00124         scrollbar()->scroll(tscrollbar_::JUMP_BACKWARDS);
00125     } else if(caller->id() == "_end") {
00126         scrollbar()->scroll(tscrollbar_::END);
00127     } else if(caller->id() == "_line_down") {
00128         scrollbar()->scroll(tscrollbar_::ITEM_FORWARD);
00129     } else if(caller->id() == "_half_page_down") {
00130         scrollbar()->scroll(tscrollbar_::HALF_JUMP_FORWARD);
00131     } else if(caller->id() == "_page_down") {
00132         scrollbar()->scroll(tscrollbar_::JUMP_FORWARD);
00133     } else {
00134         assert(false);
00135     }
00136 
00137     set_scrollbar_button_status();
00138 }
00139 
00140 void tlistbox::finalize_setup()
00141 {
00142     // If we have a list already set up wire in the callback routine.
00143     tgrid* list = dynamic_cast<tgrid*>(tcontainer_::find_widget("_list", false));
00144     if(list) {
00145         const unsigned col_count = list->get_cols();
00146         const unsigned row_count = list->get_rows();
00147 
00148         // We need to validate the stuff put inside us, we expect
00149         // * panels or grids with more nested items.
00150         // * selectable items.
00151         for(unsigned row = 0; row < row_count; ++row) {
00152             for(unsigned col = 0; col < col_count; ++col) {
00153                 twidget* widget = list->widget(row, col);
00154                 assert(widget);
00155 
00156 
00157                 tgrid* grid = dynamic_cast<tgrid*>(widget);
00158                 tselectable_* selectable = dynamic_cast<tselectable_*>(widget);
00159 
00160                 if(selectable) {
00161                     
00162                     // FIXME move the callback also in the selectable class
00163                     ttoggle_button* btn = dynamic_cast<ttoggle_button*>(widget);
00164                     assert(btn);
00165 
00166                     btn->set_callback_mouse_left_click(callback_select_list_item);
00167 
00168                 } else if(grid) {
00169                     // Grid not allowed atm.
00170                     assert(false);
00171                 } else {
00172                     std::cerr << "Widget type " << typeid(*widget).name() << ".\n";
00173                     assert(false);
00174                 }
00175             }
00176         }
00177     } else {
00178         // Validate the existance.
00179         tspacer* spacer = tlistbox::list();
00180     }
00181 
00182     scrollbar()->set_callback_positioner_move(callback_scrollbar);
00183 
00184     static std::vector<std::string> button_names;
00185     if(button_names.empty()) {
00186         button_names.push_back("_begin");
00187         button_names.push_back("_line_up");
00188         button_names.push_back("_half_page_up");
00189         button_names.push_back("_page_up");
00190 
00191         button_names.push_back("_end");
00192         button_names.push_back("_line_down");
00193         button_names.push_back("_half_page_down");
00194         button_names.push_back("_page_down");
00195     }
00196 
00197     foreach(const std::string& name, button_names) {
00198         tbutton* button = dynamic_cast<tbutton*>(tcontainer_::find_widget(name, false));
00199         if(button) {
00200             button->set_callback_mouse_left_click(callback_scrollbar_button);
00201         }
00202     }
00203 }
00204 
00205 void tlistbox::set_scrollbar_button_status()
00206 {
00207     // Set scroll up button status
00208     static std::vector<std::string> button_up_names;
00209     if(button_up_names.empty()) {
00210         button_up_names.push_back("_begin");
00211         button_up_names.push_back("_line_up");
00212         button_up_names.push_back("_half_page_up");
00213         button_up_names.push_back("_page_up");
00214     }
00215 
00216     foreach(const std::string& name, button_up_names) {
00217         tbutton* button = dynamic_cast<tbutton*>(tcontainer_::find_widget(name, false));
00218         if(button) {
00219             button->set_active(!scrollbar()->at_begin());
00220         }
00221     }
00222 
00223     // Set scroll down button status
00224     static std::vector<std::string> button_down_names;
00225     if(button_down_names.empty()) {
00226         button_down_names.push_back("_end");
00227         button_down_names.push_back("_line_down");
00228         button_down_names.push_back("_half_page_down");
00229         button_down_names.push_back("_page_down");
00230     }
00231 
00232     foreach(const std::string& name, button_down_names) {
00233         tbutton* button = dynamic_cast<tbutton*>(tcontainer_::find_widget(name, false));
00234         if(button) {
00235             button->set_active(!scrollbar()->at_end());
00236         }
00237     }
00238 
00239     // Set the scrollbar itself
00240     scrollbar()->set_active(!(scrollbar()->at_begin() && scrollbar()->at_end()));
00241 }
00242 
00243 tpoint tlistbox::get_best_size() const 
00244 {
00245 
00246     // Set the size of the spacer to the wanted size for the list.
00247     unsigned width = 0;
00248     unsigned height = 0;
00249 
00250     if(best_spacer_size_ != tpoint(0, 0)) {
00251         // We got a best size set use that instead of calculating it.
00252         height = best_spacer_size_.y;
00253         width = best_spacer_size_.x;
00254     } else {
00255         // NOTE we should look at the number of visible items etc
00256         foreach(const trow& row, rows_) {
00257             assert(row.grid());
00258             const tpoint best_size = row.grid()->get_best_size();
00259             width = width >= best_size.x ? width : best_size.x;
00260 
00261             height += best_size.y;
00262         }
00263     }
00264     
00265     // Kind of a hack, we edit the spacer in a const function.
00266     // Of course we could cache the list and make it mutable instead.
00267     // But since the spacer is a kind of cache the const_cast doesn't 
00268     // look too ugly.
00269     const_cast<tspacer*>(list())->set_best_size(tpoint(width, height));
00270 
00271     // Now the container will return the wanted result.
00272     return tcontainer_::get_best_size();
00273 }
00274 
00275 void tlistbox::draw(surface& surface)
00276 {
00277     // Inherit.
00278     tcontainer_::draw(surface);
00279 
00280     // Handle our full redraw for the spacer area.
00281     if(!list_background_) {
00282         list_background_.assign(gui2::save_background(surface, list_rect_));
00283     } else {
00284         gui2::restore_background(list_background_, surface, list_rect_);
00285     }
00286     
00287     // Now paint the list over the spacer.
00288     unsigned offset = list_rect_.y;
00289     for(unsigned i = 0; i < scrollbar()->get_visible_items(); ++i) {
00290         trow& row = rows_[i + scrollbar()->get_item_position()];
00291 
00292         assert(row.grid());
00293         if(row.grid()->dirty()) {
00294             row.grid()->draw(row.canvas());
00295         }
00296         
00297         // draw background
00298         const SDL_Rect rect = {list_rect_.x, offset, list_rect_.w, row.get_height() };
00299 
00300         // draw widget
00301         blit_surface(row.canvas(), 0, surface, &rect);
00302 
00303         offset += row.get_height();
00304     }
00305 }
00306 
00307 void tlistbox::set_size(const SDL_Rect& rect)
00308 {
00309     // Since we allow to be resized we need to determine the real size we get.
00310     assert(best_spacer_size_ == tpoint(0, 0));
00311     SDL_Rect best_rect = rect;
00312     if(rows_.size()) {
00313 
00314         const tpoint best_size = get_best_size();
00315 
00316         if(best_size.y > rect.h) {
00317             best_spacer_size_ = list()->get_best_size();
00318             best_spacer_size_.y -= (best_size.y - rect.h);
00319             if(assume_fixed_row_size_) {
00320                 const unsigned row_height = rows_[0].grid()->get_best_size().y;
00321                 const unsigned orig_height = best_spacer_size_.y;
00322                 best_spacer_size_.y = (best_spacer_size_.y / row_height) * row_height;
00323                 best_rect.h -= (orig_height - best_spacer_size_.y);
00324             
00325                 // This call is required to update the best size.
00326                 get_best_size();
00327             }
00328         }
00329     }
00330 
00331     // Inherit.
00332     tcontainer_::set_size(best_rect);
00333 
00334     best_spacer_size_ = tpoint(0, 0);
00335 
00336     // Now set the items in the spacer.
00337     tspacer* spacer = list();
00338     list_rect_ = spacer->get_rect();
00339 
00340     foreach(trow& row, rows_) {
00341         assert(row.grid());
00342 
00343         const unsigned height = row.grid()->get_best_size().y;
00344         row.set_height(height);
00345 
00346         row.grid()->set_size(::create_rect(0, 0, list_rect_.w, height));
00347         row.canvas().assign(SDL_CreateRGBSurface(SDL_SWSURFACE, 
00348             list_rect_.w, height, 32, 0xFF0000, 0xFF00, 0xFF, 0xFF000000));
00349     }
00350 
00351     // FIXME we assume fixed row height atm.
00352     if(rows_.size() > 0) {
00353         const unsigned rows = list()->get_best_size().y / rows_[0].get_height();
00354         scrollbar()->set_visible_items(rows);
00355     } else {
00356         scrollbar()->set_visible_items(1);
00357     }
00358     set_scrollbar_button_status();
00359 }
00360 
00361 twidget* tlistbox::find_widget(const tpoint& coordinate, const bool must_be_active) 
00362 { 
00363     // Inherited
00364     twidget* result = tcontainer_::find_widget(coordinate, must_be_active);
00365 
00366     // if on the panel we need to do special things.
00367     if(result && result->id() == "_list") {
00368         int offset = coordinate.y - list_rect_.y;
00369         assert(offset >= 0);
00370         for(unsigned i = 0; i < scrollbar()->get_visible_items(); ++i) {
00371 
00372             trow& row = rows_[i + scrollbar()->get_item_position()];
00373             
00374             if(offset < row.get_height()) {
00375                 assert(row.grid());
00376                 return row.grid()->find_widget(
00377                     tpoint(coordinate.x - list_rect_.x, offset), must_be_active);
00378             } else {
00379                 offset -= row.get_height();
00380             }
00381         }
00382     }
00383 
00384     return result;
00385 }
00386 
00387 const twidget* tlistbox::find_widget(const tpoint& coordinate, const bool must_be_active) const
00388 {
00389     // Inherited
00390     const twidget* result = tcontainer_::find_widget(coordinate, must_be_active);
00391 
00392     // if on the panel we need to do special things.
00393     if(result && result->id() == "_list") {
00394         int offset = coordinate.y - list_rect_.y;
00395         assert(offset >= 0);
00396         for(unsigned i = 0; i < scrollbar()->get_visible_items(); ++i) {
00397 
00398             const trow& row = rows_[i + scrollbar()->get_item_position()];
00399             
00400             if(offset < row.get_height()) {
00401                 assert(row.grid());
00402                 return row.grid()->find_widget(
00403                     tpoint(coordinate.x - list_rect_.x, offset), must_be_active);
00404             } else {
00405                 offset -= row.get_height();
00406             }
00407         }
00408     }
00409 
00410     return result;
00411 }
00412 
00413 void tlistbox::add_item(const t_string& label)
00414 {
00415     assert(list_builder_);
00416 
00417     trow row(*list_builder_, label);
00418     assert(row.grid());
00419 
00420     row.grid()->set_parent(this);
00421     rows_.push_back(row);
00422 
00423     if(must_select_ && !selection_count_) {
00424         select_row(get_item_count() - 1);
00425     }
00426 
00427     scrollbar()->set_item_count(get_item_count());
00428     set_scrollbar_button_status();
00429 }
00430 
00431 tscrollbar_* tlistbox::scrollbar()
00432 {
00433     // Note we don't cache the result, we might want change things later.   
00434     tscrollbar_* result = 
00435         dynamic_cast<tscrollbar_*>(tcontainer_::find_widget("_scrollbar", false));
00436     assert(result);
00437     return result;
00438 }
00439 
00440 const tscrollbar_* tlistbox::scrollbar() const
00441 {
00442     // Note we don't cache the result, we might want change things later.   
00443     const tscrollbar_* result = 
00444         dynamic_cast<const tscrollbar_*>(tcontainer_::find_widget("_scrollbar", false));
00445     assert(result);
00446     return result;
00447 }
00448 
00449 tspacer* tlistbox::list()
00450 {
00451     tspacer* list = 
00452         dynamic_cast<tspacer*>(tcontainer_::find_widget("_list", false));
00453     assert(list);
00454     return list;
00455 }
00456 
00457 const tspacer* tlistbox::list() const
00458 {
00459     const tspacer* list = 
00460         dynamic_cast<const tspacer*>(tcontainer_::find_widget("_list", false));
00461     assert(list);
00462     return list;
00463 }
00464 
00465 bool tlistbox::select_row(const unsigned row, const bool select)
00466 {
00467     if(!select && must_select_ && selection_count_ < 2) {
00468         return false;
00469     }
00470 
00471     if((select && rows_[row].get_selected()) 
00472         || (!select && !rows_[row].get_selected())) {
00473         return true;
00474     }
00475 
00476     if(select && !multi_select_ && selection_count_ == 1) {
00477         assert(selected_row_ < get_item_count());
00478         rows_[selected_row_].select(false);
00479         --selection_count_;
00480     }
00481 
00482     if(select) {
00483         ++selection_count_;
00484     } else {
00485         --selection_count_;
00486     }
00487 
00488     assert(row < get_item_count());
00489     selected_row_ = row;
00490     rows_[row].select();
00491 
00492     return true;
00493 }
00494 
00495 void tlistbox::set_row_active(const unsigned row, const bool active)
00496 {
00497     assert(row < get_item_count());
00498     assert(rows_[row].grid());
00499     rows_[row].grid()->set_active(active);
00500 }
00501 
00502 tlistbox::trow::trow(const tbuilder_grid& list_builder_,const t_string& label) :
00503     grid_(dynamic_cast<tgrid*>(list_builder_.build())),
00504     height_(0),
00505     selected_(false)
00506 {
00507     assert(grid_);
00508     init_in_grid(grid_, label);
00509 }
00510 
00511 void tlistbox::trow::init_in_grid(tgrid* grid, const t_string& label) 
00512 {
00513     for(unsigned row = 0; row < grid->get_rows(); ++row) {
00514         for(unsigned col = 0; col < grid->get_cols(); ++col) {
00515             twidget* widget = grid->widget(row, col);
00516             assert(widget);
00517 
00518 
00519             tgrid* child_grid = dynamic_cast<tgrid*>(widget);
00520             ttoggle_button* btn = dynamic_cast<ttoggle_button*>(widget);
00521 
00522             if(btn) {
00523                 btn->set_callback_mouse_left_click(callback_select_list_item);
00524                 btn->set_label(label);
00525             } else if(grid) {
00526                 init_in_grid(child_grid, label);
00527             } else {
00528                 std::cerr << "Widget type " << typeid(*widget).name() << ".\n";
00529                 assert(false);
00530             }
00531         }
00532     }
00533 }
00534 
00535 void tlistbox::trow::select(const bool sel) 
00536 {
00537     selected_ = sel;
00538     assert(grid_);
00539     select_in_grid(grid_, sel);
00540 }
00541 
00542 void tlistbox::trow::select_in_grid(tgrid* grid, const bool sel)
00543 {
00544     for(unsigned row = 0; row < grid->get_rows(); ++row) {
00545         for(unsigned col = 0; col < grid->get_cols(); ++col) {
00546             twidget* widget = grid->widget(row, col);
00547             assert(widget);
00548 
00549             tgrid* child_grid = dynamic_cast<tgrid*>(widget);
00550             tselectable_* selectable = dynamic_cast<tselectable_*>(widget);
00551 
00552             if(selectable) {
00553                 selectable->set_selected(sel);
00554             } else if(grid) {
00555                 select_in_grid(child_grid, sel);
00556             } else {
00557                 std::cerr << "Widget type " << typeid(*widget).name() << ".\n";
00558                 assert(false);
00559             }
00560         }
00561     }
00562 }
00563 
00564 } // namespace gui2
00565 
00566 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs