window.cpp

Go to the documentation of this file.
00001 /* $Id: window.cpp 26024 2013-11-17 13:35:48Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014 #include "company_func.h"
00015 #include "gfx_func.h"
00016 #include "console_func.h"
00017 #include "console_gui.h"
00018 #include "viewport_func.h"
00019 #include "progress.h"
00020 #include "blitter/factory.hpp"
00021 #include "zoom_func.h"
00022 #include "vehicle_base.h"
00023 #include "window_func.h"
00024 #include "tilehighlight_func.h"
00025 #include "network/network.h"
00026 #include "querystring_gui.h"
00027 #include "widgets/dropdown_func.h"
00028 #include "strings_func.h"
00029 #include "settings_type.h"
00030 #include "newgrf_debug.h"
00031 #include "hotkeys.h"
00032 #include "toolbar_gui.h"
00033 #include "statusbar_gui.h"
00034 #include "error.h"
00035 #include "game/game.hpp"
00036 #include "video/video_driver.hpp"
00037 
00039 enum ViewportAutoscrolling {
00040   VA_DISABLED,                  
00041   VA_MAIN_VIEWPORT_FULLSCREEN,  
00042   VA_MAIN_VIEWPORT,             
00043   VA_EVERY_VIEWPORT,            
00044 };
00045 
00046 static Point _drag_delta; 
00047 static Window *_mouseover_last_w = NULL; 
00048 static Window *_last_scroll_window = NULL; 
00049 
00051 Window *_z_front_window = NULL;
00053 Window *_z_back_window  = NULL;
00054 
00056 bool _window_highlight_colour = false;
00057 
00058 /*
00059  * Window that currently has focus. - The main purpose is to generate
00060  * #FocusLost events, not to give next window in z-order focus when a
00061  * window is closed.
00062  */
00063 Window *_focused_window;
00064 
00065 Point _cursorpos_drag_start;
00066 
00067 int _scrollbar_start_pos;
00068 int _scrollbar_size;
00069 byte _scroller_click_timeout = 0;
00070 
00071 bool _scrolling_viewport;  
00072 bool _mouse_hovering;      
00073 
00074 SpecialMouseMode _special_mouse_mode; 
00075 
00077 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
00078       WindowClass window_class, WindowClass parent_class, uint32 flags,
00079       const NWidgetPart *nwid_parts, int16 nwid_length) :
00080   default_pos(def_pos),
00081   default_width(def_width),
00082   default_height(def_height),
00083   cls(window_class),
00084   parent_cls(parent_class),
00085   flags(flags),
00086   nwid_parts(nwid_parts),
00087   nwid_length(nwid_length)
00088 {
00089 }
00090 
00091 WindowDesc::~WindowDesc()
00092 {
00093 }
00094 
00104 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00105 {
00106   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00107   if (line_height < 0) line_height = wid->resize_y;
00108   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00109   return (clickpos - (int)wid->pos_y - padding) / line_height;
00110 }
00111 
00115 void Window::DisableAllWidgetHighlight()
00116 {
00117   for (uint i = 0; i < this->nested_array_size; i++) {
00118     NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00119     if (nwid == NULL) continue;
00120 
00121     if (nwid->IsHighlighted()) {
00122       nwid->SetHighlighted(TC_INVALID);
00123       this->SetWidgetDirty(i);
00124     }
00125   }
00126 
00127   CLRBITS(this->flags, WF_HIGHLIGHTED);
00128 }
00129 
00135 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
00136 {
00137   assert(widget_index < this->nested_array_size);
00138 
00139   NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00140   if (nwid == NULL) return;
00141 
00142   nwid->SetHighlighted(highlighted_colour);
00143   this->SetWidgetDirty(widget_index);
00144 
00145   if (highlighted_colour != TC_INVALID) {
00146     /* If we set a highlight, the window has a highlight */
00147     this->flags |= WF_HIGHLIGHTED;
00148   } else {
00149     /* If we disable a highlight, check all widgets if anyone still has a highlight */
00150     bool valid = false;
00151     for (uint i = 0; i < this->nested_array_size; i++) {
00152       NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00153       if (nwid == NULL) continue;
00154       if (!nwid->IsHighlighted()) continue;
00155 
00156       valid = true;
00157     }
00158     /* If nobody has a highlight, disable the flag on the window */
00159     if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
00160   }
00161 }
00162 
00168 bool Window::IsWidgetHighlighted(byte widget_index) const
00169 {
00170   assert(widget_index < this->nested_array_size);
00171 
00172   const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00173   if (nwid == NULL) return false;
00174 
00175   return nwid->IsHighlighted();
00176 }
00177 
00185 void Window::OnDropdownClose(Point pt, int widget, int index, bool instant_close)
00186 {
00187   if (widget < 0) return;
00188 
00189   if (instant_close) {
00190     /* Send event for selected option if we're still
00191      * on the parent button of the dropdown (behaviour of the dropdowns in the main toolbar). */
00192     if (GetWidgetFromPos(this, pt.x, pt.y) == widget) {
00193       this->OnDropdownSelect(widget, index);
00194     }
00195   }
00196 
00197   /* Raise the dropdown button */
00198   if (this->nested_array != NULL) {
00199     NWidgetCore *nwi2 = this->GetWidget<NWidgetCore>(widget);
00200     if ((nwi2->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
00201       nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
00202     } else {
00203       this->RaiseWidget(widget);
00204     }
00205   } else {
00206     this->RaiseWidget(widget);
00207   }
00208   this->SetWidgetDirty(widget);
00209 }
00210 
00216 const Scrollbar *Window::GetScrollbar(uint widnum) const
00217 {
00218   return this->GetWidget<NWidgetScrollbar>(widnum);
00219 }
00220 
00226 Scrollbar *Window::GetScrollbar(uint widnum)
00227 {
00228   return this->GetWidget<NWidgetScrollbar>(widnum);
00229 }
00230 
00236 const QueryString *Window::GetQueryString(uint widnum) const
00237 {
00238   const SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00239   return query != this->querystrings.End() ? query->second : NULL;
00240 }
00241 
00247 QueryString *Window::GetQueryString(uint widnum)
00248 {
00249   SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00250   return query != this->querystrings.End() ? query->second : NULL;
00251 }
00252 
00257 /* virtual */ const char *Window::GetFocusedText() const
00258 {
00259   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00260     return this->GetQueryString(this->nested_focus->index)->GetText();
00261   }
00262 
00263   return NULL;
00264 }
00265 
00270 /* virtual */ const char *Window::GetCaret() const
00271 {
00272   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00273     return this->GetQueryString(this->nested_focus->index)->GetCaret();
00274   }
00275 
00276   return NULL;
00277 }
00278 
00284 /* virtual */ const char *Window::GetMarkedText(size_t *length) const
00285 {
00286   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00287     return this->GetQueryString(this->nested_focus->index)->GetMarkedText(length);
00288   }
00289 
00290   return NULL;
00291 }
00292 
00297 /* virtual */ Point Window::GetCaretPosition() const
00298 {
00299   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00300     return this->GetQueryString(this->nested_focus->index)->GetCaretPosition(this, this->nested_focus->index);
00301   }
00302 
00303   Point pt = {0, 0};
00304   return pt;
00305 }
00306 
00313 /* virtual */ Rect Window::GetTextBoundingRect(const char *from, const char *to) const
00314 {
00315   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00316     return this->GetQueryString(this->nested_focus->index)->GetBoundingRect(this, this->nested_focus->index, from, to);
00317   }
00318 
00319   Rect r = {0, 0, 0, 0};
00320   return r;
00321 }
00322 
00328 /* virtual */ const char *Window::GetTextCharacterAtPosition(const Point &pt) const
00329 {
00330   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00331     return this->GetQueryString(this->nested_focus->index)->GetCharAtPosition(this, this->nested_focus->index, pt);
00332   }
00333 
00334   return NULL;
00335 }
00336 
00341 void SetFocusedWindow(Window *w)
00342 {
00343   if (_focused_window == w) return;
00344 
00345   /* Invalidate focused widget */
00346   if (_focused_window != NULL) {
00347     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00348   }
00349 
00350   /* Remember which window was previously focused */
00351   Window *old_focused = _focused_window;
00352   _focused_window = w;
00353 
00354   /* So we can inform it that it lost focus */
00355   if (old_focused != NULL) old_focused->OnFocusLost();
00356   if (_focused_window != NULL) _focused_window->OnFocus();
00357 }
00358 
00364 bool EditBoxInGlobalFocus()
00365 {
00366   if (_focused_window == NULL) return false;
00367 
00368   /* The console does not have an edit box so a special case is needed. */
00369   if (_focused_window->window_class == WC_CONSOLE) return true;
00370 
00371   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00372 }
00373 
00377 void Window::UnfocusFocusedWidget()
00378 {
00379   if (this->nested_focus != NULL) {
00380     if (this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
00381 
00382     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00383     this->nested_focus->SetDirty(this);
00384     this->nested_focus = NULL;
00385   }
00386 }
00387 
00393 bool Window::SetFocusedWidget(int widget_index)
00394 {
00395   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00396   if ((uint)widget_index >= this->nested_array_size) return false;
00397 
00398   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00399   if (this->nested_focus != NULL) {
00400     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00401 
00402     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00403     this->nested_focus->SetDirty(this);
00404     if (this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
00405   }
00406   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00407   return true;
00408 }
00409 
00413 void Window::OnFocusLost()
00414 {
00415   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
00416 }
00417 
00425 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00426 {
00427   va_list wdg_list;
00428 
00429   va_start(wdg_list, widgets);
00430 
00431   while (widgets != WIDGET_LIST_END) {
00432     SetWidgetDisabledState(widgets, disab_stat);
00433     widgets = va_arg(wdg_list, int);
00434   }
00435 
00436   va_end(wdg_list);
00437 }
00438 
00444 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00445 {
00446   va_list wdg_list;
00447 
00448   va_start(wdg_list, widgets);
00449 
00450   while (widgets != WIDGET_LIST_END) {
00451     SetWidgetLoweredState(widgets, lowered_stat);
00452     widgets = va_arg(wdg_list, int);
00453   }
00454 
00455   va_end(wdg_list);
00456 }
00457 
00462 void Window::RaiseButtons(bool autoraise)
00463 {
00464   for (uint i = 0; i < this->nested_array_size; i++) {
00465     if (this->nested_array[i] == NULL) continue;
00466     WidgetType type = this->nested_array[i]->type;
00467     if (((type & ~WWB_PUSHBUTTON) < WWT_LAST || type == NWID_PUSHBUTTON_DROPDOWN) &&
00468         (!autoraise || (type & WWB_PUSHBUTTON) || type == WWT_EDITBOX) && this->IsWidgetLowered(i)) {
00469       this->RaiseWidget(i);
00470       this->SetWidgetDirty(i);
00471     }
00472   }
00473 }
00474 
00479 void Window::SetWidgetDirty(byte widget_index) const
00480 {
00481   /* Sometimes this function is called before the window is even fully initialized */
00482   if (this->nested_array == NULL) return;
00483 
00484   this->nested_array[widget_index]->SetDirty(this);
00485 }
00486 
00492 void Window::HandleButtonClick(byte widget)
00493 {
00494   this->LowerWidget(widget);
00495   this->SetTimeout();
00496   this->SetWidgetDirty(widget);
00497 }
00498 
00499 static void StartWindowDrag(Window *w);
00500 static void StartWindowSizing(Window *w, bool to_left);
00501 
00509 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00510 {
00511   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00512   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00513 
00514   bool focused_widget_changed = false;
00515   /* If clicked on a window that previously did dot have focus */
00516   if (_focused_window != w &&                 // We already have focus, right?
00517       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00518       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00519     focused_widget_changed = true;
00520     SetFocusedWindow(w);
00521     w->OnFocus();
00522   }
00523 
00524   if (nw == NULL) return; // exit if clicked outside of widgets
00525 
00526   /* don't allow any interaction if the button has been disabled */
00527   if (nw->IsDisabled()) return;
00528 
00529   int widget_index = nw->index; 
00530 
00531   /* Clicked on a widget that is not disabled.
00532    * So unless the clicked widget is the caption bar, change focus to this widget.
00533    * Exception: In the OSK we always want the editbox to stay focussed. */
00534   if (widget_type != WWT_CAPTION && w->window_class != WC_OSK) {
00535     /* focused_widget_changed is 'now' only true if the window this widget
00536      * is in gained focus. In that case it must remain true, also if the
00537      * local widget focus did not change. As such it's the logical-or of
00538      * both changed states.
00539      *
00540      * If this is not preserved, then the OSK window would be opened when
00541      * a user has the edit box focused and then click on another window and
00542      * then back again on the edit box (to type some text).
00543      */
00544     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00545   }
00546 
00547   /* Close any child drop down menus. If the button pressed was the drop down
00548    * list's own button, then we should not process the click any further. */
00549   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00550 
00551   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00552 
00553   Point pt = { x, y };
00554 
00555   switch (widget_type) {
00556     case NWID_VSCROLLBAR:
00557     case NWID_HSCROLLBAR:
00558       ScrollbarClickHandler(w, nw, x, y);
00559       break;
00560 
00561     case WWT_EDITBOX: {
00562       QueryString *query = w->GetQueryString(widget_index);
00563       if (query != NULL) query->ClickEditBox(w, pt, widget_index, click_count, focused_widget_changed);
00564       break;
00565     }
00566 
00567     case WWT_CLOSEBOX: // 'X'
00568       delete w;
00569       return;
00570 
00571     case WWT_CAPTION: // 'Title bar'
00572       StartWindowDrag(w);
00573       return;
00574 
00575     case WWT_RESIZEBOX:
00576       /* When the resize widget is on the left size of the window
00577        * we assume that that button is used to resize to the left. */
00578       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00579       nw->SetDirty(w);
00580       return;
00581 
00582     case WWT_DEBUGBOX:
00583       w->ShowNewGRFInspectWindow();
00584       break;
00585 
00586     case WWT_SHADEBOX:
00587       nw->SetDirty(w);
00588       w->SetShaded(!w->IsShaded());
00589       return;
00590 
00591     case WWT_STICKYBOX:
00592       w->flags ^= WF_STICKY;
00593       nw->SetDirty(w);
00594       return;
00595 
00596     default:
00597       break;
00598   }
00599 
00600   /* Widget has no index, so the window is not interested in it. */
00601   if (widget_index < 0) return;
00602 
00603   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
00604   if (w->IsWidgetHighlighted(widget_index)) {
00605     w->SetWidgetHighlight(widget_index, TC_INVALID);
00606     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
00607   }
00608 
00609   w->OnClick(pt, widget_index, click_count);
00610 }
00611 
00618 static void DispatchRightClickEvent(Window *w, int x, int y)
00619 {
00620   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00621   if (wid == NULL) return;
00622 
00623   /* No widget to handle, or the window is not interested in it. */
00624   if (wid->index >= 0) {
00625     Point pt = { x, y };
00626     if (w->OnRightClick(pt, wid->index)) return;
00627   }
00628 
00629   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00630 }
00631 
00638 static void DispatchHoverEvent(Window *w, int x, int y)
00639 {
00640   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00641 
00642   /* No widget to handle */
00643   if (wid == NULL) return;
00644 
00645   /* Show the tooltip if there is any */
00646   if (wid->tool_tip != 0) {
00647     GuiShowTooltips(w, wid->tool_tip);
00648     return;
00649   }
00650 
00651   /* Widget has no index, so the window is not interested in it. */
00652   if (wid->index < 0) return;
00653 
00654   Point pt = { x, y };
00655   w->OnHover(pt, wid->index);
00656 }
00657 
00665 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
00666 {
00667   if (nwid == NULL) return;
00668 
00669   /* Using wheel on caption/shade-box shades or unshades the window. */
00670   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00671     w->SetShaded(wheel < 0);
00672     return;
00673   }
00674 
00675   /* Wheeling a vertical scrollbar. */
00676   if (nwid->type == NWID_VSCROLLBAR) {
00677     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
00678     if (sb->GetCount() > sb->GetCapacity()) {
00679       sb->UpdatePosition(wheel);
00680       w->SetDirty();
00681     }
00682     return;
00683   }
00684 
00685   /* Scroll the widget attached to the scrollbar. */
00686   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00687   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00688     sb->UpdatePosition(wheel);
00689     w->SetDirty();
00690   }
00691 }
00692 
00698 static bool MayBeShown(const Window *w)
00699 {
00700   /* If we're not modal, everything is okay. */
00701   if (!HasModalProgress()) return true;
00702 
00703   switch (w->window_class) {
00704     case WC_MAIN_WINDOW:    
00705     case WC_MODAL_PROGRESS: 
00706     case WC_CONFIRM_POPUP_QUERY: 
00707       return true;
00708 
00709     default:
00710       return false;
00711   }
00712 }
00713 
00726 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00727 {
00728   const Window *v;
00729   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00730     if (MayBeShown(v) &&
00731         right > v->left &&
00732         bottom > v->top &&
00733         left < v->left + v->width &&
00734         top < v->top + v->height) {
00735       /* v and rectangle intersect with each other */
00736       int x;
00737 
00738       if (left < (x = v->left)) {
00739         DrawOverlappedWindow(w, left, top, x, bottom);
00740         DrawOverlappedWindow(w, x, top, right, bottom);
00741         return;
00742       }
00743 
00744       if (right > (x = v->left + v->width)) {
00745         DrawOverlappedWindow(w, left, top, x, bottom);
00746         DrawOverlappedWindow(w, x, top, right, bottom);
00747         return;
00748       }
00749 
00750       if (top < (x = v->top)) {
00751         DrawOverlappedWindow(w, left, top, right, x);
00752         DrawOverlappedWindow(w, left, x, right, bottom);
00753         return;
00754       }
00755 
00756       if (bottom > (x = v->top + v->height)) {
00757         DrawOverlappedWindow(w, left, top, right, x);
00758         DrawOverlappedWindow(w, left, x, right, bottom);
00759         return;
00760       }
00761 
00762       return;
00763     }
00764   }
00765 
00766   /* Setup blitter, and dispatch a repaint event to window *wz */
00767   DrawPixelInfo *dp = _cur_dpi;
00768   dp->width = right - left;
00769   dp->height = bottom - top;
00770   dp->left = left - w->left;
00771   dp->top = top - w->top;
00772   dp->pitch = _screen.pitch;
00773   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00774   dp->zoom = ZOOM_LVL_NORMAL;
00775   w->OnPaint();
00776 }
00777 
00786 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00787 {
00788   Window *w;
00789   DrawPixelInfo bk;
00790   _cur_dpi = &bk;
00791 
00792   FOR_ALL_WINDOWS_FROM_BACK(w) {
00793     if (MayBeShown(w) &&
00794         right > w->left &&
00795         bottom > w->top &&
00796         left < w->left + w->width &&
00797         top < w->top + w->height) {
00798       /* Window w intersects with the rectangle => needs repaint */
00799       DrawOverlappedWindow(w, left, top, right, bottom);
00800     }
00801   }
00802 }
00803 
00808 void Window::SetDirty() const
00809 {
00810   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00811 }
00812 
00819 void Window::ReInit(int rx, int ry)
00820 {
00821   this->SetDirty(); // Mark whole current window as dirty.
00822 
00823   /* Save current size. */
00824   int window_width  = this->width;
00825   int window_height = this->height;
00826 
00827   this->OnInit();
00828   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00829   this->nested_root->SetupSmallestSize(this, false);
00830   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00831   this->width  = this->nested_root->smallest_x;
00832   this->height = this->nested_root->smallest_y;
00833   this->resize.step_width  = this->nested_root->resize_x;
00834   this->resize.step_height = this->nested_root->resize_y;
00835 
00836   /* Resize as close to the original size + requested resize as possible. */
00837   window_width  = max(window_width  + rx, this->width);
00838   window_height = max(window_height + ry, this->height);
00839   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00840   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00841   /* dx and dy has to go by step.. calculate it.
00842    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00843   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00844   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00845 
00846   ResizeWindow(this, dx, dy);
00847   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00848 }
00849 
00855 void Window::SetShaded(bool make_shaded)
00856 {
00857   if (this->shade_select == NULL) return;
00858 
00859   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00860   if (this->shade_select->shown_plane != desired) {
00861     if (make_shaded) {
00862       this->unshaded_size.width  = this->width;
00863       this->unshaded_size.height = this->height;
00864       this->shade_select->SetDisplayedPlane(desired);
00865       this->ReInit(0, -this->height);
00866     } else {
00867       this->shade_select->SetDisplayedPlane(desired);
00868       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00869       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00870       this->ReInit(dx, dy);
00871     }
00872   }
00873 }
00874 
00881 static Window *FindChildWindow(const Window *w, WindowClass wc)
00882 {
00883   Window *v;
00884   FOR_ALL_WINDOWS_FROM_BACK(v) {
00885     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00886   }
00887 
00888   return NULL;
00889 }
00890 
00895 void Window::DeleteChildWindows(WindowClass wc) const
00896 {
00897   Window *child = FindChildWindow(this, wc);
00898   while (child != NULL) {
00899     delete child;
00900     child = FindChildWindow(this, wc);
00901   }
00902 }
00903 
00907 Window::~Window()
00908 {
00909   if (_thd.window_class == this->window_class &&
00910       _thd.window_number == this->window_number) {
00911     ResetObjectToPlace();
00912   }
00913 
00914   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00915   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00916 
00917   /* We can't scroll the window when it's closed. */
00918   if (_last_scroll_window == this) _last_scroll_window = NULL;
00919 
00920   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00921   if (_focused_window == this) {
00922     this->OnFocusLost();
00923     _focused_window = NULL;
00924   }
00925 
00926   this->DeleteChildWindows();
00927 
00928   if (this->viewport != NULL) DeleteWindowViewport(this);
00929 
00930   this->SetDirty();
00931 
00932   free(this->nested_array); // Contents is released through deletion of #nested_root.
00933   delete this->nested_root;
00934 
00935   this->window_class = WC_INVALID;
00936 }
00937 
00944 Window *FindWindowById(WindowClass cls, WindowNumber number)
00945 {
00946   Window *w;
00947   FOR_ALL_WINDOWS_FROM_BACK(w) {
00948     if (w->window_class == cls && w->window_number == number) return w;
00949   }
00950 
00951   return NULL;
00952 }
00953 
00960 Window *FindWindowByClass(WindowClass cls)
00961 {
00962   Window *w;
00963   FOR_ALL_WINDOWS_FROM_BACK(w) {
00964     if (w->window_class == cls) return w;
00965   }
00966 
00967   return NULL;
00968 }
00969 
00976 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00977 {
00978   Window *w = FindWindowById(cls, number);
00979   if (force || w == NULL ||
00980       (w->flags & WF_STICKY) == 0) {
00981     delete w;
00982   }
00983 }
00984 
00989 void DeleteWindowByClass(WindowClass cls)
00990 {
00991   Window *w;
00992 
00993 restart_search:
00994   /* When we find the window to delete, we need to restart the search
00995    * as deleting this window could cascade in deleting (many) others
00996    * anywhere in the z-array */
00997   FOR_ALL_WINDOWS_FROM_BACK(w) {
00998     if (w->window_class == cls) {
00999       delete w;
01000       goto restart_search;
01001     }
01002   }
01003 }
01004 
01011 void DeleteCompanyWindows(CompanyID id)
01012 {
01013   Window *w;
01014 
01015 restart_search:
01016   /* When we find the window to delete, we need to restart the search
01017    * as deleting this window could cascade in deleting (many) others
01018    * anywhere in the z-array */
01019   FOR_ALL_WINDOWS_FROM_BACK(w) {
01020     if (w->owner == id) {
01021       delete w;
01022       goto restart_search;
01023     }
01024   }
01025 
01026   /* Also delete the company specific windows that don't have a company-colour. */
01027   DeleteWindowById(WC_BUY_COMPANY, id);
01028 }
01029 
01037 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
01038 {
01039   Window *w;
01040   FOR_ALL_WINDOWS_FROM_BACK(w) {
01041     if (w->owner != old_owner) continue;
01042 
01043     switch (w->window_class) {
01044       case WC_COMPANY_COLOUR:
01045       case WC_FINANCES:
01046       case WC_STATION_LIST:
01047       case WC_TRAINS_LIST:
01048       case WC_ROADVEH_LIST:
01049       case WC_SHIPS_LIST:
01050       case WC_AIRCRAFT_LIST:
01051       case WC_BUY_COMPANY:
01052       case WC_COMPANY:
01053       case WC_COMPANY_INFRASTRUCTURE:
01054         continue;
01055 
01056       default:
01057         w->owner = new_owner;
01058         break;
01059     }
01060   }
01061 }
01062 
01063 static void BringWindowToFront(Window *w);
01064 
01072 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
01073 {
01074   Window *w = FindWindowById(cls, number);
01075 
01076   if (w != NULL) {
01077     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
01078 
01079     w->SetWhiteBorder();
01080     BringWindowToFront(w);
01081     w->SetDirty();
01082   }
01083 
01084   return w;
01085 }
01086 
01087 static inline bool IsVitalWindow(const Window *w)
01088 {
01089   switch (w->window_class) {
01090     case WC_MAIN_TOOLBAR:
01091     case WC_STATUS_BAR:
01092     case WC_NEWS_WINDOW:
01093     case WC_SEND_NETWORK_MSG:
01094       return true;
01095 
01096     default:
01097       return false;
01098   }
01099 }
01100 
01109 static uint GetWindowZPriority(const Window *w)
01110 {
01111   assert(w->window_class != WC_INVALID);
01112 
01113   uint z_priority = 0;
01114 
01115   switch (w->window_class) {
01116     case WC_ENDSCREEN:
01117       ++z_priority;
01118 
01119     case WC_HIGHSCORE:
01120       ++z_priority;
01121 
01122     case WC_TOOLTIPS:
01123       ++z_priority;
01124 
01125     case WC_DROPDOWN_MENU:
01126       ++z_priority;
01127 
01128     case WC_MAIN_TOOLBAR:
01129     case WC_STATUS_BAR:
01130       ++z_priority;
01131 
01132     case WC_OSK:
01133       ++z_priority;
01134 
01135     case WC_QUERY_STRING:
01136     case WC_SEND_NETWORK_MSG:
01137       ++z_priority;
01138 
01139     case WC_ERRMSG:
01140     case WC_CONFIRM_POPUP_QUERY:
01141     case WC_MODAL_PROGRESS:
01142     case WC_NETWORK_STATUS_WINDOW:
01143       ++z_priority;
01144 
01145     case WC_GENERATE_LANDSCAPE:
01146     case WC_SAVELOAD:
01147     case WC_GAME_OPTIONS:
01148     case WC_CUSTOM_CURRENCY:
01149     case WC_NETWORK_WINDOW:
01150     case WC_GRF_PARAMETERS:
01151     case WC_AI_LIST:
01152     case WC_AI_SETTINGS:
01153     case WC_TEXTFILE:
01154       ++z_priority;
01155 
01156     case WC_CONSOLE:
01157       ++z_priority;
01158 
01159     case WC_NEWS_WINDOW:
01160       ++z_priority;
01161 
01162     default:
01163       ++z_priority;
01164 
01165     case WC_MAIN_WINDOW:
01166       return z_priority;
01167   }
01168 }
01169 
01174 static void AddWindowToZOrdering(Window *w)
01175 {
01176   assert(w->z_front == NULL && w->z_back == NULL);
01177 
01178   if (_z_front_window == NULL) {
01179     /* It's the only window. */
01180     _z_front_window = _z_back_window = w;
01181     w->z_front = w->z_back = NULL;
01182   } else {
01183     /* Search down the z-ordering for its location. */
01184     Window *v = _z_front_window;
01185     uint last_z_priority = UINT_MAX;
01186     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
01187       if (v->window_class != WC_INVALID) {
01188         /* Sanity check z-ordering, while we're at it. */
01189         assert(last_z_priority >= GetWindowZPriority(v));
01190         last_z_priority = GetWindowZPriority(v);
01191       }
01192 
01193       v = v->z_back;
01194     }
01195 
01196     if (v == NULL) {
01197       /* It's the new back window. */
01198       w->z_front = _z_back_window;
01199       w->z_back = NULL;
01200       _z_back_window->z_back = w;
01201       _z_back_window = w;
01202     } else if (v == _z_front_window) {
01203       /* It's the new front window. */
01204       w->z_front = NULL;
01205       w->z_back = _z_front_window;
01206       _z_front_window->z_front = w;
01207       _z_front_window = w;
01208     } else {
01209       /* It's somewhere else in the z-ordering. */
01210       w->z_front = v->z_front;
01211       w->z_back = v;
01212       v->z_front->z_back = w;
01213       v->z_front = w;
01214     }
01215   }
01216 }
01217 
01218 
01223 static void RemoveWindowFromZOrdering(Window *w)
01224 {
01225   if (w->z_front == NULL) {
01226     assert(_z_front_window == w);
01227     _z_front_window = w->z_back;
01228   } else {
01229     w->z_front->z_back = w->z_back;
01230   }
01231 
01232   if (w->z_back == NULL) {
01233     assert(_z_back_window == w);
01234     _z_back_window = w->z_front;
01235   } else {
01236     w->z_back->z_front = w->z_front;
01237   }
01238 
01239   w->z_front = w->z_back = NULL;
01240 }
01241 
01247 static void BringWindowToFront(Window *w)
01248 {
01249   RemoveWindowFromZOrdering(w);
01250   AddWindowToZOrdering(w);
01251 
01252   w->SetDirty();
01253 }
01254 
01263 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
01264 {
01265   /* Set up window properties; some of them are needed to set up smallest size below */
01266   this->window_class = desc->cls;
01267   this->SetWhiteBorder();
01268   if (desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
01269   this->owner = INVALID_OWNER;
01270   this->nested_focus = NULL;
01271   this->window_number = window_number;
01272   this->desc_flags = desc->flags;
01273 
01274   this->OnInit();
01275   /* Initialize nested widget tree. */
01276   if (this->nested_array == NULL) {
01277     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01278     this->nested_root->SetupSmallestSize(this, true);
01279   } else {
01280     this->nested_root->SetupSmallestSize(this, false);
01281   }
01282   /* Initialize to smallest size. */
01283   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
01284 
01285   /* Further set up window properties,
01286    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
01287   this->resize.step_width  = this->nested_root->resize_x;
01288   this->resize.step_height = this->nested_root->resize_y;
01289 
01290   /* Give focus to the opened window unless a text box
01291    * of focused window has focus (so we don't interrupt typing). But if the new
01292    * window has a text box, then take focus anyway. */
01293   if (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL) SetFocusedWindow(this);
01294 
01295   /* Insert the window into the correct location in the z-ordering. */
01296   AddWindowToZOrdering(this);
01297 }
01298 
01306 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
01307 {
01308   this->left = x;
01309   this->top = y;
01310   this->width = sm_width;
01311   this->height = sm_height;
01312 }
01313 
01324 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
01325 {
01326   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
01327   def_height = max(def_height, this->height);
01328   /* Try to make windows smaller when our window is too small.
01329    * w->(width|height) is normally the same as min_(width|height),
01330    * but this way the GUIs can be made a little more dynamic;
01331    * one can use the same spec for multiple windows and those
01332    * can then determine the real minimum size of the window. */
01333   if (this->width != def_width || this->height != def_height) {
01334     /* Think about the overlapping toolbars when determining the minimum window size */
01335     int free_height = _screen.height;
01336     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01337     if (wt != NULL) free_height -= wt->height;
01338     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01339     if (wt != NULL) free_height -= wt->height;
01340 
01341     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
01342     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
01343 
01344     /* X and Y has to go by step.. calculate it.
01345      * The cast to int is necessary else x/y are implicitly casted to
01346      * unsigned int, which won't work. */
01347     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01348     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01349 
01350     ResizeWindow(this, enlarge_x, enlarge_y);
01351     /* ResizeWindow() calls this->OnResize(). */
01352   } else {
01353     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
01354     this->OnResize();
01355   }
01356 
01357   int nx = this->left;
01358   int ny = this->top;
01359 
01360   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01361 
01362   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01363   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01364   nx = max(nx, 0);
01365 
01366   if (this->viewport != NULL) {
01367     this->viewport->left += nx - this->left;
01368     this->viewport->top  += ny - this->top;
01369   }
01370   this->left = nx;
01371   this->top = ny;
01372 
01373   this->SetDirty();
01374 }
01375 
01387 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01388 {
01389   int right  = width + left;
01390   int bottom = height + top;
01391 
01392   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01393   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01394 
01395   /* Make sure it is not obscured by any window. */
01396   const Window *w;
01397   FOR_ALL_WINDOWS_FROM_BACK(w) {
01398     if (w->window_class == WC_MAIN_WINDOW) continue;
01399 
01400     if (right > w->left &&
01401         w->left + w->width > left &&
01402         bottom > w->top &&
01403         w->top + w->height > top) {
01404       return false;
01405     }
01406   }
01407 
01408   pos.x = left;
01409   pos.y = top;
01410   return true;
01411 }
01412 
01424 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01425 {
01426   /* Left part of the rectangle may be at most 1/4 off-screen,
01427    * right part of the rectangle may be at most 1/2 off-screen
01428    */
01429   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01430   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01431   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01432 
01433   /* Make sure it is not obscured by any window. */
01434   const Window *w;
01435   FOR_ALL_WINDOWS_FROM_BACK(w) {
01436     if (w->window_class == WC_MAIN_WINDOW) continue;
01437 
01438     if (left + width > w->left &&
01439         w->left + w->width > left &&
01440         top + height > w->top &&
01441         w->top + w->height > top) {
01442       return false;
01443     }
01444   }
01445 
01446   pos.x = left;
01447   pos.y = top;
01448   return true;
01449 }
01450 
01457 static Point GetAutoPlacePosition(int width, int height)
01458 {
01459   Point pt;
01460 
01461   /* First attempt, try top-left of the screen */
01462   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01463   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01464 
01465   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01466    * The new window must be entirely on-screen, and not overlap with an existing window.
01467    * Eight starting points are tried, two at each corner.
01468    */
01469   const Window *w;
01470   FOR_ALL_WINDOWS_FROM_BACK(w) {
01471     if (w->window_class == WC_MAIN_WINDOW) continue;
01472 
01473     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01474     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01475     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01476     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01477     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01478     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01479     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01480     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01481   }
01482 
01483   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01484    * The new window may be partly off-screen, and must not overlap with an existing window.
01485    * Only four starting points are tried.
01486    */
01487   FOR_ALL_WINDOWS_FROM_BACK(w) {
01488     if (w->window_class == WC_MAIN_WINDOW) continue;
01489 
01490     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01491     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01492     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01493     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01494   }
01495 
01496   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01497    * of (+5, +5)
01498    */
01499   int left = 0, top = 24;
01500 
01501 restart:
01502   FOR_ALL_WINDOWS_FROM_BACK(w) {
01503     if (w->left == left && w->top == top) {
01504       left += 5;
01505       top += 5;
01506       goto restart;
01507     }
01508   }
01509 
01510   pt.x = left;
01511   pt.y = top;
01512   return pt;
01513 }
01514 
01521 Point GetToolbarAlignedWindowPosition(int window_width)
01522 {
01523   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01524   assert(w != NULL);
01525   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01526   return pt;
01527 }
01528 
01546 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01547 {
01548   Point pt;
01549   const Window *w;
01550 
01551   int16 default_width  = max(desc->default_width,  sm_width);
01552   int16 default_height = max(desc->default_height, sm_height);
01553 
01554   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01555       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01556       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01557 
01558     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01559     if (pt.x > _screen.width + 10 - default_width) {
01560       pt.x = (_screen.width + 10 - default_width) - 20;
01561     }
01562     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01563     return pt;
01564   }
01565 
01566   switch (desc->default_pos) {
01567     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01568       return GetToolbarAlignedWindowPosition(default_width);
01569 
01570     case WDP_AUTO: // Find a good automatic position for the window
01571       return GetAutoPlacePosition(default_width, default_height);
01572 
01573     case WDP_CENTER: // Centre the window horizontally
01574       pt.x = (_screen.width - default_width) / 2;
01575       pt.y = (_screen.height - default_height) / 2;
01576       break;
01577 
01578     case WDP_MANUAL:
01579       pt.x = 0;
01580       pt.y = 0;
01581       break;
01582 
01583     default:
01584       NOT_REACHED();
01585   }
01586 
01587   return pt;
01588 }
01589 
01590 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01591 {
01592   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
01593 }
01594 
01603 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
01604 {
01605   int biggest_index = -1;
01606   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
01607   this->nested_array_size = (uint)(biggest_index + 1);
01608 
01609   if (fill_nested) {
01610     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01611     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01612   }
01613 }
01614 
01620 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
01621 {
01622   this->InitializeData(desc, window_number);
01623   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01624   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01625   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
01626 }
01627 
01633 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
01634 {
01635   this->CreateNestedTree(desc, false);
01636   this->FinishInitNested(desc, window_number);
01637 }
01638 
01640 Window::Window() : scrolling_scrollbar(-1)
01641 {
01642 }
01643 
01651 Window *FindWindowFromPt(int x, int y)
01652 {
01653   Window *w;
01654   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01655     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01656       return w;
01657     }
01658   }
01659 
01660   return NULL;
01661 }
01662 
01666 void InitWindowSystem()
01667 {
01668   IConsoleClose();
01669 
01670   _z_back_window = NULL;
01671   _z_front_window = NULL;
01672   _focused_window = NULL;
01673   _mouseover_last_w = NULL;
01674   _last_scroll_window = NULL;
01675   _scrolling_viewport = false;
01676   _mouse_hovering = false;
01677 
01678   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01679   NWidgetScrollbar::InvalidateDimensionCache();
01680 
01681   ShowFirstError();
01682 }
01683 
01687 void UnInitWindowSystem()
01688 {
01689   UnshowCriticalError();
01690 
01691   Window *w;
01692   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01693 
01694   for (w = _z_front_window; w != NULL; /* nothing */) {
01695     Window *to_del = w;
01696     w = w->z_back;
01697     free(to_del);
01698   }
01699 
01700   _z_front_window = NULL;
01701   _z_back_window = NULL;
01702 }
01703 
01707 void ResetWindowSystem()
01708 {
01709   UnInitWindowSystem();
01710   InitWindowSystem();
01711   _thd.Reset();
01712 }
01713 
01714 static void DecreaseWindowCounters()
01715 {
01716   Window *w;
01717   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01718     if (_scroller_click_timeout == 0) {
01719       /* Unclick scrollbar buttons if they are pressed. */
01720       for (uint i = 0; i < w->nested_array_size; i++) {
01721         NWidgetBase *nwid = w->nested_array[i];
01722         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01723           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01724           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01725             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01726             w->scrolling_scrollbar = -1;
01727             sb->SetDirty(w);
01728           }
01729         }
01730       }
01731     }
01732 
01733     /* Handle editboxes */
01734     for (SmallMap<int, QueryString*>::Pair *it = w->querystrings.Begin(); it != w->querystrings.End(); ++it) {
01735       it->second->HandleEditBox(w, it->first);
01736     }
01737 
01738     w->OnMouseLoop();
01739   }
01740 
01741   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01742     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
01743       CLRBITS(w->flags, WF_TIMEOUT);
01744 
01745       w->OnTimeout();
01746       w->RaiseButtons(true);
01747     }
01748   }
01749 }
01750 
01751 static void HandlePlacePresize()
01752 {
01753   if (_special_mouse_mode != WSM_PRESIZE) return;
01754 
01755   Window *w = _thd.GetCallbackWnd();
01756   if (w == NULL) return;
01757 
01758   Point pt = GetTileBelowCursor();
01759   if (pt.x == -1) {
01760     _thd.selend.x = -1;
01761     return;
01762   }
01763 
01764   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01765 }
01766 
01771 static EventState HandleMouseDragDrop()
01772 {
01773   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01774 
01775   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
01776 
01777   Window *w = _thd.GetCallbackWnd();
01778   if (w != NULL) {
01779     /* Send an event in client coordinates. */
01780     Point pt;
01781     pt.x = _cursor.pos.x - w->left;
01782     pt.y = _cursor.pos.y - w->top;
01783     if (_left_button_down) {
01784       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01785     } else {
01786       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01787     }
01788   }
01789 
01790   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
01791   return ES_HANDLED;
01792 }
01793 
01795 static void HandleMouseOver()
01796 {
01797   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01798 
01799   /* We changed window, put a MOUSEOVER event to the last window */
01800   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01801     /* Reset mouse-over coordinates of previous window */
01802     Point pt = { -1, -1 };
01803     _mouseover_last_w->OnMouseOver(pt, 0);
01804   }
01805 
01806   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01807   _mouseover_last_w = w;
01808 
01809   if (w != NULL) {
01810     /* send an event in client coordinates. */
01811     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01812     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01813     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01814   }
01815 }
01816 
01818 static const int MIN_VISIBLE_TITLE_BAR = 13;
01819 
01821 enum PreventHideDirection {
01822   PHD_UP,   
01823   PHD_DOWN, 
01824 };
01825 
01836 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01837 {
01838   if (v == NULL) return;
01839 
01840   int v_bottom = v->top + v->height;
01841   int v_right = v->left + v->width;
01842   int safe_y = (dir == PHD_UP) ? (v->top - MIN_VISIBLE_TITLE_BAR - rect.top) : (v_bottom + MIN_VISIBLE_TITLE_BAR - rect.bottom); // Compute safe vertical position.
01843 
01844   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01845   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01846 
01847   /* Vertically, the rectangle is hidden behind v. */
01848   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01849     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01850     return;
01851   }
01852   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01853     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01854     return;
01855   }
01856 
01857   /* Horizontally also hidden, force movement to a safe area. */
01858   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01859     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01860   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01861     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01862   } else {
01863     *ny = safe_y;
01864   }
01865 }
01866 
01874 static void EnsureVisibleCaption(Window *w, int nx, int ny)
01875 {
01876   /* Search for the title bar rectangle. */
01877   Rect caption_rect;
01878   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01879   if (caption != NULL) {
01880     caption_rect.left   = caption->pos_x;
01881     caption_rect.right  = caption->pos_x + caption->current_x;
01882     caption_rect.top    = caption->pos_y;
01883     caption_rect.bottom = caption->pos_y + caption->current_y;
01884 
01885     /* Make sure the window doesn't leave the screen */
01886     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01887     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01888 
01889     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01890     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01891     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01892   }
01893 
01894   if (w->viewport != NULL) {
01895     w->viewport->left += nx - w->left;
01896     w->viewport->top  += ny - w->top;
01897   }
01898 
01899   w->left = nx;
01900   w->top  = ny;
01901 }
01902 
01913 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
01914 {
01915   if (delta_x != 0 || delta_y != 0) {
01916     if (clamp_to_screen) {
01917       /* Determine the new right/bottom position. If that is outside of the bounds of
01918        * the resolution clamp it in such a manner that it stays within the bounds. */
01919       int new_right  = w->left + w->width  + delta_x;
01920       int new_bottom = w->top  + w->height + delta_y;
01921       if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
01922       if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
01923     }
01924 
01925     w->SetDirty();
01926 
01927     uint new_xinc = max(0, (w->nested_root->resize_x == 0) ? 0 : (int)(w->nested_root->current_x - w->nested_root->smallest_x) + delta_x);
01928     uint new_yinc = max(0, (w->nested_root->resize_y == 0) ? 0 : (int)(w->nested_root->current_y - w->nested_root->smallest_y) + delta_y);
01929     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01930     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01931 
01932     w->nested_root->AssignSizePosition(ST_RESIZE, 0, 0, w->nested_root->smallest_x + new_xinc, w->nested_root->smallest_y + new_yinc, _current_text_dir == TD_RTL);
01933     w->width  = w->nested_root->current_x;
01934     w->height = w->nested_root->current_y;
01935   }
01936 
01937   EnsureVisibleCaption(w, w->left, w->top);
01938 
01939   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
01940   w->OnResize();
01941   w->SetDirty();
01942 }
01943 
01949 int GetMainViewTop()
01950 {
01951   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01952   return (w == NULL) ? 0 : w->top + w->height;
01953 }
01954 
01960 int GetMainViewBottom()
01961 {
01962   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01963   return (w == NULL) ? _screen.height : w->top;
01964 }
01965 
01966 static bool _dragging_window; 
01967 
01972 static EventState HandleWindowDragging()
01973 {
01974   /* Get out immediately if no window is being dragged at all. */
01975   if (!_dragging_window) return ES_NOT_HANDLED;
01976 
01977   /* If button still down, but cursor hasn't moved, there is nothing to do. */
01978   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01979 
01980   /* Otherwise find the window... */
01981   Window *w;
01982   FOR_ALL_WINDOWS_FROM_BACK(w) {
01983     if (w->flags & WF_DRAGGING) {
01984       /* Stop the dragging if the left mouse button was released */
01985       if (!_left_button_down) {
01986         w->flags &= ~WF_DRAGGING;
01987         break;
01988       }
01989 
01990       w->SetDirty();
01991 
01992       int x = _cursor.pos.x + _drag_delta.x;
01993       int y = _cursor.pos.y + _drag_delta.y;
01994       int nx = x;
01995       int ny = y;
01996 
01997       if (_settings_client.gui.window_snap_radius != 0) {
01998         const Window *v;
01999 
02000         int hsnap = _settings_client.gui.window_snap_radius;
02001         int vsnap = _settings_client.gui.window_snap_radius;
02002         int delta;
02003 
02004         FOR_ALL_WINDOWS_FROM_BACK(v) {
02005           if (v == w) continue; // Don't snap at yourself
02006 
02007           if (y + w->height > v->top && y < v->top + v->height) {
02008             /* Your left border <-> other right border */
02009             delta = abs(v->left + v->width - x);
02010             if (delta <= hsnap) {
02011               nx = v->left + v->width;
02012               hsnap = delta;
02013             }
02014 
02015             /* Your right border <-> other left border */
02016             delta = abs(v->left - x - w->width);
02017             if (delta <= hsnap) {
02018               nx = v->left - w->width;
02019               hsnap = delta;
02020             }
02021           }
02022 
02023           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
02024             /* Your left border <-> other left border */
02025             delta = abs(v->left - x);
02026             if (delta <= hsnap) {
02027               nx = v->left;
02028               hsnap = delta;
02029             }
02030 
02031             /* Your right border <-> other right border */
02032             delta = abs(v->left + v->width - x - w->width);
02033             if (delta <= hsnap) {
02034               nx = v->left + v->width - w->width;
02035               hsnap = delta;
02036             }
02037           }
02038 
02039           if (x + w->width > v->left && x < v->left + v->width) {
02040             /* Your top border <-> other bottom border */
02041             delta = abs(v->top + v->height - y);
02042             if (delta <= vsnap) {
02043               ny = v->top + v->height;
02044               vsnap = delta;
02045             }
02046 
02047             /* Your bottom border <-> other top border */
02048             delta = abs(v->top - y - w->height);
02049             if (delta <= vsnap) {
02050               ny = v->top - w->height;
02051               vsnap = delta;
02052             }
02053           }
02054 
02055           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
02056             /* Your top border <-> other top border */
02057             delta = abs(v->top - y);
02058             if (delta <= vsnap) {
02059               ny = v->top;
02060               vsnap = delta;
02061             }
02062 
02063             /* Your bottom border <-> other bottom border */
02064             delta = abs(v->top + v->height - y - w->height);
02065             if (delta <= vsnap) {
02066               ny = v->top + v->height - w->height;
02067               vsnap = delta;
02068             }
02069           }
02070         }
02071       }
02072 
02073       EnsureVisibleCaption(w, nx, ny);
02074 
02075       w->SetDirty();
02076       return ES_HANDLED;
02077     } else if (w->flags & WF_SIZING) {
02078       /* Stop the sizing if the left mouse button was released */
02079       if (!_left_button_down) {
02080         w->flags &= ~WF_SIZING;
02081         w->SetDirty();
02082         break;
02083       }
02084 
02085       /* Compute difference in pixels between cursor position and reference point in the window.
02086        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
02087        */
02088       int x, y = _cursor.pos.y - _drag_delta.y;
02089       if (w->flags & WF_SIZING_LEFT) {
02090         x = _drag_delta.x - _cursor.pos.x;
02091       } else {
02092         x = _cursor.pos.x - _drag_delta.x;
02093       }
02094 
02095       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
02096       if (w->resize.step_width  == 0) x = 0;
02097       if (w->resize.step_height == 0) y = 0;
02098 
02099       /* Check the resize button won't go past the bottom of the screen */
02100       if (w->top + w->height + y > _screen.height) {
02101         y = _screen.height - w->height - w->top;
02102       }
02103 
02104       /* X and Y has to go by step.. calculate it.
02105        * The cast to int is necessary else x/y are implicitly casted to
02106        * unsigned int, which won't work. */
02107       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
02108       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
02109 
02110       /* Check that we don't go below the minimum set size */
02111       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
02112         x = w->nested_root->smallest_x - w->width;
02113       }
02114       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
02115         y = w->nested_root->smallest_y - w->height;
02116       }
02117 
02118       /* Window already on size */
02119       if (x == 0 && y == 0) return ES_HANDLED;
02120 
02121       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
02122       _drag_delta.y += y;
02123       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
02124         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
02125         w->SetDirty();
02126         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
02127         /* ResizeWindow() below ensures marking new position as dirty. */
02128       } else {
02129         _drag_delta.x += x;
02130       }
02131 
02132       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
02133       ResizeWindow(w, x, y);
02134       return ES_HANDLED;
02135     }
02136   }
02137 
02138   _dragging_window = false;
02139   return ES_HANDLED;
02140 }
02141 
02146 static void StartWindowDrag(Window *w)
02147 {
02148   w->flags |= WF_DRAGGING;
02149   w->flags &= ~WF_CENTERED;
02150   _dragging_window = true;
02151 
02152   _drag_delta.x = w->left - _cursor.pos.x;
02153   _drag_delta.y = w->top  - _cursor.pos.y;
02154 
02155   BringWindowToFront(w);
02156   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02157 }
02158 
02164 static void StartWindowSizing(Window *w, bool to_left)
02165 {
02166   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
02167   w->flags &= ~WF_CENTERED;
02168   _dragging_window = true;
02169 
02170   _drag_delta.x = _cursor.pos.x;
02171   _drag_delta.y = _cursor.pos.y;
02172 
02173   BringWindowToFront(w);
02174   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02175 }
02176 
02181 static EventState HandleScrollbarScrolling()
02182 {
02183   Window *w;
02184   FOR_ALL_WINDOWS_FROM_BACK(w) {
02185     if (w->scrolling_scrollbar >= 0) {
02186       /* Abort if no button is clicked any more. */
02187       if (!_left_button_down) {
02188         w->scrolling_scrollbar = -1;
02189         w->SetDirty();
02190         return ES_HANDLED;
02191       }
02192 
02193       int i;
02194       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
02195       bool rtl = false;
02196 
02197       if (sb->type == NWID_HSCROLLBAR) {
02198         i = _cursor.pos.x - _cursorpos_drag_start.x;
02199         rtl = _current_text_dir == TD_RTL;
02200       } else {
02201         i = _cursor.pos.y - _cursorpos_drag_start.y;
02202       }
02203 
02204       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
02205         if (_scroller_click_timeout == 1) {
02206           _scroller_click_timeout = 3;
02207           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
02208           w->SetDirty();
02209         }
02210         return ES_HANDLED;
02211       }
02212 
02213       /* Find the item we want to move to and make sure it's inside bounds. */
02214       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
02215       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
02216       if (pos != sb->GetPosition()) {
02217         sb->SetPosition(pos);
02218         w->SetDirty();
02219       }
02220       return ES_HANDLED;
02221     }
02222   }
02223 
02224   return ES_NOT_HANDLED;
02225 }
02226 
02231 static EventState HandleViewportScroll()
02232 {
02233   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02234 
02235   if (!_scrolling_viewport) return ES_NOT_HANDLED;
02236 
02237   /* When we don't have a last scroll window we are starting to scroll.
02238    * When the last scroll window and this are not the same we went
02239    * outside of the window and should not left-mouse scroll anymore. */
02240   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
02241 
02242   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
02243     _cursor.fix_at = false;
02244     _scrolling_viewport = false;
02245     _last_scroll_window = NULL;
02246     return ES_NOT_HANDLED;
02247   }
02248 
02249   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
02250     /* If the main window is following a vehicle, then first let go of it! */
02251     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
02252     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
02253     return ES_NOT_HANDLED;
02254   }
02255 
02256   Point delta;
02257   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
02258     delta.x = -_cursor.delta.x;
02259     delta.y = -_cursor.delta.y;
02260   } else {
02261     delta.x = _cursor.delta.x;
02262     delta.y = _cursor.delta.y;
02263   }
02264 
02265   if (scrollwheel_scrolling) {
02266     /* We are using scrollwheels for scrolling */
02267     delta.x = _cursor.h_wheel;
02268     delta.y = _cursor.v_wheel;
02269     _cursor.v_wheel = 0;
02270     _cursor.h_wheel = 0;
02271   }
02272 
02273   /* Create a scroll-event and send it to the window */
02274   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
02275 
02276   _cursor.delta.x = 0;
02277   _cursor.delta.y = 0;
02278   return ES_HANDLED;
02279 }
02280 
02291 static bool MaybeBringWindowToFront(Window *w)
02292 {
02293   bool bring_to_front = false;
02294 
02295   if (w->window_class == WC_MAIN_WINDOW ||
02296       IsVitalWindow(w) ||
02297       w->window_class == WC_TOOLTIPS ||
02298       w->window_class == WC_DROPDOWN_MENU) {
02299     return true;
02300   }
02301 
02302   /* Use unshaded window size rather than current size for shaded windows. */
02303   int w_width  = w->width;
02304   int w_height = w->height;
02305   if (w->IsShaded()) {
02306     w_width  = w->unshaded_size.width;
02307     w_height = w->unshaded_size.height;
02308   }
02309 
02310   Window *u;
02311   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
02312     /* A modal child will prevent the activation of the parent window */
02313     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
02314       u->SetWhiteBorder();
02315       u->SetDirty();
02316       return false;
02317     }
02318 
02319     if (u->window_class == WC_MAIN_WINDOW ||
02320         IsVitalWindow(u) ||
02321         u->window_class == WC_TOOLTIPS ||
02322         u->window_class == WC_DROPDOWN_MENU) {
02323       continue;
02324     }
02325 
02326     /* Window sizes don't interfere, leave z-order alone */
02327     if (w->left + w_width <= u->left ||
02328         u->left + u->width <= w->left ||
02329         w->top  + w_height <= u->top ||
02330         u->top + u->height <= w->top) {
02331       continue;
02332     }
02333 
02334     bring_to_front = true;
02335   }
02336 
02337   if (bring_to_front) BringWindowToFront(w);
02338   return true;
02339 }
02340 
02349 EventState Window::HandleEditBoxKey(int wid, WChar key, uint16 keycode)
02350 {
02351   QueryString *query = this->GetQueryString(wid);
02352   if (query == NULL) return ES_NOT_HANDLED;
02353 
02354   int action = QueryString::ACTION_NOTHING;
02355 
02356   switch (query->text.HandleKeyPress(key, keycode)) {
02357     case HKPR_EDITING:
02358       this->SetWidgetDirty(wid);
02359       this->OnEditboxChanged(wid);
02360       break;
02361 
02362     case HKPR_CURSOR:
02363       this->SetWidgetDirty(wid);
02364       /* For the OSK also invalidate the parent window */
02365       if (this->window_class == WC_OSK) this->InvalidateData();
02366       break;
02367 
02368     case HKPR_CONFIRM:
02369       if (this->window_class == WC_OSK) {
02370         this->OnClick(Point(), WID_OSK_OK, 1);
02371       } else if (query->ok_button >= 0) {
02372         this->OnClick(Point(), query->ok_button, 1);
02373       } else {
02374         action = query->ok_button;
02375       }
02376       break;
02377 
02378     case HKPR_CANCEL:
02379       if (this->window_class == WC_OSK) {
02380         this->OnClick(Point(), WID_OSK_CANCEL, 1);
02381       } else if (query->cancel_button >= 0) {
02382         this->OnClick(Point(), query->cancel_button, 1);
02383       } else {
02384         action = query->cancel_button;
02385       }
02386       break;
02387 
02388     case HKPR_NOT_HANDLED:
02389       return ES_NOT_HANDLED;
02390 
02391     default: break;
02392   }
02393 
02394   switch (action) {
02395     case QueryString::ACTION_DESELECT:
02396       this->UnfocusFocusedWidget();
02397       break;
02398 
02399     case QueryString::ACTION_CLEAR:
02400       if (query->text.bytes <= 1) {
02401         /* If already empty, unfocus instead */
02402         this->UnfocusFocusedWidget();
02403       } else {
02404         query->text.DeleteAll();
02405         this->SetWidgetDirty(wid);
02406         this->OnEditboxChanged(wid);
02407       }
02408       break;
02409 
02410     default:
02411       break;
02412   }
02413 
02414   return ES_HANDLED;
02415 }
02416 
02422 void HandleKeypress(uint keycode, WChar key)
02423 {
02424   /* World generation is multithreaded and messes with companies.
02425    * But there is no company related window open anyway, so _current_company is not used. */
02426   assert(HasModalProgress() || IsLocalCompany());
02427 
02428   /*
02429    * The Unicode standard defines an area called the private use area. Code points in this
02430    * area are reserved for private use and thus not portable between systems. For instance,
02431    * Apple defines code points for the arrow keys in this area, but these are only printable
02432    * on a system running OS X. We don't want these keys to show up in text fields and such,
02433    * and thus we have to clear the unicode character when we encounter such a key.
02434    */
02435   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02436 
02437   /*
02438    * If both key and keycode is zero, we don't bother to process the event.
02439    */
02440   if (key == 0 && keycode == 0) return;
02441 
02442   /* Check if the focused window has a focused editbox */
02443   if (EditBoxInGlobalFocus()) {
02444     /* All input will in this case go to the focused editbox */
02445     if (_focused_window->window_class == WC_CONSOLE) {
02446       if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02447     } else {
02448       if (_focused_window->HandleEditBoxKey(_focused_window->nested_focus->index, key, keycode) == ES_HANDLED) return;
02449     }
02450   }
02451 
02452   /* Call the event, start with the uppermost window, but ignore the toolbar. */
02453   Window *w;
02454   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02455     if (w->window_class == WC_MAIN_TOOLBAR) continue;
02456     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02457   }
02458 
02459   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02460   /* When there is no toolbar w is null, check for that */
02461   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02462 
02463   HandleGlobalHotkeys(key, keycode);
02464 }
02465 
02469 void HandleCtrlChanged()
02470 {
02471   /* Call the event, start with the uppermost window. */
02472   Window *w;
02473   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02474     if (w->OnCTRLStateChange() == ES_HANDLED) return;
02475   }
02476 }
02477 
02483 /* virtual */ void Window::InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
02484 {
02485   QueryString *query = this->GetQueryString(wid);
02486   if (query == NULL) return;
02487 
02488   if (query->text.InsertString(str, marked, caret, insert_location, replacement_end) || marked) {
02489     this->SetWidgetDirty(wid);
02490     this->OnEditboxChanged(wid);
02491   }
02492 }
02493 
02500 void HandleTextInput(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
02501 {
02502   if (!EditBoxInGlobalFocus()) return;
02503 
02504   _focused_window->InsertTextString(_focused_window->window_class == WC_CONSOLE ? 0 : _focused_window->nested_focus->index, str, marked, caret, insert_location, replacement_end);
02505 }
02506 
02513 static int _input_events_this_tick = 0;
02514 
02519 static void HandleAutoscroll()
02520 {
02521   if (_game_mode == GM_MENU || HasModalProgress()) return;
02522   if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
02523   if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
02524 
02525   int x = _cursor.pos.x;
02526   int y = _cursor.pos.y;
02527   Window *w = FindWindowFromPt(x, y);
02528   if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
02529   if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
02530 
02531   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02532   if (vp == NULL) return;
02533 
02534   x -= vp->left;
02535   y -= vp->top;
02536 
02537   /* here allows scrolling in both x and y axis */
02538 #define scrollspeed 3
02539   if (x - 15 < 0) {
02540     w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02541   } else if (15 - (vp->width - x) > 0) {
02542     w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02543   }
02544   if (y - 15 < 0) {
02545     w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02546   } else if (15 - (vp->height - y) > 0) {
02547     w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02548   }
02549 #undef scrollspeed
02550 }
02551 
02552 enum MouseClick {
02553   MC_NONE = 0,
02554   MC_LEFT,
02555   MC_RIGHT,
02556   MC_DOUBLE_LEFT,
02557   MC_HOVER,
02558 
02559   MAX_OFFSET_DOUBLE_CLICK = 5,     
02560   TIME_BETWEEN_DOUBLE_CLICK = 500, 
02561   MAX_OFFSET_HOVER = 5,            
02562 };
02563 extern EventState VpHandlePlaceSizingDrag();
02564 
02565 static void ScrollMainViewport(int x, int y)
02566 {
02567   if (_game_mode != GM_MENU) {
02568     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02569     assert(w);
02570 
02571     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02572     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02573   }
02574 }
02575 
02585 static const int8 scrollamt[16][2] = {
02586   { 0,  0}, 
02587   {-2,  0}, 
02588   { 0, -2}, 
02589   {-2, -1}, 
02590   { 2,  0}, 
02591   { 0,  0}, 
02592   { 2, -1}, 
02593   { 0, -2}, 
02594   { 0,  2}, 
02595   {-2,  1}, 
02596   { 0,  0}, 
02597   {-2,  0}, 
02598   { 2,  1}, 
02599   { 0,  2}, 
02600   { 2,  0}, 
02601   { 0,  0}, 
02602 };
02603 
02604 static void HandleKeyScrolling()
02605 {
02606   /*
02607    * Check that any of the dirkeys is pressed and that the focused window
02608    * doesn't have an edit-box as focused widget.
02609    */
02610   if (_dirkeys && !EditBoxInGlobalFocus()) {
02611     int factor = _shift_pressed ? 50 : 10;
02612     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02613   }
02614 }
02615 
02616 static void MouseLoop(MouseClick click, int mousewheel)
02617 {
02618   /* World generation is multithreaded and messes with companies.
02619    * But there is no company related window open anyway, so _current_company is not used. */
02620   assert(HasModalProgress() || IsLocalCompany());
02621 
02622   HandlePlacePresize();
02623   UpdateTileSelection();
02624 
02625   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
02626   if (HandleMouseDragDrop()      == ES_HANDLED) return;
02627   if (HandleWindowDragging()     == ES_HANDLED) return;
02628   if (HandleScrollbarScrolling() == ES_HANDLED) return;
02629   if (HandleViewportScroll()     == ES_HANDLED) return;
02630 
02631   HandleMouseOver();
02632 
02633   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02634   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02635 
02636   int x = _cursor.pos.x;
02637   int y = _cursor.pos.y;
02638   Window *w = FindWindowFromPt(x, y);
02639   if (w == NULL) return;
02640 
02641   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02642   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02643 
02644   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
02645   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
02646 
02647   if (mousewheel != 0) {
02648     /* Send mousewheel event to window */
02649     w->OnMouseWheel(mousewheel);
02650 
02651     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02652     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02653   }
02654 
02655   if (vp != NULL) {
02656     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02657     switch (click) {
02658       case MC_DOUBLE_LEFT:
02659       case MC_LEFT:
02660         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02661         if (!HandleViewportClicked(vp, x, y) &&
02662             !(w->flags & WF_DISABLE_VP_SCROLL) &&
02663             _settings_client.gui.left_mouse_btn_scrolling) {
02664           _scrolling_viewport = true;
02665           _cursor.fix_at = false;
02666         }
02667         break;
02668 
02669       case MC_RIGHT:
02670         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
02671           _scrolling_viewport = true;
02672           _cursor.fix_at = true;
02673 
02674           /* clear 2D scrolling caches before we start a 2D scroll */
02675           _cursor.h_wheel = 0;
02676           _cursor.v_wheel = 0;
02677         }
02678         break;
02679 
02680       default:
02681         break;
02682     }
02683   } else {
02684     switch (click) {
02685       case MC_LEFT:
02686       case MC_DOUBLE_LEFT:
02687         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02688         break;
02689 
02690       default:
02691         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02692         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02693          * Simulate a right button click so we can get started. */
02694         /* FALL THROUGH */
02695 
02696       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02697 
02698       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02699     }
02700   }
02701 }
02702 
02706 void HandleMouseEvents()
02707 {
02708   /* World generation is multithreaded and messes with companies.
02709    * But there is no company related window open anyway, so _current_company is not used. */
02710   assert(HasModalProgress() || IsLocalCompany());
02711 
02712   static int double_click_time = 0;
02713   static Point double_click_pos = {0, 0};
02714 
02715   /* Mouse event? */
02716   MouseClick click = MC_NONE;
02717   if (_left_button_down && !_left_button_clicked) {
02718     click = MC_LEFT;
02719     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02720         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
02721         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02722       click = MC_DOUBLE_LEFT;
02723     }
02724     double_click_time = _realtime_tick;
02725     double_click_pos = _cursor.pos;
02726     _left_button_clicked = true;
02727     _input_events_this_tick++;
02728   } else if (_right_button_clicked) {
02729     _right_button_clicked = false;
02730     click = MC_RIGHT;
02731     _input_events_this_tick++;
02732   }
02733 
02734   int mousewheel = 0;
02735   if (_cursor.wheel) {
02736     mousewheel = _cursor.wheel;
02737     _cursor.wheel = 0;
02738     _input_events_this_tick++;
02739   }
02740 
02741   static uint32 hover_time = 0;
02742   static Point hover_pos = {0, 0};
02743 
02744   if (_settings_client.gui.hover_delay > 0) {
02745     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02746         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
02747         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02748       hover_pos = _cursor.pos;
02749       hover_time = _realtime_tick;
02750       _mouse_hovering = false;
02751     } else {
02752       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02753         click = MC_HOVER;
02754         _input_events_this_tick++;
02755         _mouse_hovering = true;
02756       }
02757     }
02758   }
02759 
02760   /* Handle sprite picker before any GUI interaction */
02761   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02762     /* Next realtime tick? Then redraw has finished */
02763     _newgrf_debug_sprite_picker.mode = SPM_NONE;
02764     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02765   }
02766 
02767   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02768     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
02769     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
02770     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02771     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02772     _newgrf_debug_sprite_picker.sprites.Clear();
02773     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02774     MarkWholeScreenDirty();
02775   } else {
02776     MouseLoop(click, mousewheel);
02777   }
02778 
02779   /* We have moved the mouse the required distance,
02780    * no need to move it at any later time. */
02781   _cursor.delta.x = 0;
02782   _cursor.delta.y = 0;
02783 }
02784 
02788 static void CheckSoftLimit()
02789 {
02790   if (_settings_client.gui.window_soft_limit == 0) return;
02791 
02792   for (;;) {
02793     uint deletable_count = 0;
02794     Window *w, *last_deletable = NULL;
02795     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02796       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
02797 
02798       last_deletable = w;
02799       deletable_count++;
02800     }
02801 
02802     /* We've not reached the soft limit yet. */
02803     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02804 
02805     assert(last_deletable != NULL);
02806     delete last_deletable;
02807   }
02808 }
02809 
02813 void InputLoop()
02814 {
02815   /* World generation is multithreaded and messes with companies.
02816    * But there is no company related window open anyway, so _current_company is not used. */
02817   assert(HasModalProgress() || IsLocalCompany());
02818 
02819   CheckSoftLimit();
02820   HandleKeyScrolling();
02821 
02822   /* Do the actual free of the deleted windows. */
02823   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02824     Window *w = v;
02825     v = v->z_back;
02826 
02827     if (w->window_class != WC_INVALID) continue;
02828 
02829     RemoveWindowFromZOrdering(w);
02830     free(w);
02831   }
02832 
02833   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02834   DecreaseWindowCounters();
02835 
02836   if (_input_events_this_tick != 0) {
02837     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02838     _input_events_this_tick = 0;
02839     /* there were some inputs this tick, don't scroll ??? */
02840     return;
02841   }
02842 
02843   /* HandleMouseEvents was already called for this tick */
02844   HandleMouseEvents();
02845   HandleAutoscroll();
02846 }
02847 
02851 void UpdateWindows()
02852 {
02853   Window *w;
02854 
02855   static int highlight_timer = 1;
02856   if (--highlight_timer == 0) {
02857     highlight_timer = 15;
02858     _window_highlight_colour = !_window_highlight_colour;
02859   }
02860 
02861   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02862     w->ProcessScheduledInvalidations();
02863     w->ProcessHighlightedInvalidations();
02864   }
02865 
02866   static int we4_timer = 0;
02867   int t = we4_timer + 1;
02868 
02869   if (t >= 100) {
02870     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02871       w->OnHundredthTick();
02872     }
02873     t = 0;
02874   }
02875   we4_timer = t;
02876 
02877   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02878     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
02879       CLRBITS(w->flags, WF_WHITE_BORDER);
02880       w->SetDirty();
02881     }
02882   }
02883 
02884   DrawDirtyBlocks();
02885 
02886   FOR_ALL_WINDOWS_FROM_BACK(w) {
02887     /* Update viewport only if window is not shaded. */
02888     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02889   }
02890   NetworkDrawChatMessage();
02891   /* Redraw mouse cursor in case it was hidden */
02892   DrawMouseCursor();
02893 }
02894 
02900 void SetWindowDirty(WindowClass cls, WindowNumber number)
02901 {
02902   const Window *w;
02903   FOR_ALL_WINDOWS_FROM_BACK(w) {
02904     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02905   }
02906 }
02907 
02914 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02915 {
02916   const Window *w;
02917   FOR_ALL_WINDOWS_FROM_BACK(w) {
02918     if (w->window_class == cls && w->window_number == number) {
02919       w->SetWidgetDirty(widget_index);
02920     }
02921   }
02922 }
02923 
02928 void SetWindowClassesDirty(WindowClass cls)
02929 {
02930   Window *w;
02931   FOR_ALL_WINDOWS_FROM_BACK(w) {
02932     if (w->window_class == cls) w->SetDirty();
02933   }
02934 }
02935 
02941 void Window::InvalidateData(int data, bool gui_scope)
02942 {
02943   this->SetDirty();
02944   if (!gui_scope) {
02945     /* Schedule GUI-scope invalidation for next redraw. */
02946     *this->scheduled_invalidation_data.Append() = data;
02947   }
02948   this->OnInvalidateData(data, gui_scope);
02949 }
02950 
02954 void Window::ProcessScheduledInvalidations()
02955 {
02956   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
02957     this->OnInvalidateData(*data, true);
02958   }
02959   this->scheduled_invalidation_data.Clear();
02960 }
02961 
02965 void Window::ProcessHighlightedInvalidations()
02966 {
02967   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
02968 
02969   for (uint i = 0; i < this->nested_array_size; i++) {
02970     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
02971   }
02972 }
02973 
03000 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
03001 {
03002   Window *w;
03003   FOR_ALL_WINDOWS_FROM_BACK(w) {
03004     if (w->window_class == cls && w->window_number == number) {
03005       w->InvalidateData(data, gui_scope);
03006     }
03007   }
03008 }
03009 
03018 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
03019 {
03020   Window *w;
03021 
03022   FOR_ALL_WINDOWS_FROM_BACK(w) {
03023     if (w->window_class == cls) {
03024       w->InvalidateData(data, gui_scope);
03025     }
03026   }
03027 }
03028 
03032 void CallWindowTickEvent()
03033 {
03034   Window *w;
03035   FOR_ALL_WINDOWS_FROM_FRONT(w) {
03036     w->OnTick();
03037   }
03038 }
03039 
03046 void DeleteNonVitalWindows()
03047 {
03048   Window *w;
03049 
03050 restart_search:
03051   /* When we find the window to delete, we need to restart the search
03052    * as deleting this window could cascade in deleting (many) others
03053    * anywhere in the z-array */
03054   FOR_ALL_WINDOWS_FROM_BACK(w) {
03055     if (w->window_class != WC_MAIN_WINDOW &&
03056         w->window_class != WC_SELECT_GAME &&
03057         w->window_class != WC_MAIN_TOOLBAR &&
03058         w->window_class != WC_STATUS_BAR &&
03059         w->window_class != WC_TOOLTIPS &&
03060         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
03061 
03062       delete w;
03063       goto restart_search;
03064     }
03065   }
03066 }
03067 
03075 void DeleteAllNonVitalWindows()
03076 {
03077   Window *w;
03078 
03079   /* Delete every window except for stickied ones, then sticky ones as well */
03080   DeleteNonVitalWindows();
03081 
03082 restart_search:
03083   /* When we find the window to delete, we need to restart the search
03084    * as deleting this window could cascade in deleting (many) others
03085    * anywhere in the z-array */
03086   FOR_ALL_WINDOWS_FROM_BACK(w) {
03087     if (w->flags & WF_STICKY) {
03088       delete w;
03089       goto restart_search;
03090     }
03091   }
03092 }
03093 
03098 void DeleteConstructionWindows()
03099 {
03100   Window *w;
03101 
03102 restart_search:
03103   /* When we find the window to delete, we need to restart the search
03104    * as deleting this window could cascade in deleting (many) others
03105    * anywhere in the z-array */
03106   FOR_ALL_WINDOWS_FROM_BACK(w) {
03107     if (w->desc_flags & WDF_CONSTRUCTION) {
03108       delete w;
03109       goto restart_search;
03110     }
03111   }
03112 
03113   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
03114 }
03115 
03117 void HideVitalWindows()
03118 {
03119   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
03120   DeleteWindowById(WC_STATUS_BAR, 0);
03121 }
03122 
03124 void ReInitAllWindows()
03125 {
03126   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
03127   NWidgetScrollbar::InvalidateDimensionCache();
03128 
03129   Window *w;
03130   FOR_ALL_WINDOWS_FROM_BACK(w) {
03131     w->ReInit();
03132   }
03133 #ifdef ENABLE_NETWORK
03134   void NetworkReInitChatBoxSize();
03135   NetworkReInitChatBoxSize();
03136 #endif
03137 
03138   /* Make sure essential parts of all windows are visible */
03139   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
03140   MarkWholeScreenDirty();
03141 }
03142 
03150 static int PositionWindow(Window *w, WindowClass clss, int setting)
03151 {
03152   if (w == NULL || w->window_class != clss) {
03153     w = FindWindowById(clss, 0);
03154   }
03155   if (w == NULL) return 0;
03156 
03157   int old_left = w->left;
03158   switch (setting) {
03159     case 1:  w->left = (_screen.width - w->width) / 2; break;
03160     case 2:  w->left = _screen.width - w->width; break;
03161     default: w->left = 0; break;
03162   }
03163   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
03164   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
03165   return w->left;
03166 }
03167 
03173 int PositionMainToolbar(Window *w)
03174 {
03175   DEBUG(misc, 5, "Repositioning Main Toolbar...");
03176   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
03177 }
03178 
03184 int PositionStatusbar(Window *w)
03185 {
03186   DEBUG(misc, 5, "Repositioning statusbar...");
03187   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
03188 }
03189 
03195 int PositionNewsMessage(Window *w)
03196 {
03197   DEBUG(misc, 5, "Repositioning news message...");
03198   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
03199 }
03200 
03206 int PositionNetworkChatWindow(Window *w)
03207 {
03208   DEBUG(misc, 5, "Repositioning network chat window...");
03209   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
03210 }
03211 
03212 
03218 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
03219 {
03220   Window *w;
03221   FOR_ALL_WINDOWS_FROM_BACK(w) {
03222     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
03223       w->viewport->follow_vehicle = to_index;
03224       w->SetDirty();
03225     }
03226   }
03227 }
03228 
03229 
03235 void RelocateAllWindows(int neww, int newh)
03236 {
03237   Window *w;
03238 
03239   FOR_ALL_WINDOWS_FROM_BACK(w) {
03240     int left, top;
03241 
03242     if (w->window_class == WC_MAIN_WINDOW) {
03243       ViewPort *vp = w->viewport;
03244       vp->width = w->width = neww;
03245       vp->height = w->height = newh;
03246       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
03247       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
03248       continue; // don't modify top,left
03249     }
03250 
03251     /* XXX - this probably needs something more sane. For example specifying
03252      * in a 'backup'-desc that the window should always be centered. */
03253     switch (w->window_class) {
03254       case WC_BOOTSTRAP:
03255         ResizeWindow(w, neww, newh);
03256         continue;
03257 
03258       case WC_MAIN_TOOLBAR:
03259         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0, false);
03260 
03261         top = w->top;
03262         left = PositionMainToolbar(w); // changes toolbar orientation
03263         break;
03264 
03265       case WC_NEWS_WINDOW:
03266         top = newh - w->height;
03267         left = PositionNewsMessage(w);
03268         break;
03269 
03270       case WC_STATUS_BAR:
03271         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0, false);
03272 
03273         top = newh - w->height;
03274         left = PositionStatusbar(w);
03275         break;
03276 
03277       case WC_SEND_NETWORK_MSG:
03278         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
03279         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
03280         left = PositionNetworkChatWindow(w);
03281         break;
03282 
03283       case WC_CONSOLE:
03284         IConsoleResize(w);
03285         continue;
03286 
03287       default: {
03288         if (w->flags & WF_CENTERED) {
03289           top = (newh - w->height) >> 1;
03290           left = (neww - w->width) >> 1;
03291           break;
03292         }
03293 
03294         left = w->left;
03295         if (left + (w->width >> 1) >= neww) left = neww - w->width;
03296         if (left < 0) left = 0;
03297 
03298         top = w->top;
03299         if (top + (w->height >> 1) >= newh) top = newh - w->height;
03300         break;
03301       }
03302     }
03303 
03304     EnsureVisibleCaption(w, left, top);
03305   }
03306 }
03307 
03313 PickerWindowBase::~PickerWindowBase()
03314 {
03315   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
03316   ResetObjectToPlace();
03317 }