gui/widgets/window.cpp

Go to the documentation of this file.
00001 /* $Id: window.cpp 54167 2012-05-13 13:33:24Z mordante $ */
00002 /*
00003    Copyright (C) 2007 - 2012 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 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 /**
00017  *  @file
00018  *  Implementation of window.hpp.
00019  */
00020 
00021 #define GETTEXT_DOMAIN "wesnoth-lib"
00022 
00023 #include "gui/widgets/window_private.hpp"
00024 
00025 #include "font.hpp"
00026 #include "foreach.hpp"
00027 #include "game_display.hpp"
00028 #include "gettext.hpp"
00029 #include "log.hpp"
00030 #include "gui/auxiliary/event/distributor.hpp"
00031 #include "gui/auxiliary/event/message.hpp"
00032 #include "gui/auxiliary/log.hpp"
00033 #include "gui/auxiliary/layout_exception.hpp"
00034 #include "gui/auxiliary/window_builder/control.hpp"
00035 #include "gui/dialogs/title_screen.hpp"
00036 #include "gui/dialogs/tip.hpp"
00037 #include "gui/widgets/button.hpp"
00038 #include "gui/widgets/settings.hpp"
00039 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
00040 #include "gui/widgets/debug.hpp"
00041 #endif
00042 #include "preferences.hpp"
00043 #include "preferences_display.hpp"
00044 #include "video.hpp"
00045 
00046 #include <boost/bind.hpp>
00047 
00048 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
00049 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
00050 
00051 #define LOG_IMPL_SCOPE_HEADER window.get_control_type() \
00052         + " [" + window.id() + "] " + __func__
00053 #define LOG_IMPL_HEADER LOG_IMPL_SCOPE_HEADER + ':'
00054 
00055 namespace gui2{
00056 
00057 namespace implementation {
00058 /** @todo See whether this hack can be removed. */
00059 // Needed to fix a compiler error in REGISTER_WIDGET.
00060 class tbuilder_window
00061     : public tbuilder_control
00062 {
00063 public:
00064     tbuilder_window(const config& cfg)
00065         : tbuilder_control(cfg)
00066     {
00067     }
00068 
00069     twidget* build() const { return NULL; }
00070 };
00071 
00072 } // namespace implementation
00073 REGISTER_WIDGET(window)
00074 
00075 unsigned twindow::sunset_ = 0;
00076 
00077 namespace {
00078 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
00079     const unsigned MANUAL = tdebug_layout_graph::MANUAL;
00080     const unsigned SHOW = tdebug_layout_graph::SHOW;
00081     const unsigned LAYOUT = tdebug_layout_graph::LAYOUT;
00082 #else
00083     // values are irrelavant when DEBUG_WINDOW_LAYOUT_GRAPHS is not defined.
00084     const unsigned MANUAL = 0;
00085     const unsigned SHOW = 0;
00086     const unsigned LAYOUT = 0;
00087 #endif
00088 
00089 /**
00090  * The interval between draw events.
00091  *
00092  * When the window is shown this value is set, the callback function always
00093  * uses this value instead of the parameter send, that way the window can stop
00094  * drawing when it wants.
00095  */
00096 static int draw_interval = 0;
00097 
00098 /**
00099  * SDL_AddTimer() callback for the draw event.
00100  *
00101  * When this callback is called it pushes a new draw event in the event queue.
00102  *
00103  * @returns                       The new timer interval, 0 to stop.
00104  */
00105 static Uint32 draw_timer(Uint32, void*)
00106 {
00107 //  DBG_GUI_E << "Pushing draw event in queue.\n";
00108 
00109     SDL_Event event;
00110     SDL_UserEvent data;
00111 
00112     data.type = DRAW_EVENT;
00113     data.code = 0;
00114     data.data1 = NULL;
00115     data.data2 = NULL;
00116 
00117     event.type = DRAW_EVENT;
00118     event.user = data;
00119 
00120     SDL_PushEvent(&event);
00121     return draw_interval;
00122 }
00123 
00124 /**
00125  * SDL_AddTimer() callback for delay_event.
00126  *
00127  * @param event                   The event to push in the event queue.
00128  *
00129  * @return                        The new timer interval (always 0).
00130  */
00131 static Uint32 delay_event_callback(const Uint32, void* event)
00132 {
00133     SDL_PushEvent(static_cast<SDL_Event*>(event));
00134     return 0;
00135 }
00136 
00137 /**
00138  * Allows an event to be delayed a certain amount of time.
00139  *
00140  * @note the delay is the minimum time, after the time has passed the event
00141  * will be pushed in the SDL event queue, so it might delay more.
00142  *
00143  * @param event                   The event to delay.
00144  * @param delay                   The number of ms to delay the event.
00145  */
00146 static void delay_event(const SDL_Event& event, const Uint32 delay)
00147 {
00148     SDL_AddTimer(delay, delay_event_callback, new SDL_Event(event));
00149 }
00150 
00151 /**
00152  * Adds a SHOW_HELPTIP event to the SDL event queue.
00153  *
00154  * The event is used to show the helptip for the currently focussed widget.
00155  */
00156 static bool helptip()
00157 {
00158     DBG_GUI_E << "Pushing SHOW_HELPTIP_EVENT event in queue.\n";
00159 
00160     SDL_Event event;
00161     SDL_UserEvent data;
00162 
00163     data.type = SHOW_HELPTIP_EVENT;
00164     data.code = 0;
00165     data.data1 = NULL;
00166     data.data2 = NULL;
00167 
00168     event.type = SHOW_HELPTIP_EVENT;
00169     event.user = data;
00170 
00171     SDL_PushEvent(&event);
00172 
00173     return true;
00174 }
00175 
00176 /**
00177  * Small helper class to get an unique id for every window instance.
00178  *
00179  * This is used to send event to the proper window, this allows windows to post
00180  * messages to themselves and let them delay for a certain amount of time.
00181  */
00182 class tmanager
00183 {
00184     tmanager();
00185 public:
00186 
00187     static tmanager& instance();
00188 
00189     void add(twindow& window);
00190 
00191     void remove(twindow& window);
00192 
00193     unsigned get_id(twindow& window);
00194 
00195     twindow* window(const unsigned id);
00196 
00197 private:
00198 
00199     // The number of active window should be rather small
00200     // so keep it simple and don't add a reverse lookup map.
00201     std::map<unsigned, twindow*> windows_;
00202 };
00203 
00204 tmanager::tmanager()
00205     : windows_()
00206 {
00207 }
00208 
00209 tmanager& tmanager::instance()
00210 {
00211     static tmanager window_manager;
00212     return window_manager;
00213 }
00214 
00215 void tmanager::add(twindow& window)
00216 {
00217     static unsigned id;
00218     ++id;
00219     windows_[id] = &window;
00220 }
00221 
00222 void tmanager::remove(twindow& window)
00223 {
00224     for(std::map<unsigned, twindow*>::iterator itor = windows_.begin();
00225             itor != windows_.end(); ++itor) {
00226 
00227         if(itor->second == &window) {
00228             windows_.erase(itor);
00229             return;
00230         }
00231     }
00232     assert(false);
00233 }
00234 
00235 unsigned tmanager::get_id(twindow& window)
00236 {
00237     for(std::map<unsigned, twindow*>::iterator itor = windows_.begin();
00238             itor != windows_.end(); ++itor) {
00239 
00240         if(itor->second == &window) {
00241             return itor->first;
00242         }
00243     }
00244     assert(false);
00245 
00246     return 0;
00247 }
00248 
00249 twindow* tmanager::window(const unsigned id)
00250 {
00251     std::map<unsigned, twindow*>::iterator itor = windows_.find(id);
00252 
00253     if(itor == windows_.end()) {
00254         return NULL;
00255     } else {
00256         return itor->second;
00257     }
00258 }
00259 
00260 } // namespace
00261 
00262 twindow::twindow(CVideo& video,
00263         tformula<unsigned>x,
00264         tformula<unsigned>y,
00265         tformula<unsigned>w,
00266         tformula<unsigned>h,
00267         const bool automatic_placement,
00268         const unsigned horizontal_placement,
00269         const unsigned vertical_placement,
00270         const unsigned maximum_width,
00271         const unsigned maximum_height,
00272         const std::string& definition,
00273         const twindow_builder::tresolution::ttip& tooltip,
00274         const twindow_builder::tresolution::ttip& helptip)
00275     : tpanel()
00276     , cursor::setter(cursor::NORMAL)
00277     , video_(video)
00278     , status_(NEW)
00279     , show_mode_(none)
00280     , retval_(NONE)
00281     , owner_(0)
00282     , need_layout_(true)
00283     , variables_()
00284     , invalidate_layout_blocked_(false)
00285     , suspend_drawing_(true)
00286     , restorer_()
00287     , automatic_placement_(automatic_placement)
00288     , horizontal_placement_(horizontal_placement)
00289     , vertical_placement_(vertical_placement)
00290     , maximum_width_(maximum_width)
00291     , maximum_height_(maximum_height)
00292     , x_(x)
00293     , y_(y)
00294     , w_(w)
00295     , h_(h)
00296     , tooltip_(tooltip)
00297     , helptip_(helptip)
00298     , click_dismiss_(false)
00299     , enter_disabled_(false)
00300     , escape_disabled_(false)
00301     , linked_size_()
00302     , dirty_list_()
00303 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
00304     , debug_layout_(new tdebug_layout_graph(this))
00305 #endif
00306     , event_distributor_(new event::tdistributor(
00307             *this, event::tdispatcher::front_child))
00308 {
00309     // We load the config in here as exception.
00310     // Our caller did update the screen size so no need for us to do that again.
00311     set_definition(definition);
00312     load_config();
00313 
00314     tmanager::instance().add(*this);
00315 
00316     connect();
00317 
00318     connect_signal<event::DRAW>(boost::bind(&twindow::draw, this));
00319 
00320     connect_signal<event::SDL_VIDEO_RESIZE>(
00321               boost::bind(&twindow::signal_handler_sdl_video_resize
00322                   , this, _2, _3, _5));
00323 
00324     connect_signal<event::SDL_ACTIVATE>(
00325               boost::bind(&event::tdistributor::initialize_state
00326                   , event_distributor_));
00327 
00328     connect_signal<event::SDL_LEFT_BUTTON_DOWN>(
00329               boost::bind(
00330                   &twindow::signal_handler_click_dismiss, this, _2, _3, _4)
00331             , event::tdispatcher::front_child);
00332     connect_signal<event::SDL_MIDDLE_BUTTON_DOWN>(
00333               boost::bind(
00334                   &twindow::signal_handler_click_dismiss, this, _2, _3, _4)
00335             , event::tdispatcher::front_child);
00336     connect_signal<event::SDL_RIGHT_BUTTON_DOWN>(
00337               boost::bind(
00338                   &twindow::signal_handler_click_dismiss, this, _2, _3, _4)
00339             , event::tdispatcher::front_child);
00340 
00341     connect_signal<event::SDL_KEY_DOWN>(
00342               boost::bind(&twindow::signal_handler_sdl_key_down
00343                   , this, _2, _3, _5)
00344             , event::tdispatcher::back_pre_child);
00345     connect_signal<event::SDL_KEY_DOWN>(
00346               boost::bind(&twindow::signal_handler_sdl_key_down
00347                   , this, _2, _3, _5));
00348 
00349     connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
00350               boost::bind(
00351                   &twindow::signal_handler_message_show_tooltip
00352                 , this
00353                 , _2
00354                 , _3
00355                 , _5)
00356             , event::tdispatcher::back_pre_child);
00357 
00358     connect_signal<event::MESSAGE_SHOW_HELPTIP>(
00359               boost::bind(
00360                   &twindow::signal_handler_message_show_helptip
00361                 , this
00362                 , _2
00363                 , _3
00364                 , _5)
00365             , event::tdispatcher::back_pre_child);
00366 
00367     connect_signal<event::REQUEST_PLACEMENT>(
00368               boost::bind(
00369                   &twindow::signal_handler_request_placement
00370                 , this
00371                 , _2
00372                 , _3)
00373             , event::tdispatcher::back_pre_child);
00374 
00375     register_hotkey(hotkey::GLOBAL__HELPTIP, boost::bind(gui2::helptip));
00376 }
00377 
00378 twindow::~twindow()
00379 {
00380     /*
00381      * We need to delete our children here instead of waiting for the grid to
00382      * automatically do it. The reason is when the grid deletes its children
00383      * they will try to unregister them self from the linked widget list. At
00384      * this point the member of twindow are destroyed and we enter UB. (For
00385      * some reason the bug didn't trigger on g++ but it does on MSVC.
00386      */
00387     for(unsigned row = 0; row < grid().get_rows(); ++row) {
00388         for(unsigned col = 0; col < grid().get_cols(); ++col) {
00389             grid().remove_child(row, col);
00390         }
00391     }
00392 
00393     /*
00394      * The tip needs to be closed if the window closes and the window is
00395      * not a tip. If we don't do that the tip will unrender in the next
00396      * window and cause drawing glitches.
00397      * Another issue is that on smallgui and an MP game the tooltip not
00398      * unrendered properly can capture the mouse and make playing impossible.
00399      */
00400     if(show_mode_ == modal) {
00401         tip::remove();
00402     }
00403 
00404     tmanager::instance().remove(*this);
00405 
00406 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
00407 
00408     delete debug_layout_;
00409 
00410 #endif
00411     delete event_distributor_;
00412 }
00413 
00414 twindow* twindow::window_instance(const unsigned handle)
00415 {
00416     return tmanager::instance().window(handle);
00417 }
00418 
00419 void twindow::update_screen_size()
00420 {
00421     // Only if we're the toplevel window we need to update the size, otherwise
00422     // it's done in the resize event.
00423     if(draw_interval == 0) {
00424         const SDL_Rect rect = screen_area();
00425         settings::screen_width = rect.w;
00426         settings::screen_height = rect.h;
00427 
00428         settings::gamemap_width = settings::screen_width;
00429         settings::gamemap_height = settings::screen_height;
00430 
00431         display* display = display::get_singleton();
00432         if(display) {
00433             const unsigned w = display->map_outside_area().w;
00434             const unsigned h = display->map_outside_area().h;
00435             if(w && h) {
00436                 settings::gamemap_width = w;
00437                 settings::gamemap_height = h;
00438             }
00439         }
00440     }
00441 }
00442 
00443 twindow::tretval twindow::get_retval_by_id(const std::string& id)
00444 {
00445 /*WIKI
00446  * @page = GUIToolkitWML
00447  * @order = 3_widget_window_2
00448  *
00449  * List if the id's that have generate a return value:
00450  * * ok confirms the dialog.
00451  * * cancel cancels the dialog.
00452  *
00453  */
00454     // Note it might change to a map later depending on the number
00455     // of items.
00456     if(id == "ok") {
00457         return OK;
00458     } else if(id == "cancel") {
00459         return CANCEL;
00460 
00461     /**
00462      * The ones for the title screen.
00463      *
00464      * This is a kind of hack, but the values are hardcoded in the titlescreen
00465      * and don't want to change them at the moment. It would be a good idea to
00466      * add some namespaces to avoid names clashing.
00467      */
00468     } else if(id == "tutorial") {
00469         return static_cast<tretval>(ttitle_screen::TUTORIAL);
00470     } else if(id == "editor") {
00471         return static_cast<tretval>(ttitle_screen::START_MAP_EDITOR);
00472     } else if(id == "credits") {
00473         return static_cast<tretval>(ttitle_screen::SHOW_ABOUT);
00474     } else if(id == "quit") {
00475         return static_cast<tretval>(ttitle_screen::QUIT_GAME);
00476 
00477     /**
00478      * The hacks which are here so the old engine can handle the event. The new
00479      * engine can't handle all dialogs yet, so it needs to fall back to the old
00480      * engine to make certain things happen.
00481      */
00482     } else if(id == "help") {
00483         return static_cast<tretval>(ttitle_screen::SHOW_HELP);
00484     } else if(id == "campaign") {
00485         return static_cast<tretval>(ttitle_screen::NEW_CAMPAIGN);
00486     } else if(id == "multiplayer") {
00487         return static_cast<tretval>(ttitle_screen::MULTIPLAYER);
00488     } else if(id == "load") {
00489         return static_cast<tretval>(ttitle_screen::LOAD_GAME);
00490     } else if(id == "addons") {
00491         return static_cast<tretval>(ttitle_screen::GET_ADDONS);
00492     } else if(id == "language") {
00493         return static_cast<tretval>(ttitle_screen::CHANGE_LANGUAGE);
00494     } else if(id == "preferences") {
00495         return static_cast<tretval>(ttitle_screen::EDIT_PREFERENCES);
00496 
00497     // default if nothing matched
00498     } else {
00499         return NONE;
00500     }
00501 }
00502 
00503 void twindow::show_tooltip(/*const unsigned auto_close_timeout*/)
00504 {
00505     log_scope2(log_gui_draw, "Window: show as tooltip.");
00506 
00507     generate_dot_file("show", SHOW);
00508 
00509     assert(status_ == NEW);
00510 
00511     set_mouse_behaviour(event::tdispatcher::none);
00512     set_want_keyboard_input(false);
00513 
00514     show_mode_ = tooltip;
00515 
00516     /*
00517      * Before show has been called, some functions might have done some testing
00518      * on the window and called layout, which can give glitches. So
00519      * reinvalidate the window to avoid those glitches.
00520      */
00521     invalidate_layout();
00522     suspend_drawing_ = false;
00523 }
00524 
00525 void twindow::show_non_modal(/*const unsigned auto_close_timeout*/)
00526 {
00527     log_scope2(log_gui_draw, "Window: show non modal.");
00528 
00529     generate_dot_file("show", SHOW);
00530 
00531     assert(status_ == NEW);
00532 
00533     set_mouse_behaviour(event::tdispatcher::hit);
00534 
00535     show_mode_ = modal;
00536 
00537     /*
00538      * Before show has been called, some functions might have done some testing
00539      * on the window and called layout, which can give glitches. So
00540      * reinvalidate the window to avoid those glitches.
00541      */
00542     invalidate_layout();
00543     suspend_drawing_ = false;
00544 }
00545 
00546 int twindow::show(const bool restore, const unsigned auto_close_timeout)
00547 {
00548     /*
00549      * Removes the old tip if one shown. The show_tip doesn't remove
00550      * the tip, since it's the tip.
00551      */
00552     tip::remove();
00553 
00554     show_mode_ = modal;
00555 
00556     /**
00557      * Helper class to set and restore the drawing interval.
00558      *
00559      * We need to make sure we restore the value when the function ends, be it
00560      * normally or due to an exception.
00561      */
00562     class tdraw_interval_setter
00563     {
00564     public:
00565         tdraw_interval_setter()
00566             : interval_(draw_interval)
00567         {
00568             if(interval_ == 0) {
00569                 draw_interval = 30;
00570                 SDL_AddTimer(draw_interval, draw_timer, NULL);
00571 
00572                 // There might be some time between creation and showing so
00573                 // reupdate the sizes.
00574                 update_screen_size();
00575 
00576             }
00577         }
00578 
00579         ~tdraw_interval_setter()
00580         {
00581             draw_interval = interval_;
00582         }
00583     private:
00584 
00585         int interval_;
00586     };
00587 
00588     log_scope2(log_gui_draw, LOG_SCOPE_HEADER);
00589 
00590     generate_dot_file("show", SHOW);
00591 
00592     assert(status_ == NEW);
00593 
00594     tdraw_interval_setter draw_interval_setter;
00595 
00596     /*
00597      * Before show has been called, some functions might have done some testing
00598      * on the window and called layout, which can give glitches. So
00599      * reinvalidate the window to avoid those glitches.
00600      */
00601     invalidate_layout();
00602     suspend_drawing_ = false;
00603 
00604     if(auto_close_timeout) {
00605         // Make sure we're drawn before we try to close ourselves, which can
00606         // happen if the timeout is small.
00607         draw();
00608 
00609         SDL_Event event;
00610         SDL_UserEvent data;
00611 
00612         data.type = CLOSE_WINDOW_EVENT;
00613         data.code = tmanager::instance().get_id(*this);
00614         data.data1 = NULL;
00615         data.data2 = NULL;
00616 
00617         event.type = CLOSE_WINDOW_EVENT;
00618         event.user = data;
00619 
00620         delay_event(event, auto_close_timeout);
00621     }
00622 
00623     try {
00624         // Start our loop drawing will happen here as well.
00625         for(status_ = SHOWING; status_ != REQUEST_CLOSE; ) {
00626             // process installed callback if valid, to allow e.g. network polling
00627             events::pump();
00628             // Add a delay so we don't keep spinning if there's no event.
00629             SDL_Delay(10);
00630         }
00631     } catch(...) {
00632         /**
00633          * @todo Clean up the code duplication.
00634          *
00635          * In the future the restoring shouldn't be needed so the duplication
00636          * doesn't hurt too much but keep this todo as a reminder.
00637          */
00638         suspend_drawing_ = true;
00639 
00640         // restore area
00641         if(restore) {
00642             SDL_Rect rect = get_rect();
00643             sdl_blit(restorer_, 0, video_.getSurface(), &rect);
00644             update_rect(get_rect());
00645             font::undraw_floating_labels(video_.getSurface());
00646         }
00647         throw;
00648     }
00649 
00650     suspend_drawing_ = true;
00651 
00652     // restore area
00653     if(restore) {
00654         SDL_Rect rect = get_rect();
00655         sdl_blit(restorer_, 0, video_.getSurface(), &rect);
00656         update_rect(get_rect());
00657         font::undraw_floating_labels(video_.getSurface());
00658     }
00659 
00660     return retval_;
00661 }
00662 
00663 void twindow::draw()
00664 {
00665     /***** ***** ***** ***** Init ***** ***** ***** *****/
00666     // Prohibited from drawing?
00667     if(suspend_drawing_) {
00668         return;
00669     }
00670 
00671     surface frame_buffer = video_.getSurface();
00672 
00673     /***** ***** Layout and get dirty list ***** *****/
00674     if(need_layout_) {
00675         // Restore old surface. In the future this phase will not be needed
00676         // since all will be redrawn when needed with dirty rects. Since that
00677         // doesn't work yet we need to undraw the window.
00678         if(restorer_) {
00679             SDL_Rect rect = get_rect();
00680             sdl_blit(restorer_, 0, frame_buffer, &rect);
00681             // Since the old area might be bigger as the new one, invalidate
00682             // it.
00683             update_rect(rect);
00684         }
00685 
00686         layout();
00687 
00688         // Get new surface for restoring
00689         SDL_Rect rect = get_rect();
00690         // We want the labels underneath the window so draw them and use them
00691         // as restore point.
00692         font::draw_floating_labels(frame_buffer);
00693         restorer_ = get_surface_portion(frame_buffer, rect);
00694 
00695         // Need full redraw so only set ourselves dirty.
00696         dirty_list_.push_back(std::vector<twidget*>(1, this));
00697     } else {
00698 
00699         // Let widgets update themselves, which might dirty some things.
00700         layout_children();
00701 
00702         // Now find the widgets that are dirty.
00703         std::vector<twidget*> call_stack;
00704         if(!new_widgets) {
00705             populate_dirty_list(*this, call_stack);
00706         } else {
00707             /* Force to update and redraw the entire screen */
00708             dirty_list_.clear();
00709             dirty_list_.push_back(std::vector<twidget*>(1, this));
00710             update_rect(screen_area());
00711         }
00712     }
00713 
00714     if(dirty_list_.empty()) {
00715         if(preferences::use_color_cursors() || sunset_) {
00716             surface frame_buffer = get_video_surface();
00717 
00718             if(sunset_) {
00719                 /** @todo should probably be moved to event::thandler::draw. */
00720                 static unsigned i = 0;
00721                 if(++i % sunset_ == 0) {
00722                     SDL_Rect r = ::create_rect(0, 0, frame_buffer->w, frame_buffer->h);
00723                     const Uint32 color =
00724                             SDL_MapRGBA(frame_buffer->format,0,0,0,255);
00725 
00726                     fill_rect_alpha(r, color, 1, frame_buffer);
00727                     update_rect(r);
00728                 }
00729             }
00730         }
00731         return;
00732     }
00733 
00734     foreach(std::vector<twidget*>& item, dirty_list_) {
00735 
00736         assert(!item.empty());
00737 
00738         const SDL_Rect dirty_rect = new_widgets
00739                 ? screen_area()
00740                 : item.back()->get_dirty_rect();
00741 
00742 // For testing we disable the clipping rect and force the entire screen to
00743 // update. This way an item rendered at the wrong place is directly visible.
00744 #if 0
00745         dirty_list_.clear();
00746         dirty_list_.push_back(std::vector<twidget*>(1, this));
00747         update_rect(screen_area());
00748 #else
00749         clip_rect_setter clip(frame_buffer, &dirty_rect);
00750 #endif
00751 
00752         /*
00753          * The actual update routine does the following:
00754          * - Restore the background.
00755          *
00756          * - draw [begin, end) the back ground of all widgets.
00757          *
00758          * - draw the children of the last item in the list, if this item is
00759          *   a container it's children get a full redraw. If it's not a
00760          *   container nothing happens.
00761          *
00762          * - draw [rbegin, rend) the fore ground of all widgets. For items
00763          *   which have two layers eg window or panel it draws the foreground
00764          *   layer. For other widgets it's a nop.
00765          *
00766          * Before drawing there needs to be determined whether a dirty widget
00767          * really needs to be redrawn. If the widget doesn't need to be
00768          * redrawing either being not VISIBLE or has status NOT_DRAWN. If
00769          * it's not drawn it's still set not dirty to avoid it keep getting
00770          * on the dirty list.
00771          */
00772 
00773         for(std::vector<twidget*>::iterator itor = item.begin();
00774                 itor != item.end(); ++itor) {
00775 
00776             if((**itor).get_visible() != twidget::VISIBLE
00777                     || (**itor).get_drawing_action() == twidget::NOT_DRAWN) {
00778 
00779                 for(std::vector<twidget*>::iterator citor = itor;
00780                         citor != item.end(); ++citor) {
00781 
00782                     (**citor).set_dirty(false);
00783                 }
00784 
00785                 item.erase(itor, item.end());
00786                 break;
00787             }
00788         }
00789 
00790         // Restore.
00791         SDL_Rect rect = get_rect();
00792         sdl_blit(restorer_, 0, frame_buffer, &rect);
00793 
00794         if(new_widgets) {
00795             // Background.
00796             for(std::vector<twidget*>::iterator itor = item.begin();
00797                     itor != item.end(); ++itor) {
00798 
00799                 (**itor).draw_background(frame_buffer, 0, 0);
00800             }
00801 
00802             // Children.
00803             if(!item.empty()) {
00804                 item.back()->draw_children(frame_buffer, 0, 0);
00805             }
00806 
00807             // Foreground.
00808             for(std::vector<twidget*>::reverse_iterator ritor = item.rbegin();
00809                     ritor != item.rend(); ++ritor) {
00810 
00811                 (**ritor).draw_foreground(frame_buffer, 0, 0);
00812                 (**ritor).set_dirty(false);
00813             }
00814         } else {
00815             // Background.
00816             for(std::vector<twidget*>::iterator itor = item.begin();
00817                     itor != item.end(); ++itor) {
00818 
00819                 (**itor).draw_background(frame_buffer);
00820             }
00821 
00822             // Children.
00823             if(!item.empty()) {
00824                 item.back()->draw_children(frame_buffer);
00825             }
00826 
00827             // Foreground.
00828             for(std::vector<twidget*>::reverse_iterator ritor = item.rbegin();
00829                     ritor != item.rend(); ++ritor) {
00830 
00831                 (**ritor).draw_foreground(frame_buffer);
00832                 (**ritor).set_dirty(false);
00833             }
00834         }
00835 
00836         update_rect(dirty_rect);
00837     }
00838 
00839     dirty_list_.clear();
00840 
00841     std::vector<twidget*> call_stack;
00842     populate_dirty_list(*this, call_stack);
00843     assert(dirty_list_.empty());
00844 
00845     SDL_Rect rect = get_rect();
00846     update_rect(rect);
00847 }
00848 
00849 void twindow::undraw()
00850 {
00851     if(restorer_) {
00852         SDL_Rect rect = get_rect();
00853         sdl_blit(restorer_, 0, video_.getSurface(), &rect);
00854         // Since the old area might be bigger as the new one, invalidate
00855         // it.
00856         update_rect(rect);
00857     }
00858 }
00859 
00860 twindow::tinvalidate_layout_blocker::tinvalidate_layout_blocker(twindow& window)
00861     : window_(window)
00862 {
00863     assert(!window_.invalidate_layout_blocked_);
00864     window_.invalidate_layout_blocked_ = true;
00865 }
00866 
00867 twindow::tinvalidate_layout_blocker::~tinvalidate_layout_blocker()
00868 {
00869     assert(window_.invalidate_layout_blocked_);
00870     window_.invalidate_layout_blocked_ = false;
00871 }
00872 
00873 void twindow::invalidate_layout()
00874 {
00875     if(!invalidate_layout_blocked_) {
00876         need_layout_ = true;
00877     }
00878 }
00879 
00880 void twindow::init_linked_size_group(const std::string& id,
00881         const bool fixed_width, const bool fixed_height)
00882 {
00883     assert(fixed_width || fixed_height);
00884     assert(!has_linked_size_group(id));
00885 
00886     linked_size_[id] = tlinked_size(fixed_width, fixed_height);
00887 }
00888 
00889 bool twindow::has_linked_size_group(const std::string& id)
00890 {
00891     return linked_size_.find(id) != linked_size_.end();
00892 }
00893 
00894 void twindow::add_linked_widget(const std::string& id, twidget* widget)
00895 {
00896     assert(widget);
00897     assert(has_linked_size_group(id));
00898 
00899     std::vector<twidget*>& widgets = linked_size_[id].widgets;
00900     if(std::find(widgets.begin(), widgets.end(), widget) == widgets.end()) {
00901         widgets.push_back(widget);
00902     }
00903 }
00904 
00905 void twindow::remove_linked_widget(const std::string& id
00906         , const twidget* widget)
00907 {
00908     assert(widget);
00909     assert(has_linked_size_group(id));
00910 
00911     std::vector<twidget*>& widgets = linked_size_[id].widgets;
00912 
00913     std::vector<twidget*>::iterator itor =
00914             std::find(widgets.begin(), widgets.end(), widget);
00915 
00916     if(itor != widgets.end()) {
00917         widgets.erase(itor);
00918 
00919         assert(std::find(widgets.begin(), widgets.end(), widget)
00920                == widgets.end());
00921     }
00922 }
00923 
00924 void twindow::layout()
00925 {
00926     /***** Initialize. *****/
00927 
00928     boost::intrusive_ptr<const twindow_definition::tresolution> conf =
00929         boost::dynamic_pointer_cast<const twindow_definition::tresolution>
00930         (config());
00931     assert(conf);
00932 
00933     log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
00934 
00935     get_screen_size_variables(variables_);
00936 
00937     const int maximum_width = automatic_placement_
00938             ?  maximum_width_
00939                 ? std::min(maximum_width_, settings::screen_width)
00940                 : settings::screen_width
00941             : w_(variables_);
00942 
00943     const int maximum_height = automatic_placement_
00944             ? maximum_height_
00945                 ? std::min(maximum_height_, settings::screen_height)
00946                 : settings::screen_height
00947             : h_(variables_);
00948 
00949     /***** Handle click dismiss status. *****/
00950     tbutton* click_dismiss_button = NULL;
00951     if((click_dismiss_button
00952             = find_widget<tbutton>(this, "click_dismiss", false, false))) {
00953 
00954         click_dismiss_button->set_visible(twidget::INVISIBLE);
00955     }
00956     if(click_dismiss_) {
00957         tbutton* button = find_widget<tbutton>(this, "ok", false, false);
00958         if(button) {
00959             button->set_visible(twidget::INVISIBLE);
00960             click_dismiss_button = button;
00961         }
00962         VALIDATE(click_dismiss_button
00963                 , _("Click dismiss needs a 'click_dismiss' or 'ok' button."));
00964     }
00965 
00966     /***** Layout. *****/
00967     layout_init(true);
00968     generate_dot_file("layout_init", LAYOUT);
00969 
00970     layout_linked_widgets();
00971 
00972     try {
00973         twindow_implementation::layout(*this, maximum_width, maximum_height);
00974     } catch(tlayout_exception_resize_failed&) {
00975 
00976         /** @todo implement the scrollbars on the window. */
00977 
00978         std::stringstream sstr;
00979         sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
00980                 << "' found the following problem: Failed to size window;"
00981                 << " wanted size " << get_best_size()
00982                 << " available size "
00983                 << maximum_width << ',' << maximum_height
00984                 << " screen size "
00985                 << settings::screen_width << ',' << settings::screen_height
00986                 << '.';
00987 
00988         throw twml_exception(_("Failed to show a dialog, "
00989                 "which doesn't fit on the screen."), sstr.str());
00990     }
00991 
00992     /****** Validate click dismiss status. *****/
00993     if(click_dismiss_ && disable_click_dismiss()) {
00994         assert(click_dismiss_button);
00995         click_dismiss_button->set_visible(twidget::VISIBLE);
00996 
00997         connect_signal_mouse_left_click(
00998                   *click_dismiss_button
00999                 , boost::bind(
01000                       &twindow::set_retval
01001                     , this
01002                     , OK
01003                     , true));
01004 
01005         layout_init(true);
01006         generate_dot_file("layout_init", LAYOUT);
01007 
01008         layout_linked_widgets();
01009 
01010         try {
01011             twindow_implementation::layout(
01012                     *this, maximum_width, maximum_height);
01013 
01014         } catch(tlayout_exception_resize_failed&) {
01015 
01016             /** @todo implement the scrollbars on the window. */
01017 
01018             std::stringstream sstr;
01019             sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
01020                 << "' found the following problem: Failed to size window;"
01021                 << " wanted size " << get_best_size()
01022                 << " available size "
01023                 << maximum_width << ',' << maximum_height
01024                 << " screen size "
01025                 << settings::screen_width << ',' << settings::screen_height
01026                 << '.';
01027 
01028             throw twml_exception(_("Failed to show a dialog, "
01029                         "which doesn't fit on the screen."), sstr.str());
01030         }
01031     }
01032 
01033     /***** Get the best location for the window *****/
01034     tpoint size = get_best_size();
01035 
01036     assert(size.x <= maximum_width && size.y <= maximum_height);
01037 
01038     tpoint origin(0, 0);
01039 
01040     if(automatic_placement_) {
01041 
01042         switch(horizontal_placement_) {
01043             case tgrid::HORIZONTAL_ALIGN_LEFT :
01044                 // Do nothing
01045                 break;
01046             case tgrid::HORIZONTAL_ALIGN_CENTER :
01047                 origin.x = (settings::screen_width - size.x) / 2;
01048                 break;
01049             case tgrid::HORIZONTAL_ALIGN_RIGHT :
01050                 origin.x = settings::screen_width - size.x;
01051                 break;
01052             default :
01053                 assert(false);
01054         }
01055         switch(vertical_placement_) {
01056             case tgrid::VERTICAL_ALIGN_TOP :
01057                 // Do nothing
01058                 break;
01059             case tgrid::VERTICAL_ALIGN_CENTER :
01060                 origin.y = (settings::screen_height - size.y) / 2;
01061                 break;
01062             case tgrid::VERTICAL_ALIGN_BOTTOM :
01063                 origin.y = settings::screen_height - size.y;
01064                 break;
01065             default :
01066                 assert(false);
01067         }
01068     } else {
01069         origin.x = x_(variables_);
01070         origin.y = y_(variables_);
01071 
01072         size.x = w_(variables_);
01073         size.y = h_(variables_);
01074     }
01075 
01076     /***** Set the window size *****/
01077     place(origin, size);
01078 
01079     generate_dot_file("layout_finished", LAYOUT);
01080     need_layout_ = false;
01081 
01082     event::init_mouse_location();
01083 }
01084 
01085 void twindow::layout_linked_widgets()
01086 {
01087     // evaluate the group sizes
01088     typedef std::pair<const std::string, tlinked_size> hack;
01089     foreach(hack& linked_size, linked_size_) {
01090 
01091         tpoint max_size(0, 0);
01092 
01093         // Determine the maximum size.
01094         foreach(twidget* widget, linked_size.second.widgets) {
01095 
01096             const tpoint size = widget->get_best_size();
01097 
01098             if(size.x > max_size.x) {
01099                 max_size.x = size.x;
01100             }
01101             if(size.y > max_size.y) {
01102                 max_size.y = size.y;
01103             }
01104         }
01105 
01106         // Set the maximum size.
01107         foreach(twidget* widget, linked_size.second.widgets) {
01108 
01109             tpoint size = widget->get_best_size();
01110 
01111             if(linked_size.second.width) {
01112                 size.x = max_size.x;
01113             }
01114             if(linked_size.second.height) {
01115                 size.y = max_size.y;
01116             }
01117 
01118             widget->set_layout_size(size);
01119         }
01120     }
01121 }
01122 
01123 bool twindow::click_dismiss()
01124 {
01125     if(does_click_dismiss()) {
01126         set_retval(OK);
01127         return true;
01128     }
01129     return false;
01130 }
01131 
01132 const std::string& twindow::get_control_type() const
01133 {
01134     static const std::string type = "window";
01135     return type;
01136 }
01137 
01138 void twindow::draw(surface& /*surf*/, const bool /*force*/,
01139         const bool /*invalidate_background*/)
01140 {
01141     assert(false);
01142 }
01143 
01144 namespace {
01145 
01146 /**
01147  * Swaps an item in a grid for another one.*/
01148 void swap_grid(tgrid* grid,
01149         tgrid* content_grid, twidget* widget, const std::string& id)
01150 {
01151     assert(content_grid);
01152     assert(widget);
01153 
01154     // Make sure the new child has same id.
01155     widget->set_id(id);
01156 
01157     // Get the container containing the wanted widget.
01158     tgrid* parent_grid = NULL;
01159     if(grid) {
01160         parent_grid = find_widget<tgrid>(grid, id, false, false);
01161     }
01162     if(!parent_grid) {
01163         parent_grid = find_widget<tgrid>(content_grid, id, true, false);
01164         assert(parent_grid);
01165     }
01166     if(tgrid* g = dynamic_cast<tgrid*>(parent_grid->parent())) {
01167         widget = g->swap_child(id, widget, false);
01168     } else if(tcontainer_* c
01169             = dynamic_cast<tcontainer_*>(parent_grid->parent())) {
01170 
01171         widget = c->grid().swap_child(id, widget, true);
01172     } else {
01173         assert(false);
01174     }
01175 
01176     assert(widget);
01177 
01178     delete widget;
01179 }
01180 
01181 } // namespace
01182 
01183 void twindow::finalize(const boost::intrusive_ptr<tbuilder_grid>& content_grid)
01184 {
01185     swap_grid(NULL, &grid(), content_grid->build(), "_window_content_grid");
01186 }
01187 
01188 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
01189 
01190 void twindow::generate_dot_file(const std::string& generator,
01191         const unsigned domain)
01192 {
01193     debug_layout_->generate_dot_file(generator, domain);
01194 }
01195 #endif
01196 
01197 void twindow_implementation::layout(twindow& window,
01198         const unsigned maximum_width, const unsigned maximum_height)
01199 {
01200     log_scope2(log_gui_layout, LOG_IMPL_SCOPE_HEADER);
01201 
01202     /*
01203      * For now we return the status, need to test later whether this can
01204      * entirely be converted to an exception based system as in 'promised' on
01205      * the algorithm page.
01206      */
01207 
01208     try {
01209         tpoint size = window.get_best_size();
01210 
01211         DBG_GUI_L << LOG_IMPL_HEADER
01212                 << " best size : " << size
01213                 << " maximum size : " << maximum_width << ',' << maximum_height
01214                 << ".\n";
01215         if(size.x <= static_cast<int>(maximum_width)
01216                 && size.y <= static_cast<int>(maximum_height)) {
01217 
01218             DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.\n";
01219             return;
01220         }
01221 
01222         if(size.x > static_cast<int>(maximum_width)) {
01223             window.reduce_width(maximum_width);
01224 
01225             size = window.get_best_size();
01226             if(size.x > static_cast<int>(maximum_width)) {
01227                 DBG_GUI_L << LOG_IMPL_HEADER
01228                         << " Result: Resize width failed."
01229                         << " Wanted width " << maximum_width
01230                         << " resulting width " << size.x
01231                         << ".\n";
01232                 throw tlayout_exception_width_resize_failed();
01233             }
01234             DBG_GUI_L << LOG_IMPL_HEADER
01235                     << " Status: Resize width succeeded.\n";
01236         }
01237 
01238         if(size.y > static_cast<int>(maximum_height)) {
01239             window.reduce_height(maximum_height);
01240 
01241             size = window.get_best_size();
01242             if(size.y > static_cast<int>(maximum_height)) {
01243                 DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
01244                     << " Wanted height " << maximum_height
01245                     << " resulting height " << size.y
01246                     << ".\n";
01247                 throw tlayout_exception_height_resize_failed();
01248             }
01249             DBG_GUI_L << LOG_IMPL_HEADER
01250                     << " Status: Resize height succeeded.\n";
01251         }
01252 
01253         assert(size.x <= static_cast<int>(maximum_width)
01254                 && size.y <= static_cast<int>(maximum_height));
01255 
01256 
01257         DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.\n";
01258         return;
01259 
01260     } catch (tlayout_exception_width_modified&) {
01261         DBG_GUI_L << LOG_IMPL_HEADER
01262                 << " Status: Width has been modified, rerun.\n";
01263 
01264         window.layout_init(false);
01265         window.layout_linked_widgets();
01266         layout(window, maximum_width, maximum_height);
01267         return;
01268     }
01269 }
01270 
01271 void twindow::mouse_capture(const bool capture)
01272 {
01273     assert(event_distributor_);
01274     event_distributor_->capture_mouse(capture);
01275 }
01276 
01277 void twindow::keyboard_capture(twidget* widget)
01278 {
01279     assert(event_distributor_);
01280     event_distributor_->keyboard_capture(widget);
01281 }
01282 
01283 void twindow::add_to_keyboard_chain(twidget* widget)
01284 {
01285     assert(event_distributor_);
01286     event_distributor_->keyboard_add_to_chain(widget);
01287 }
01288 
01289 void twindow::remove_from_keyboard_chain(twidget* widget)
01290 {
01291     assert(event_distributor_);
01292     event_distributor_->keyboard_remove_from_chain(widget);
01293 }
01294 
01295 void twindow::signal_handler_sdl_video_resize(
01296             const event::tevent event, bool& handled, const tpoint& new_size)
01297 {
01298     DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
01299 
01300     if(new_size.x < preferences::min_allowed_width()
01301             || new_size.y < preferences::min_allowed_height()) {
01302 
01303         DBG_GUI_E << LOG_HEADER << " resize aborted, too small.\n";
01304         return;
01305     }
01306 
01307     if(new_size.x == static_cast<int>(settings::screen_width)
01308             && new_size.y == static_cast<int>(settings::screen_height)) {
01309 
01310         DBG_GUI_E << LOG_HEADER << " resize not needed.\n";
01311         handled = true;
01312         return;
01313     }
01314 
01315     if(!preferences::set_resolution(video_ , new_size.x, new_size.y)) {
01316 
01317         LOG_GUI_E << LOG_HEADER
01318                 << " resize aborted, resize failed.\n";
01319         return;
01320     }
01321 
01322     settings::gamemap_width += new_size.x - settings::screen_width ;
01323     settings::gamemap_height += new_size.y - settings::screen_height ;
01324     settings::screen_width = new_size.x;
01325     settings::screen_height = new_size.y;
01326     invalidate_layout();
01327 
01328     handled = true;
01329 }
01330 
01331 void twindow::signal_handler_click_dismiss(
01332         const event::tevent event, bool& handled, bool& halt)
01333 {
01334     DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
01335 
01336     handled = halt = click_dismiss();
01337 }
01338 
01339 void twindow::signal_handler_sdl_key_down(
01340         const event::tevent event, bool& handled, SDLKey key)
01341 {
01342     DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
01343 
01344     if(!enter_disabled_ && (key == SDLK_KP_ENTER || key == SDLK_RETURN)) {
01345         set_retval(OK);
01346         handled = true;
01347     } else if(key == SDLK_ESCAPE && !escape_disabled_) {
01348         set_retval(CANCEL);
01349         handled = true;
01350     } else if(key == SDLK_SPACE) {
01351         handled = click_dismiss();
01352     }
01353 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
01354     if(key == SDLK_F12) {
01355         debug_layout_->generate_dot_file(
01356                 "manual", tdebug_layout_graph::MANUAL);
01357         handled = true;
01358     }
01359 #endif
01360 }
01361 
01362 void twindow::signal_handler_message_show_tooltip(
01363           const event::tevent event
01364         , bool& handled
01365         , event::tmessage& message)
01366 {
01367     DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
01368 
01369     event::tmessage_show_tooltip& request =
01370             dynamic_cast<event::tmessage_show_tooltip&>(message);
01371 
01372     tip::show(video_, tooltip_.id, request.message, request.location);
01373 
01374     handled = true;
01375 }
01376 
01377 void twindow::signal_handler_message_show_helptip(
01378           const event::tevent event
01379         , bool& handled
01380         , event::tmessage& message)
01381 {
01382     DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
01383 
01384     event::tmessage_show_helptip& request =
01385             dynamic_cast<event::tmessage_show_helptip&>(message);
01386 
01387     tip::show(video_, helptip_.id, request.message, request.location);
01388 
01389     handled = true;
01390 }
01391 
01392 void twindow::signal_handler_request_placement(
01393           const event::tevent event
01394         , bool& handled)
01395 {
01396     DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
01397 
01398     invalidate_layout();
01399 
01400     handled = true;
01401 }
01402 
01403 } // namespace gui2
01404 
01405 
01406 /**
01407  * @page layout_algorithm Layout algorithm
01408  *
01409  * @section introduction Introduction
01410  *
01411  * This page describes how the layout engine for the dialogs works. First
01412  * a global overview of some terms used in this document.
01413  *
01414  * - @ref gui2::twidget "Widget"; Any item which can be used in the widget
01415  *   toolkit. Not all widgets are visible. In general widgets can not be
01416  *   sized directly, but this is controlled by a window. A widget has an
01417  *   internal size cache and if the value in the cache is not equal to 0,0
01418  *   that value is its best size. This value gets set when the widget can
01419  *   honour a resize request.  It will be set with the value which honors
01420  *   the request.
01421  *
01422  * - @ref gui2::tgrid "Grid"; A grid is an invisible container which holds
01423  *   one or more widgets.  Several widgets have a grid in them to hold
01424  *   multiple widgets eg panels and windows.
01425  *
01426  * - @ref gui2::tgrid::tchild "Grid cell"; Every widget which is in a grid is
01427  *   put in a grid cell. These cells also hold the information about the gaps
01428  *   between widgets the behaviour on growing etc. All grid cells must have a
01429  *   widget inside them.
01430  *
01431  * - @ref gui2::twindow "Window"; A window is a top level item which has a
01432  *   grid with its children. The window handles the sizing of the window and
01433  *   makes sure everything fits.
01434  *
01435  * - @ref gui2::twindow::tlinked_size "Shared size group"; A shared size
01436  *   group is a number of widgets which share width and or height. These
01437  *   widgets are handled separately in the layout algorithm. All grid cells
01438  *   width such a widget will get the same height and or width and these
01439  *   widgets won't be resized when there's not enough size. To be sure that
01440  *   these widgets don't cause trouble for the layout algorithm, they must be
01441  *   in a container with scrollbars so there will always be a way to properly
01442  *   layout them. The engine must enforce this restriction so the shared
01443  *   layout property must be set by the engine after validation.
01444  *
01445  * - All visible grid cells; A grid cell is visible when the widget inside
01446  *   of it doesn't have the state INVISIBLE. Widgets which are HIDDEN are
01447  *   sized properly since when they become VISIBLE the layout shouldn't be
01448  *   invalidated. A grid cell that's invisible has size 0,0.
01449  *
01450  * - All resizable grid cells; A grid cell is resizable under the following
01451  *   conditions:
01452  *   - The widget is VISIBLE.
01453  *   - The widget is not in a shared size group.
01454  *
01455  * There are two layout algorithms with a different purpose.
01456  *
01457  * - The Window algorithm; this algorithm's goal is it to make sure all grid
01458  *   cells fit in the window. Sizing the grid cells depends on the widget
01459  *   size as well, but this algorithm only sizes the grid cells and doesn't
01460  *   handle the widgets inside them.
01461  *
01462  * - The Grid algorithm; after the Window algorithm made sure that all grid
01463  *   cells fit this algorithm makes sure the widgets are put in the optimal
01464  *   state in their grid cell.
01465  *
01466  * @section layout_algorihm_window Window
01467  *
01468  * Here is the algorithm used to layout the window:
01469  *
01470  * - Perform a full initialization
01471  *   (@ref gui2::twidget::layout_init (full_initialization = true)):
01472  *   - Clear the internal best size cache for all widgets.
01473  *   - For widgets with scrollbars hide them unless the
01474  *     @ref gui2::tscrollbar_container::tscrollbar_mode "scrollbar_mode" is
01475  *     always_visible or auto_visible.
01476  * - Handle shared sizes:
01477  *   - Height and width:
01478  *     - Get the best size for all widgets that share height and width.
01479  *     - Set the maximum of width and height as best size for all these
01480  *       widgets.
01481  *   - Width only:
01482  *     - Get the best width for all widgets which share their width.
01483  *     - Set the maximum width for all widgets, but keep their own height.
01484  *   - Height only:
01485  *     - Get the best height for all widgets which share their height.
01486  *     - Set the maximum height for all widgets, but keep their own width.
01487  * - Start layout loop:
01488  *   - Get best size.
01489  *   - If width <= maximum_width && height <= maximum_height we're done.
01490  *   - If width > maximum_width, optimize the width:
01491  *     - For every grid cell in a grid row there will be a resize request
01492  *       (@ref gui2::tgrid::reduce_width):
01493  *       - Sort the widgets in the row on the resize priority.
01494  *         - Loop through this priority queue until the row fits
01495  *           - If priority != 0 try to share the extra width else all
01496  *             widgets are tried to reduce the full size.
01497  *           - Try to shrink the widgets by either wrapping or using a
01498  *             scrollbar (@ref gui2::twidget::request_reduce_width).
01499  *           - If the row fits in the wanted width this row is done.
01500  *           - Else try the next priority.
01501  *         - All priorities done and the width still doesn't fit.
01502  *         - Loop through this priority queue until the row fits.
01503  *           - If priority != 0:
01504  *             - try to share the extra width
01505  *           -Else:
01506  *             - All widgets are tried to reduce the full size.
01507  *           - Try to shrink the widgets by sizing them smaller as really
01508  *             wanted (@ref gui2::twidget::demand_reduce_width).
01509  *             For labels, buttons etc. they get ellipsized.
01510  *           - If the row fits in the wanted width this row is done.
01511  *           - Else try the next priority.
01512  *         - All priorities done and the width still doesn't fit.
01513  *         - Throw a layout width doesn't fit exception.
01514  *   - If height > maximum_height, optimize the height
01515  *       (@ref gui2::tgrid::reduce_height):
01516  *     - For every grid cell in a grid column there will be a resize request:
01517  *       - Sort the widgets in the column on the resize priority.
01518  *         - Loop through this priority queue until the column fits:
01519  *           - If priority != 0 try to share the extra height else all
01520  *              widgets are tried to reduce the full size.
01521  *           - Try to shrink the widgets by using a scrollbar
01522  *             (@ref gui2::twidget::request_reduce_height).
01523  *             - If succeeded for a widget the width is influenced and the
01524  *               width might be invalid.
01525  *             - Throw a width modified exception.
01526  *           - If the column fits in the wanted height this column is done.
01527  *           - Else try the next priority.
01528  *         - All priorities done and the height still doesn't fit.
01529  *         - Loop through this priority queue until the column fits.
01530  *           - If priority != 0 try to share the extra height else all
01531  *             widgets are tried to reduce the full size.
01532  *           - Try to shrink the widgets by sizing them smaller as really
01533  *             wanted (@ref gui2::twidget::demand_reduce_width).
01534  *             For labels, buttons etc. they get ellipsized .
01535  *           - If the column fits in the wanted height this column is done.
01536  *           - Else try the next priority.
01537  *         - All priorities done and the height still doesn't fit.
01538  *         - Throw a layout height doesn't fit exception.
01539  * - End layout loop.
01540  *
01541  * - Catch @ref gui2::tlayout_exception_width_modified "width modified":
01542  *   - Goto relayout.
01543  *
01544  * - Catch
01545  *   @ref gui2::tlayout_exception_width_resize_failed "width resize failed":
01546  *   - If the window has a horizontal scrollbar which isn't shown but can be
01547  *     shown.
01548  *     - Show the scrollbar.
01549  *     - goto relayout.
01550  *   - Else show a layout failure message.
01551  *
01552  * - Catch
01553  *   @ref gui2::tlayout_exception_height_resize_failed "height resize failed":
01554  *   - If the window has a vertical scrollbar which isn't shown but can be
01555  *     shown:
01556  *     - Show the scrollbar.
01557  *     - goto relayout.
01558  *   - Else:
01559  *     - show a layout failure message.
01560  *
01561  * - Relayout:
01562  *   - Initialize all widgets
01563  *     (@ref gui2::twidget::layout_init (full_initialization = false))
01564  *   - Handle shared sizes, since the reinitialization resets that state.
01565  *   - Goto start layout loop.
01566  *
01567  * @section grid Grid
01568  *
01569  * This section will be documented later.
01570  */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

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