window.cpp

Go to the documentation of this file.
00001 /* $Id$ */
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 "settings_func.h"
00031 #include "ini_type.h"
00032 #include "newgrf_debug.h"
00033 #include "hotkeys.h"
00034 #include "toolbar_gui.h"
00035 #include "statusbar_gui.h"
00036 #include "error.h"
00037 #include "game/game.hpp"
00038 
00040 enum ViewportAutoscrolling {
00041   VA_DISABLED,                  
00042   VA_MAIN_VIEWPORT_FULLSCREEN,  
00043   VA_MAIN_VIEWPORT,             
00044   VA_EVERY_VIEWPORT,            
00045 };
00046 
00047 static Point _drag_delta; 
00048 static Window *_mouseover_last_w = NULL; 
00049 static Window *_last_scroll_window = NULL; 
00050 
00052 Window *_z_front_window = NULL;
00054 Window *_z_back_window  = NULL;
00055 
00057 bool _window_highlight_colour = false;
00058 
00059 /*
00060  * Window that currently has focus. - The main purpose is to generate
00061  * #FocusLost events, not to give next window in z-order focus when a
00062  * window is closed.
00063  */
00064 Window *_focused_window;
00065 
00066 Point _cursorpos_drag_start;
00067 
00068 int _scrollbar_start_pos;
00069 int _scrollbar_size;
00070 byte _scroller_click_timeout = 0;
00071 
00072 bool _scrolling_viewport;  
00073 bool _mouse_hovering;      
00074 
00075 SpecialMouseMode _special_mouse_mode; 
00076 
00081 static SmallVector<WindowDesc*, 16> *_window_descs = NULL;
00082 
00084 char *_windows_file;
00085 
00087 WindowDesc::WindowDesc(WindowPosition def_pos, const char *ini_key, int16 def_width, int16 def_height,
00088       WindowClass window_class, WindowClass parent_class, uint32 flags,
00089       const NWidgetPart *nwid_parts, int16 nwid_length) :
00090   default_pos(def_pos),
00091   default_width(def_width),
00092   default_height(def_height),
00093   cls(window_class),
00094   parent_cls(parent_class),
00095   ini_key(ini_key),
00096   flags(flags),
00097   nwid_parts(nwid_parts),
00098   nwid_length(nwid_length),
00099   pref_sticky(false),
00100   pref_width(0),
00101   pref_height(0)
00102 {
00103   if (_window_descs == NULL) _window_descs = new SmallVector<WindowDesc*, 16>();
00104   *_window_descs->Append() = this;
00105 }
00106 
00107 WindowDesc::~WindowDesc()
00108 {
00109 }
00110 
00114 void WindowDesc::LoadFromConfig()
00115 {
00116   IniFile *ini = new IniFile();
00117   ini->LoadFromDisk(_windows_file, BASE_DIR);
00118   for (WindowDesc **it = _window_descs->Begin(); it != _window_descs->End(); ++it) {
00119     if ((*it)->ini_key == NULL) continue;
00120     IniLoadWindowSettings(ini, (*it)->ini_key, *it);
00121   }
00122   delete ini;
00123 }
00124 
00128 static int CDECL DescSorter(WindowDesc * const *a, WindowDesc * const *b)
00129 {
00130   if ((*a)->ini_key != NULL && (*b)->ini_key != NULL) return strcmp((*a)->ini_key, (*b)->ini_key);
00131   return ((*b)->ini_key != NULL ? 1 : 0) - ((*a)->ini_key != NULL ? 1 : 0);
00132 }
00133 
00137 void WindowDesc::SaveToConfig()
00138 {
00139   /* Sort the stuff to get a nice ini file on first write */
00140   QSortT(_window_descs->Begin(), _window_descs->Length(), DescSorter);
00141 
00142   IniFile *ini = new IniFile();
00143   ini->LoadFromDisk(_windows_file, BASE_DIR);
00144   for (WindowDesc **it = _window_descs->Begin(); it != _window_descs->End(); ++it) {
00145     if ((*it)->ini_key == NULL) continue;
00146     IniSaveWindowSettings(ini, (*it)->ini_key, *it);
00147   }
00148   ini->SaveToDisk(_windows_file);
00149   delete ini;
00150 }
00151 
00155 void Window::ApplyDefaults()
00156 {
00157   if (this->nested_root != NULL && this->nested_root->GetWidgetOfType(WWT_STICKYBOX) != NULL) {
00158     if (this->window_desc->pref_sticky) this->flags |= WF_STICKY;
00159   } else {
00160     /* There is no stickybox; clear the preference in case someone tried to be funny */
00161     this->window_desc->pref_sticky = false;
00162   }
00163 }
00164 
00174 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00175 {
00176   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00177   if (line_height < 0) line_height = wid->resize_y;
00178   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00179   return (clickpos - (int)wid->pos_y - padding) / line_height;
00180 }
00181 
00185 void Window::DisableAllWidgetHighlight()
00186 {
00187   for (uint i = 0; i < this->nested_array_size; i++) {
00188     NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00189     if (nwid == NULL) continue;
00190 
00191     if (nwid->IsHighlighted()) {
00192       nwid->SetHighlighted(TC_INVALID);
00193       this->SetWidgetDirty(i);
00194     }
00195   }
00196 
00197   CLRBITS(this->flags, WF_HIGHLIGHTED);
00198 }
00199 
00205 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
00206 {
00207   assert(widget_index < this->nested_array_size);
00208 
00209   NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00210   if (nwid == NULL) return;
00211 
00212   nwid->SetHighlighted(highlighted_colour);
00213   this->SetWidgetDirty(widget_index);
00214 
00215   if (highlighted_colour != TC_INVALID) {
00216     /* If we set a highlight, the window has a highlight */
00217     this->flags |= WF_HIGHLIGHTED;
00218   } else {
00219     /* If we disable a highlight, check all widgets if anyone still has a highlight */
00220     bool valid = false;
00221     for (uint i = 0; i < this->nested_array_size; i++) {
00222       NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00223       if (nwid == NULL) continue;
00224       if (!nwid->IsHighlighted()) continue;
00225 
00226       valid = true;
00227     }
00228     /* If nobody has a highlight, disable the flag on the window */
00229     if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
00230   }
00231 }
00232 
00238 bool Window::IsWidgetHighlighted(byte widget_index) const
00239 {
00240   assert(widget_index < this->nested_array_size);
00241 
00242   const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00243   if (nwid == NULL) return false;
00244 
00245   return nwid->IsHighlighted();
00246 }
00247 
00255 void Window::OnDropdownClose(Point pt, int widget, int index, bool instant_close)
00256 {
00257   if (widget < 0) return;
00258 
00259   if (instant_close) {
00260     /* Send event for selected option if we're still
00261      * on the parent button of the dropdown (behaviour of the dropdowns in the main toolbar). */
00262     if (GetWidgetFromPos(this, pt.x, pt.y) == widget) {
00263       this->OnDropdownSelect(widget, index);
00264     }
00265   }
00266 
00267   /* Raise the dropdown button */
00268   if (this->nested_array != NULL) {
00269     NWidgetCore *nwi2 = this->GetWidget<NWidgetCore>(widget);
00270     if ((nwi2->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
00271       nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
00272     } else {
00273       this->RaiseWidget(widget);
00274     }
00275   } else {
00276     this->RaiseWidget(widget);
00277   }
00278   this->SetWidgetDirty(widget);
00279 }
00280 
00286 const Scrollbar *Window::GetScrollbar(uint widnum) const
00287 {
00288   return this->GetWidget<NWidgetScrollbar>(widnum);
00289 }
00290 
00296 Scrollbar *Window::GetScrollbar(uint widnum)
00297 {
00298   return this->GetWidget<NWidgetScrollbar>(widnum);
00299 }
00300 
00306 const QueryString *Window::GetQueryString(uint widnum) const
00307 {
00308   const SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00309   return query != this->querystrings.End() ? query->second : NULL;
00310 }
00311 
00317 QueryString *Window::GetQueryString(uint widnum)
00318 {
00319   SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00320   return query != this->querystrings.End() ? query->second : NULL;
00321 }
00322 
00323 
00328 void SetFocusedWindow(Window *w)
00329 {
00330   if (_focused_window == w) return;
00331 
00332   /* Invalidate focused widget */
00333   if (_focused_window != NULL) {
00334     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00335   }
00336 
00337   /* Remember which window was previously focused */
00338   Window *old_focused = _focused_window;
00339   _focused_window = w;
00340 
00341   /* So we can inform it that it lost focus */
00342   if (old_focused != NULL) old_focused->OnFocusLost();
00343   if (_focused_window != NULL) _focused_window->OnFocus();
00344 }
00345 
00351 static bool EditBoxInGlobalFocus()
00352 {
00353   if (_focused_window == NULL) return false;
00354 
00355   /* The console does not have an edit box so a special case is needed. */
00356   if (_focused_window->window_class == WC_CONSOLE) return true;
00357 
00358   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00359 }
00360 
00364 void Window::UnfocusFocusedWidget()
00365 {
00366   if (this->nested_focus != NULL) {
00367     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00368     this->nested_focus->SetDirty(this);
00369     this->nested_focus = NULL;
00370   }
00371 }
00372 
00378 bool Window::SetFocusedWidget(int widget_index)
00379 {
00380   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00381   if ((uint)widget_index >= this->nested_array_size) return false;
00382 
00383   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00384   if (this->nested_focus != NULL) {
00385     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00386 
00387     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00388     this->nested_focus->SetDirty(this);
00389   }
00390   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00391   return true;
00392 }
00393 
00401 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00402 {
00403   va_list wdg_list;
00404 
00405   va_start(wdg_list, widgets);
00406 
00407   while (widgets != WIDGET_LIST_END) {
00408     SetWidgetDisabledState(widgets, disab_stat);
00409     widgets = va_arg(wdg_list, int);
00410   }
00411 
00412   va_end(wdg_list);
00413 }
00414 
00420 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00421 {
00422   va_list wdg_list;
00423 
00424   va_start(wdg_list, widgets);
00425 
00426   while (widgets != WIDGET_LIST_END) {
00427     SetWidgetLoweredState(widgets, lowered_stat);
00428     widgets = va_arg(wdg_list, int);
00429   }
00430 
00431   va_end(wdg_list);
00432 }
00433 
00438 void Window::RaiseButtons(bool autoraise)
00439 {
00440   for (uint i = 0; i < this->nested_array_size; i++) {
00441     if (this->nested_array[i] == NULL) continue;
00442     WidgetType type = this->nested_array[i]->type;
00443     if (((type & ~WWB_PUSHBUTTON) < WWT_LAST || type == NWID_PUSHBUTTON_DROPDOWN) &&
00444         (!autoraise || (type & WWB_PUSHBUTTON) || type == WWT_EDITBOX) && this->IsWidgetLowered(i)) {
00445       this->RaiseWidget(i);
00446       this->SetWidgetDirty(i);
00447     }
00448   }
00449 
00450   /* Special widgets without widget index */
00451   NWidgetCore *wid = this->nested_root != NULL ? (NWidgetCore*)this->nested_root->GetWidgetOfType(WWT_DEFSIZEBOX) : NULL;
00452   if (wid != NULL) {
00453     wid->SetLowered(false);
00454     wid->SetDirty(this);
00455   }
00456 }
00457 
00462 void Window::SetWidgetDirty(byte widget_index) const
00463 {
00464   /* Sometimes this function is called before the window is even fully initialized */
00465   if (this->nested_array == NULL) return;
00466 
00467   this->nested_array[widget_index]->SetDirty(this);
00468 }
00469 
00475 void Window::HandleButtonClick(byte widget)
00476 {
00477   this->LowerWidget(widget);
00478   this->SetTimeout();
00479   this->SetWidgetDirty(widget);
00480 }
00481 
00482 static void StartWindowDrag(Window *w);
00483 static void StartWindowSizing(Window *w, bool to_left);
00484 
00492 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00493 {
00494   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00495   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00496 
00497   bool focused_widget_changed = false;
00498   /* If clicked on a window that previously did dot have focus */
00499   if (_focused_window != w &&                 // We already have focus, right?
00500       (w->window_desc->flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00501       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00502     focused_widget_changed = true;
00503     SetFocusedWindow(w);
00504     w->OnFocus();
00505   }
00506 
00507   if (nw == NULL) return; // exit if clicked outside of widgets
00508 
00509   /* don't allow any interaction if the button has been disabled */
00510   if (nw->IsDisabled()) return;
00511 
00512   int widget_index = nw->index; 
00513 
00514   /* Clicked on a widget that is not disabled.
00515    * So unless the clicked widget is the caption bar, change focus to this widget.
00516    * Exception: In the OSK we always want the editbox to stay focussed. */
00517   if (widget_type != WWT_CAPTION && w->window_class != WC_OSK) {
00518     /* focused_widget_changed is 'now' only true if the window this widget
00519      * is in gained focus. In that case it must remain true, also if the
00520      * local widget focus did not change. As such it's the logical-or of
00521      * both changed states.
00522      *
00523      * If this is not preserved, then the OSK window would be opened when
00524      * a user has the edit box focused and then click on another window and
00525      * then back again on the edit box (to type some text).
00526      */
00527     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00528   }
00529 
00530   /* Close any child drop down menus. If the button pressed was the drop down
00531    * list's own button, then we should not process the click any further. */
00532   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00533 
00534   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00535 
00536   Point pt = { x, y };
00537 
00538   switch (widget_type) {
00539     case NWID_VSCROLLBAR:
00540     case NWID_HSCROLLBAR:
00541       ScrollbarClickHandler(w, nw, x, y);
00542       break;
00543 
00544     case WWT_EDITBOX: {
00545       QueryString *query = w->GetQueryString(widget_index);
00546       if (query != NULL) query->ClickEditBox(w, pt, widget_index, click_count, focused_widget_changed);
00547       break;
00548     }
00549 
00550     case WWT_CLOSEBOX: // 'X'
00551       delete w;
00552       return;
00553 
00554     case WWT_CAPTION: // 'Title bar'
00555       StartWindowDrag(w);
00556       return;
00557 
00558     case WWT_RESIZEBOX:
00559       /* When the resize widget is on the left size of the window
00560        * we assume that that button is used to resize to the left. */
00561       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00562       nw->SetDirty(w);
00563       return;
00564 
00565     case WWT_DEFSIZEBOX: {
00566       if (_ctrl_pressed) {
00567         w->window_desc->pref_width = w->width;
00568         w->window_desc->pref_height = w->height;
00569       } else {
00570         int16 def_width = max<int16>(min(w->window_desc->GetDefaultWidth(), _screen.width), w->nested_root->smallest_x);
00571         int16 def_height = max<int16>(min(w->window_desc->GetDefaultHeight(), _screen.height - 50), w->nested_root->smallest_y);
00572 
00573         int dx = (w->resize.step_width  == 0) ? 0 : def_width  - w->width;
00574         int dy = (w->resize.step_height == 0) ? 0 : def_height - w->height;
00575         /* dx and dy has to go by step.. calculate it.
00576          * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00577         if (w->resize.step_width  > 1) dx -= dx % (int)w->resize.step_width;
00578         if (w->resize.step_height > 1) dy -= dy % (int)w->resize.step_height;
00579         ResizeWindow(w, dx, dy, false);
00580       }
00581 
00582       nw->SetLowered(true);
00583       nw->SetDirty(w);
00584       w->SetTimeout();
00585       break;
00586     }
00587 
00588     case WWT_DEBUGBOX:
00589       w->ShowNewGRFInspectWindow();
00590       break;
00591 
00592     case WWT_SHADEBOX:
00593       nw->SetDirty(w);
00594       w->SetShaded(!w->IsShaded());
00595       return;
00596 
00597     case WWT_STICKYBOX:
00598       w->flags ^= WF_STICKY;
00599       nw->SetDirty(w);
00600       if (_ctrl_pressed) w->window_desc->pref_sticky = (w->flags & WF_STICKY) != 0;
00601       return;
00602 
00603     default:
00604       break;
00605   }
00606 
00607   /* Widget has no index, so the window is not interested in it. */
00608   if (widget_index < 0) return;
00609 
00610   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
00611   if (w->IsWidgetHighlighted(widget_index)) {
00612     w->SetWidgetHighlight(widget_index, TC_INVALID);
00613     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
00614   }
00615 
00616   w->OnClick(pt, widget_index, click_count);
00617 }
00618 
00625 static void DispatchRightClickEvent(Window *w, int x, int y)
00626 {
00627   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00628   if (wid == NULL) return;
00629 
00630   /* No widget to handle, or the window is not interested in it. */
00631   if (wid->index >= 0) {
00632     Point pt = { x, y };
00633     if (w->OnRightClick(pt, wid->index)) return;
00634   }
00635 
00636   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00637 }
00638 
00645 static void DispatchHoverEvent(Window *w, int x, int y)
00646 {
00647   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00648 
00649   /* No widget to handle */
00650   if (wid == NULL) return;
00651 
00652   /* Show the tooltip if there is any */
00653   if (wid->tool_tip != 0) {
00654     GuiShowTooltips(w, wid->tool_tip);
00655     return;
00656   }
00657 
00658   /* Widget has no index, so the window is not interested in it. */
00659   if (wid->index < 0) return;
00660 
00661   Point pt = { x, y };
00662   w->OnHover(pt, wid->index);
00663 }
00664 
00672 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
00673 {
00674   if (nwid == NULL) return;
00675 
00676   /* Using wheel on caption/shade-box shades or unshades the window. */
00677   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00678     w->SetShaded(wheel < 0);
00679     return;
00680   }
00681 
00682   /* Wheeling a vertical scrollbar. */
00683   if (nwid->type == NWID_VSCROLLBAR) {
00684     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
00685     if (sb->GetCount() > sb->GetCapacity()) {
00686       sb->UpdatePosition(wheel);
00687       w->SetDirty();
00688     }
00689     return;
00690   }
00691 
00692   /* Scroll the widget attached to the scrollbar. */
00693   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00694   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00695     sb->UpdatePosition(wheel);
00696     w->SetDirty();
00697   }
00698 }
00699 
00705 static bool MayBeShown(const Window *w)
00706 {
00707   /* If we're not modal, everything is okay. */
00708   if (!HasModalProgress()) return true;
00709 
00710   switch (w->window_class) {
00711     case WC_MAIN_WINDOW:    
00712     case WC_MODAL_PROGRESS: 
00713     case WC_CONFIRM_POPUP_QUERY: 
00714       return true;
00715 
00716     default:
00717       return false;
00718   }
00719 }
00720 
00733 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00734 {
00735   const Window *v;
00736   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00737     if (MayBeShown(v) &&
00738         right > v->left &&
00739         bottom > v->top &&
00740         left < v->left + v->width &&
00741         top < v->top + v->height) {
00742       /* v and rectangle intersect with each other */
00743       int x;
00744 
00745       if (left < (x = v->left)) {
00746         DrawOverlappedWindow(w, left, top, x, bottom);
00747         DrawOverlappedWindow(w, x, top, right, bottom);
00748         return;
00749       }
00750 
00751       if (right > (x = v->left + v->width)) {
00752         DrawOverlappedWindow(w, left, top, x, bottom);
00753         DrawOverlappedWindow(w, x, top, right, bottom);
00754         return;
00755       }
00756 
00757       if (top < (x = v->top)) {
00758         DrawOverlappedWindow(w, left, top, right, x);
00759         DrawOverlappedWindow(w, left, x, right, bottom);
00760         return;
00761       }
00762 
00763       if (bottom > (x = v->top + v->height)) {
00764         DrawOverlappedWindow(w, left, top, right, x);
00765         DrawOverlappedWindow(w, left, x, right, bottom);
00766         return;
00767       }
00768 
00769       return;
00770     }
00771   }
00772 
00773   /* Setup blitter, and dispatch a repaint event to window *wz */
00774   DrawPixelInfo *dp = _cur_dpi;
00775   dp->width = right - left;
00776   dp->height = bottom - top;
00777   dp->left = left - w->left;
00778   dp->top = top - w->top;
00779   dp->pitch = _screen.pitch;
00780   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00781   dp->zoom = ZOOM_LVL_NORMAL;
00782   w->OnPaint();
00783 }
00784 
00793 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00794 {
00795   Window *w;
00796   DrawPixelInfo bk;
00797   _cur_dpi = &bk;
00798 
00799   FOR_ALL_WINDOWS_FROM_BACK(w) {
00800     if (MayBeShown(w) &&
00801         right > w->left &&
00802         bottom > w->top &&
00803         left < w->left + w->width &&
00804         top < w->top + w->height) {
00805       /* Window w intersects with the rectangle => needs repaint */
00806       DrawOverlappedWindow(w, left, top, right, bottom);
00807     }
00808   }
00809 }
00810 
00815 void Window::SetDirty() const
00816 {
00817   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00818 }
00819 
00826 void Window::ReInit(int rx, int ry)
00827 {
00828   this->SetDirty(); // Mark whole current window as dirty.
00829 
00830   /* Save current size. */
00831   int window_width  = this->width;
00832   int window_height = this->height;
00833 
00834   this->OnInit();
00835   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00836   this->nested_root->SetupSmallestSize(this, false);
00837   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00838   this->width  = this->nested_root->smallest_x;
00839   this->height = this->nested_root->smallest_y;
00840   this->resize.step_width  = this->nested_root->resize_x;
00841   this->resize.step_height = this->nested_root->resize_y;
00842 
00843   /* Resize as close to the original size + requested resize as possible. */
00844   window_width  = max(window_width  + rx, this->width);
00845   window_height = max(window_height + ry, this->height);
00846   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00847   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00848   /* dx and dy has to go by step.. calculate it.
00849    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00850   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00851   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00852 
00853   ResizeWindow(this, dx, dy);
00854   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00855 }
00856 
00862 void Window::SetShaded(bool make_shaded)
00863 {
00864   if (this->shade_select == NULL) return;
00865 
00866   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00867   if (this->shade_select->shown_plane != desired) {
00868     if (make_shaded) {
00869       this->unshaded_size.width  = this->width;
00870       this->unshaded_size.height = this->height;
00871       this->shade_select->SetDisplayedPlane(desired);
00872       this->ReInit(0, -this->height);
00873     } else {
00874       this->shade_select->SetDisplayedPlane(desired);
00875       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00876       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00877       this->ReInit(dx, dy);
00878     }
00879   }
00880 }
00881 
00888 static Window *FindChildWindow(const Window *w, WindowClass wc)
00889 {
00890   Window *v;
00891   FOR_ALL_WINDOWS_FROM_BACK(v) {
00892     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00893   }
00894 
00895   return NULL;
00896 }
00897 
00902 void Window::DeleteChildWindows(WindowClass wc) const
00903 {
00904   Window *child = FindChildWindow(this, wc);
00905   while (child != NULL) {
00906     delete child;
00907     child = FindChildWindow(this, wc);
00908   }
00909 }
00910 
00914 Window::~Window()
00915 {
00916   if (_thd.window_class == this->window_class &&
00917       _thd.window_number == this->window_number) {
00918     ResetObjectToPlace();
00919   }
00920 
00921   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00922   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00923 
00924   /* We can't scroll the window when it's closed. */
00925   if (_last_scroll_window == this) _last_scroll_window = NULL;
00926 
00927   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00928   if (_focused_window == this) _focused_window = NULL;
00929 
00930   this->DeleteChildWindows();
00931 
00932   if (this->viewport != NULL) DeleteWindowViewport(this);
00933 
00934   this->SetDirty();
00935 
00936   free(this->nested_array); // Contents is released through deletion of #nested_root.
00937   delete this->nested_root;
00938 
00939   this->window_class = WC_INVALID;
00940 }
00941 
00948 Window *FindWindowById(WindowClass cls, WindowNumber number)
00949 {
00950   Window *w;
00951   FOR_ALL_WINDOWS_FROM_BACK(w) {
00952     if (w->window_class == cls && w->window_number == number) return w;
00953   }
00954 
00955   return NULL;
00956 }
00957 
00964 Window *FindWindowByClass(WindowClass cls)
00965 {
00966   Window *w;
00967   FOR_ALL_WINDOWS_FROM_BACK(w) {
00968     if (w->window_class == cls) return w;
00969   }
00970 
00971   return NULL;
00972 }
00973 
00980 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00981 {
00982   Window *w = FindWindowById(cls, number);
00983   if (force || w == NULL ||
00984       (w->flags & WF_STICKY) == 0) {
00985     delete w;
00986   }
00987 }
00988 
00993 void DeleteWindowByClass(WindowClass cls)
00994 {
00995   Window *w;
00996 
00997 restart_search:
00998   /* When we find the window to delete, we need to restart the search
00999    * as deleting this window could cascade in deleting (many) others
01000    * anywhere in the z-array */
01001   FOR_ALL_WINDOWS_FROM_BACK(w) {
01002     if (w->window_class == cls) {
01003       delete w;
01004       goto restart_search;
01005     }
01006   }
01007 }
01008 
01015 void DeleteCompanyWindows(CompanyID id)
01016 {
01017   Window *w;
01018 
01019 restart_search:
01020   /* When we find the window to delete, we need to restart the search
01021    * as deleting this window could cascade in deleting (many) others
01022    * anywhere in the z-array */
01023   FOR_ALL_WINDOWS_FROM_BACK(w) {
01024     if (w->owner == id) {
01025       delete w;
01026       goto restart_search;
01027     }
01028   }
01029 
01030   /* Also delete the company specific windows that don't have a company-colour. */
01031   DeleteWindowById(WC_BUY_COMPANY, id);
01032 }
01033 
01041 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
01042 {
01043   Window *w;
01044   FOR_ALL_WINDOWS_FROM_BACK(w) {
01045     if (w->owner != old_owner) continue;
01046 
01047     switch (w->window_class) {
01048       case WC_COMPANY_COLOUR:
01049       case WC_FINANCES:
01050       case WC_STATION_LIST:
01051       case WC_TRAINS_LIST:
01052       case WC_ROADVEH_LIST:
01053       case WC_SHIPS_LIST:
01054       case WC_AIRCRAFT_LIST:
01055       case WC_BUY_COMPANY:
01056       case WC_COMPANY:
01057       case WC_COMPANY_INFRASTRUCTURE:
01058         continue;
01059 
01060       default:
01061         w->owner = new_owner;
01062         break;
01063     }
01064   }
01065 }
01066 
01067 static void BringWindowToFront(Window *w);
01068 
01076 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
01077 {
01078   Window *w = FindWindowById(cls, number);
01079 
01080   if (w != NULL) {
01081     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
01082 
01083     w->SetWhiteBorder();
01084     BringWindowToFront(w);
01085     w->SetDirty();
01086   }
01087 
01088   return w;
01089 }
01090 
01091 static inline bool IsVitalWindow(const Window *w)
01092 {
01093   switch (w->window_class) {
01094     case WC_MAIN_TOOLBAR:
01095     case WC_STATUS_BAR:
01096     case WC_NEWS_WINDOW:
01097     case WC_SEND_NETWORK_MSG:
01098       return true;
01099 
01100     default:
01101       return false;
01102   }
01103 }
01104 
01113 static uint GetWindowZPriority(const Window *w)
01114 {
01115   assert(w->window_class != WC_INVALID);
01116 
01117   uint z_priority = 0;
01118 
01119   switch (w->window_class) {
01120     case WC_ENDSCREEN:
01121       ++z_priority;
01122 
01123     case WC_HIGHSCORE:
01124       ++z_priority;
01125 
01126     case WC_TOOLTIPS:
01127       ++z_priority;
01128 
01129     case WC_DROPDOWN_MENU:
01130       ++z_priority;
01131 
01132     case WC_MAIN_TOOLBAR:
01133     case WC_STATUS_BAR:
01134       ++z_priority;
01135 
01136     case WC_OSK:
01137       ++z_priority;
01138 
01139     case WC_QUERY_STRING:
01140     case WC_SEND_NETWORK_MSG:
01141       ++z_priority;
01142 
01143     case WC_ERRMSG:
01144     case WC_CONFIRM_POPUP_QUERY:
01145     case WC_MODAL_PROGRESS:
01146     case WC_NETWORK_STATUS_WINDOW:
01147       ++z_priority;
01148 
01149     case WC_GENERATE_LANDSCAPE:
01150     case WC_SAVELOAD:
01151     case WC_GAME_OPTIONS:
01152     case WC_CUSTOM_CURRENCY:
01153     case WC_NETWORK_WINDOW:
01154     case WC_GRF_PARAMETERS:
01155     case WC_AI_LIST:
01156     case WC_AI_SETTINGS:
01157     case WC_TEXTFILE:
01158       ++z_priority;
01159 
01160     case WC_CONSOLE:
01161       ++z_priority;
01162 
01163     case WC_NEWS_WINDOW:
01164       ++z_priority;
01165 
01166     default:
01167       ++z_priority;
01168 
01169     case WC_MAIN_WINDOW:
01170       return z_priority;
01171   }
01172 }
01173 
01178 static void AddWindowToZOrdering(Window *w)
01179 {
01180   assert(w->z_front == NULL && w->z_back == NULL);
01181 
01182   if (_z_front_window == NULL) {
01183     /* It's the only window. */
01184     _z_front_window = _z_back_window = w;
01185     w->z_front = w->z_back = NULL;
01186   } else {
01187     /* Search down the z-ordering for its location. */
01188     Window *v = _z_front_window;
01189     uint last_z_priority = UINT_MAX;
01190     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
01191       if (v->window_class != WC_INVALID) {
01192         /* Sanity check z-ordering, while we're at it. */
01193         assert(last_z_priority >= GetWindowZPriority(v));
01194         last_z_priority = GetWindowZPriority(v);
01195       }
01196 
01197       v = v->z_back;
01198     }
01199 
01200     if (v == NULL) {
01201       /* It's the new back window. */
01202       w->z_front = _z_back_window;
01203       w->z_back = NULL;
01204       _z_back_window->z_back = w;
01205       _z_back_window = w;
01206     } else if (v == _z_front_window) {
01207       /* It's the new front window. */
01208       w->z_front = NULL;
01209       w->z_back = _z_front_window;
01210       _z_front_window->z_front = w;
01211       _z_front_window = w;
01212     } else {
01213       /* It's somewhere else in the z-ordering. */
01214       w->z_front = v->z_front;
01215       w->z_back = v;
01216       v->z_front->z_back = w;
01217       v->z_front = w;
01218     }
01219   }
01220 }
01221 
01222 
01227 static void RemoveWindowFromZOrdering(Window *w)
01228 {
01229   if (w->z_front == NULL) {
01230     assert(_z_front_window == w);
01231     _z_front_window = w->z_back;
01232   } else {
01233     w->z_front->z_back = w->z_back;
01234   }
01235 
01236   if (w->z_back == NULL) {
01237     assert(_z_back_window == w);
01238     _z_back_window = w->z_front;
01239   } else {
01240     w->z_back->z_front = w->z_front;
01241   }
01242 
01243   w->z_front = w->z_back = NULL;
01244 }
01245 
01251 static void BringWindowToFront(Window *w)
01252 {
01253   RemoveWindowFromZOrdering(w);
01254   AddWindowToZOrdering(w);
01255 
01256   w->SetDirty();
01257 }
01258 
01267 void Window::InitializeData(WindowNumber window_number)
01268 {
01269   /* Set up window properties; some of them are needed to set up smallest size below */
01270   this->window_class = this->window_desc->cls;
01271   this->SetWhiteBorder();
01272   if (this->window_desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
01273   this->owner = INVALID_OWNER;
01274   this->nested_focus = NULL;
01275   this->window_number = window_number;
01276 
01277   this->OnInit();
01278   /* Initialize nested widget tree. */
01279   if (this->nested_array == NULL) {
01280     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01281     this->nested_root->SetupSmallestSize(this, true);
01282   } else {
01283     this->nested_root->SetupSmallestSize(this, false);
01284   }
01285   /* Initialize to smallest size. */
01286   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
01287 
01288   /* Further set up window properties,
01289    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
01290   this->resize.step_width  = this->nested_root->resize_x;
01291   this->resize.step_height = this->nested_root->resize_y;
01292 
01293   /* Give focus to the opened window unless a text box
01294    * of focused window has focus (so we don't interrupt typing). But if the new
01295    * window has a text box, then take focus anyway. */
01296   if (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL) SetFocusedWindow(this);
01297 
01298   /* Insert the window into the correct location in the z-ordering. */
01299   AddWindowToZOrdering(this);
01300 }
01301 
01309 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
01310 {
01311   this->left = x;
01312   this->top = y;
01313   this->width = sm_width;
01314   this->height = sm_height;
01315 }
01316 
01327 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
01328 {
01329   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
01330   def_height = max(def_height, this->height);
01331   /* Try to make windows smaller when our window is too small.
01332    * w->(width|height) is normally the same as min_(width|height),
01333    * but this way the GUIs can be made a little more dynamic;
01334    * one can use the same spec for multiple windows and those
01335    * can then determine the real minimum size of the window. */
01336   if (this->width != def_width || this->height != def_height) {
01337     /* Think about the overlapping toolbars when determining the minimum window size */
01338     int free_height = _screen.height;
01339     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01340     if (wt != NULL) free_height -= wt->height;
01341     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01342     if (wt != NULL) free_height -= wt->height;
01343 
01344     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
01345     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
01346 
01347     /* X and Y has to go by step.. calculate it.
01348      * The cast to int is necessary else x/y are implicitly casted to
01349      * unsigned int, which won't work. */
01350     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01351     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01352 
01353     ResizeWindow(this, enlarge_x, enlarge_y);
01354     /* ResizeWindow() calls this->OnResize(). */
01355   } else {
01356     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
01357     this->OnResize();
01358   }
01359 
01360   int nx = this->left;
01361   int ny = this->top;
01362 
01363   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01364 
01365   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01366   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01367   nx = max(nx, 0);
01368 
01369   if (this->viewport != NULL) {
01370     this->viewport->left += nx - this->left;
01371     this->viewport->top  += ny - this->top;
01372   }
01373   this->left = nx;
01374   this->top = ny;
01375 
01376   this->SetDirty();
01377 }
01378 
01390 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01391 {
01392   int right  = width + left;
01393   int bottom = height + top;
01394 
01395   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01396   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01397 
01398   /* Make sure it is not obscured by any window. */
01399   const Window *w;
01400   FOR_ALL_WINDOWS_FROM_BACK(w) {
01401     if (w->window_class == WC_MAIN_WINDOW) continue;
01402 
01403     if (right > w->left &&
01404         w->left + w->width > left &&
01405         bottom > w->top &&
01406         w->top + w->height > top) {
01407       return false;
01408     }
01409   }
01410 
01411   pos.x = left;
01412   pos.y = top;
01413   return true;
01414 }
01415 
01427 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01428 {
01429   /* Left part of the rectangle may be at most 1/4 off-screen,
01430    * right part of the rectangle may be at most 1/2 off-screen
01431    */
01432   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01433   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01434   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01435 
01436   /* Make sure it is not obscured by any window. */
01437   const Window *w;
01438   FOR_ALL_WINDOWS_FROM_BACK(w) {
01439     if (w->window_class == WC_MAIN_WINDOW) continue;
01440 
01441     if (left + width > w->left &&
01442         w->left + w->width > left &&
01443         top + height > w->top &&
01444         w->top + w->height > top) {
01445       return false;
01446     }
01447   }
01448 
01449   pos.x = left;
01450   pos.y = top;
01451   return true;
01452 }
01453 
01460 static Point GetAutoPlacePosition(int width, int height)
01461 {
01462   Point pt;
01463 
01464   /* First attempt, try top-left of the screen */
01465   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01466   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01467 
01468   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01469    * The new window must be entirely on-screen, and not overlap with an existing window.
01470    * Eight starting points are tried, two at each corner.
01471    */
01472   const Window *w;
01473   FOR_ALL_WINDOWS_FROM_BACK(w) {
01474     if (w->window_class == WC_MAIN_WINDOW) continue;
01475 
01476     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01477     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01478     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01479     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01480     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01481     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01482     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01483     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01484   }
01485 
01486   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01487    * The new window may be partly off-screen, and must not overlap with an existing window.
01488    * Only four starting points are tried.
01489    */
01490   FOR_ALL_WINDOWS_FROM_BACK(w) {
01491     if (w->window_class == WC_MAIN_WINDOW) continue;
01492 
01493     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01494     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01495     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01496     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01497   }
01498 
01499   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01500    * of (+5, +5)
01501    */
01502   int left = 0, top = 24;
01503 
01504 restart:
01505   FOR_ALL_WINDOWS_FROM_BACK(w) {
01506     if (w->left == left && w->top == top) {
01507       left += 5;
01508       top += 5;
01509       goto restart;
01510     }
01511   }
01512 
01513   pt.x = left;
01514   pt.y = top;
01515   return pt;
01516 }
01517 
01524 Point GetToolbarAlignedWindowPosition(int window_width)
01525 {
01526   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01527   assert(w != NULL);
01528   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01529   return pt;
01530 }
01531 
01549 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01550 {
01551   Point pt;
01552   const Window *w;
01553 
01554   int16 default_width  = max(desc->GetDefaultWidth(),  sm_width);
01555   int16 default_height = max(desc->GetDefaultHeight(), sm_height);
01556 
01557   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01558       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01559       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01560 
01561     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01562     if (pt.x > _screen.width + 10 - default_width) {
01563       pt.x = (_screen.width + 10 - default_width) - 20;
01564     }
01565     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01566     return pt;
01567   }
01568 
01569   switch (desc->default_pos) {
01570     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01571       return GetToolbarAlignedWindowPosition(default_width);
01572 
01573     case WDP_AUTO: // Find a good automatic position for the window
01574       return GetAutoPlacePosition(default_width, default_height);
01575 
01576     case WDP_CENTER: // Centre the window horizontally
01577       pt.x = (_screen.width - default_width) / 2;
01578       pt.y = (_screen.height - default_height) / 2;
01579       break;
01580 
01581     case WDP_MANUAL:
01582       pt.x = 0;
01583       pt.y = 0;
01584       break;
01585 
01586     default:
01587       NOT_REACHED();
01588   }
01589 
01590   return pt;
01591 }
01592 
01593 /* virtual */ Point Window::OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
01594 {
01595   return LocalGetWindowPlacement(this->window_desc, sm_width, sm_height, window_number);
01596 }
01597 
01605 void Window::CreateNestedTree(bool fill_nested)
01606 {
01607   int biggest_index = -1;
01608   this->nested_root = MakeWindowNWidgetTree(this->window_desc->nwid_parts, this->window_desc->nwid_length, &biggest_index, &this->shade_select);
01609   this->nested_array_size = (uint)(biggest_index + 1);
01610 
01611   if (fill_nested) {
01612     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01613     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01614   }
01615 }
01616 
01621 void Window::FinishInitNested(WindowNumber window_number)
01622 {
01623   this->InitializeData(window_number);
01624   this->ApplyDefaults();
01625   Point pt = this->OnInitialPosition(this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01626   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01627   this->FindWindowPlacementAndResize(this->window_desc->GetDefaultWidth(), this->window_desc->GetDefaultHeight());
01628 }
01629 
01634 void Window::InitNested(WindowNumber window_number)
01635 {
01636   this->CreateNestedTree(false);
01637   this->FinishInitNested(window_number);
01638 }
01639 
01644 Window::Window(WindowDesc *desc) : window_desc(desc), scrolling_scrollbar(-1)
01645 {
01646 }
01647 
01655 Window *FindWindowFromPt(int x, int y)
01656 {
01657   Window *w;
01658   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01659     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01660       return w;
01661     }
01662   }
01663 
01664   return NULL;
01665 }
01666 
01670 void InitWindowSystem()
01671 {
01672   IConsoleClose();
01673 
01674   _z_back_window = NULL;
01675   _z_front_window = NULL;
01676   _focused_window = NULL;
01677   _mouseover_last_w = NULL;
01678   _last_scroll_window = NULL;
01679   _scrolling_viewport = false;
01680   _mouse_hovering = false;
01681 
01682   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01683   NWidgetScrollbar::InvalidateDimensionCache();
01684 
01685   ShowFirstError();
01686 }
01687 
01691 void UnInitWindowSystem()
01692 {
01693   UnshowCriticalError();
01694 
01695   Window *w;
01696   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01697 
01698   for (w = _z_front_window; w != NULL; /* nothing */) {
01699     Window *to_del = w;
01700     w = w->z_back;
01701     free(to_del);
01702   }
01703 
01704   _z_front_window = NULL;
01705   _z_back_window = NULL;
01706 }
01707 
01711 void ResetWindowSystem()
01712 {
01713   UnInitWindowSystem();
01714   InitWindowSystem();
01715   _thd.Reset();
01716 }
01717 
01718 static void DecreaseWindowCounters()
01719 {
01720   Window *w;
01721   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01722     if (_scroller_click_timeout == 0) {
01723       /* Unclick scrollbar buttons if they are pressed. */
01724       for (uint i = 0; i < w->nested_array_size; i++) {
01725         NWidgetBase *nwid = w->nested_array[i];
01726         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01727           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01728           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01729             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01730             w->scrolling_scrollbar = -1;
01731             sb->SetDirty(w);
01732           }
01733         }
01734       }
01735     }
01736 
01737     /* Handle editboxes */
01738     for (SmallMap<int, QueryString*>::Pair *it = w->querystrings.Begin(); it != w->querystrings.End(); ++it) {
01739       it->second->HandleEditBox(w, it->first);
01740     }
01741 
01742     w->OnMouseLoop();
01743   }
01744 
01745   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01746     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
01747       CLRBITS(w->flags, WF_TIMEOUT);
01748 
01749       w->OnTimeout();
01750       w->RaiseButtons(true);
01751     }
01752   }
01753 }
01754 
01755 static void HandlePlacePresize()
01756 {
01757   if (_special_mouse_mode != WSM_PRESIZE) return;
01758 
01759   Window *w = _thd.GetCallbackWnd();
01760   if (w == NULL) return;
01761 
01762   Point pt = GetTileBelowCursor();
01763   if (pt.x == -1) {
01764     _thd.selend.x = -1;
01765     return;
01766   }
01767 
01768   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01769 }
01770 
01775 static EventState HandleMouseDragDrop()
01776 {
01777   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01778 
01779   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
01780 
01781   Window *w = _thd.GetCallbackWnd();
01782   if (w != NULL) {
01783     /* Send an event in client coordinates. */
01784     Point pt;
01785     pt.x = _cursor.pos.x - w->left;
01786     pt.y = _cursor.pos.y - w->top;
01787     if (_left_button_down) {
01788       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01789     } else {
01790       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01791     }
01792   }
01793 
01794   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
01795   return ES_HANDLED;
01796 }
01797 
01799 static void HandleMouseOver()
01800 {
01801   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01802 
01803   /* We changed window, put a MOUSEOVER event to the last window */
01804   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01805     /* Reset mouse-over coordinates of previous window */
01806     Point pt = { -1, -1 };
01807     _mouseover_last_w->OnMouseOver(pt, 0);
01808   }
01809 
01810   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01811   _mouseover_last_w = w;
01812 
01813   if (w != NULL) {
01814     /* send an event in client coordinates. */
01815     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01816     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01817     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01818   }
01819 }
01820 
01822 static const int MIN_VISIBLE_TITLE_BAR = 13;
01823 
01825 enum PreventHideDirection {
01826   PHD_UP,   
01827   PHD_DOWN, 
01828 };
01829 
01840 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01841 {
01842   if (v == NULL) return;
01843 
01844   int v_bottom = v->top + v->height;
01845   int v_right = v->left + v->width;
01846   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.
01847 
01848   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01849   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01850 
01851   /* Vertically, the rectangle is hidden behind v. */
01852   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01853     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01854     return;
01855   }
01856   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01857     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01858     return;
01859   }
01860 
01861   /* Horizontally also hidden, force movement to a safe area. */
01862   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01863     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01864   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01865     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01866   } else {
01867     *ny = safe_y;
01868   }
01869 }
01870 
01878 static void EnsureVisibleCaption(Window *w, int nx, int ny)
01879 {
01880   /* Search for the title bar rectangle. */
01881   Rect caption_rect;
01882   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01883   if (caption != NULL) {
01884     caption_rect.left   = caption->pos_x;
01885     caption_rect.right  = caption->pos_x + caption->current_x;
01886     caption_rect.top    = caption->pos_y;
01887     caption_rect.bottom = caption->pos_y + caption->current_y;
01888 
01889     /* Make sure the window doesn't leave the screen */
01890     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01891     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01892 
01893     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01894     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01895     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01896   }
01897 
01898   if (w->viewport != NULL) {
01899     w->viewport->left += nx - w->left;
01900     w->viewport->top  += ny - w->top;
01901   }
01902 
01903   w->left = nx;
01904   w->top  = ny;
01905 }
01906 
01917 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
01918 {
01919   if (delta_x != 0 || delta_y != 0) {
01920     if (clamp_to_screen) {
01921       /* Determine the new right/bottom position. If that is outside of the bounds of
01922        * the resolution clamp it in such a manner that it stays within the bounds. */
01923       int new_right  = w->left + w->width  + delta_x;
01924       int new_bottom = w->top  + w->height + delta_y;
01925       if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
01926       if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
01927     }
01928 
01929     w->SetDirty();
01930 
01931     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);
01932     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);
01933     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01934     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01935 
01936     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);
01937     w->width  = w->nested_root->current_x;
01938     w->height = w->nested_root->current_y;
01939   }
01940 
01941   EnsureVisibleCaption(w, w->left, w->top);
01942 
01943   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
01944   w->OnResize();
01945   w->SetDirty();
01946 }
01947 
01953 int GetMainViewTop()
01954 {
01955   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01956   return (w == NULL) ? 0 : w->top + w->height;
01957 }
01958 
01964 int GetMainViewBottom()
01965 {
01966   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01967   return (w == NULL) ? _screen.height : w->top;
01968 }
01969 
01970 static bool _dragging_window; 
01971 
01976 static EventState HandleWindowDragging()
01977 {
01978   /* Get out immediately if no window is being dragged at all. */
01979   if (!_dragging_window) return ES_NOT_HANDLED;
01980 
01981   /* If button still down, but cursor hasn't moved, there is nothing to do. */
01982   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01983 
01984   /* Otherwise find the window... */
01985   Window *w;
01986   FOR_ALL_WINDOWS_FROM_BACK(w) {
01987     if (w->flags & WF_DRAGGING) {
01988       /* Stop the dragging if the left mouse button was released */
01989       if (!_left_button_down) {
01990         w->flags &= ~WF_DRAGGING;
01991         break;
01992       }
01993 
01994       w->SetDirty();
01995 
01996       int x = _cursor.pos.x + _drag_delta.x;
01997       int y = _cursor.pos.y + _drag_delta.y;
01998       int nx = x;
01999       int ny = y;
02000 
02001       if (_settings_client.gui.window_snap_radius != 0) {
02002         const Window *v;
02003 
02004         int hsnap = _settings_client.gui.window_snap_radius;
02005         int vsnap = _settings_client.gui.window_snap_radius;
02006         int delta;
02007 
02008         FOR_ALL_WINDOWS_FROM_BACK(v) {
02009           if (v == w) continue; // Don't snap at yourself
02010 
02011           if (y + w->height > v->top && y < v->top + v->height) {
02012             /* Your left border <-> other right border */
02013             delta = abs(v->left + v->width - x);
02014             if (delta <= hsnap) {
02015               nx = v->left + v->width;
02016               hsnap = delta;
02017             }
02018 
02019             /* Your right border <-> other left border */
02020             delta = abs(v->left - x - w->width);
02021             if (delta <= hsnap) {
02022               nx = v->left - w->width;
02023               hsnap = delta;
02024             }
02025           }
02026 
02027           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
02028             /* Your left border <-> other left border */
02029             delta = abs(v->left - x);
02030             if (delta <= hsnap) {
02031               nx = v->left;
02032               hsnap = delta;
02033             }
02034 
02035             /* Your right border <-> other right border */
02036             delta = abs(v->left + v->width - x - w->width);
02037             if (delta <= hsnap) {
02038               nx = v->left + v->width - w->width;
02039               hsnap = delta;
02040             }
02041           }
02042 
02043           if (x + w->width > v->left && x < v->left + v->width) {
02044             /* Your top border <-> other bottom border */
02045             delta = abs(v->top + v->height - y);
02046             if (delta <= vsnap) {
02047               ny = v->top + v->height;
02048               vsnap = delta;
02049             }
02050 
02051             /* Your bottom border <-> other top border */
02052             delta = abs(v->top - y - w->height);
02053             if (delta <= vsnap) {
02054               ny = v->top - w->height;
02055               vsnap = delta;
02056             }
02057           }
02058 
02059           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
02060             /* Your top border <-> other top border */
02061             delta = abs(v->top - y);
02062             if (delta <= vsnap) {
02063               ny = v->top;
02064               vsnap = delta;
02065             }
02066 
02067             /* Your bottom border <-> other bottom border */
02068             delta = abs(v->top + v->height - y - w->height);
02069             if (delta <= vsnap) {
02070               ny = v->top + v->height - w->height;
02071               vsnap = delta;
02072             }
02073           }
02074         }
02075       }
02076 
02077       EnsureVisibleCaption(w, nx, ny);
02078 
02079       w->SetDirty();
02080       return ES_HANDLED;
02081     } else if (w->flags & WF_SIZING) {
02082       /* Stop the sizing if the left mouse button was released */
02083       if (!_left_button_down) {
02084         w->flags &= ~WF_SIZING;
02085         w->SetDirty();
02086         break;
02087       }
02088 
02089       /* Compute difference in pixels between cursor position and reference point in the window.
02090        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
02091        */
02092       int x, y = _cursor.pos.y - _drag_delta.y;
02093       if (w->flags & WF_SIZING_LEFT) {
02094         x = _drag_delta.x - _cursor.pos.x;
02095       } else {
02096         x = _cursor.pos.x - _drag_delta.x;
02097       }
02098 
02099       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
02100       if (w->resize.step_width  == 0) x = 0;
02101       if (w->resize.step_height == 0) y = 0;
02102 
02103       /* Check the resize button won't go past the bottom of the screen */
02104       if (w->top + w->height + y > _screen.height) {
02105         y = _screen.height - w->height - w->top;
02106       }
02107 
02108       /* X and Y has to go by step.. calculate it.
02109        * The cast to int is necessary else x/y are implicitly casted to
02110        * unsigned int, which won't work. */
02111       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
02112       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
02113 
02114       /* Check that we don't go below the minimum set size */
02115       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
02116         x = w->nested_root->smallest_x - w->width;
02117       }
02118       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
02119         y = w->nested_root->smallest_y - w->height;
02120       }
02121 
02122       /* Window already on size */
02123       if (x == 0 && y == 0) return ES_HANDLED;
02124 
02125       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
02126       _drag_delta.y += y;
02127       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
02128         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
02129         w->SetDirty();
02130         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
02131         /* ResizeWindow() below ensures marking new position as dirty. */
02132       } else {
02133         _drag_delta.x += x;
02134       }
02135 
02136       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
02137       ResizeWindow(w, x, y);
02138       return ES_HANDLED;
02139     }
02140   }
02141 
02142   _dragging_window = false;
02143   return ES_HANDLED;
02144 }
02145 
02150 static void StartWindowDrag(Window *w)
02151 {
02152   w->flags |= WF_DRAGGING;
02153   w->flags &= ~WF_CENTERED;
02154   _dragging_window = true;
02155 
02156   _drag_delta.x = w->left - _cursor.pos.x;
02157   _drag_delta.y = w->top  - _cursor.pos.y;
02158 
02159   BringWindowToFront(w);
02160   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02161 }
02162 
02168 static void StartWindowSizing(Window *w, bool to_left)
02169 {
02170   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
02171   w->flags &= ~WF_CENTERED;
02172   _dragging_window = true;
02173 
02174   _drag_delta.x = _cursor.pos.x;
02175   _drag_delta.y = _cursor.pos.y;
02176 
02177   BringWindowToFront(w);
02178   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02179 }
02180 
02185 static EventState HandleScrollbarScrolling()
02186 {
02187   Window *w;
02188   FOR_ALL_WINDOWS_FROM_BACK(w) {
02189     if (w->scrolling_scrollbar >= 0) {
02190       /* Abort if no button is clicked any more. */
02191       if (!_left_button_down) {
02192         w->scrolling_scrollbar = -1;
02193         w->SetDirty();
02194         return ES_HANDLED;
02195       }
02196 
02197       int i;
02198       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
02199       bool rtl = false;
02200 
02201       if (sb->type == NWID_HSCROLLBAR) {
02202         i = _cursor.pos.x - _cursorpos_drag_start.x;
02203         rtl = _current_text_dir == TD_RTL;
02204       } else {
02205         i = _cursor.pos.y - _cursorpos_drag_start.y;
02206       }
02207 
02208       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
02209         if (_scroller_click_timeout == 1) {
02210           _scroller_click_timeout = 3;
02211           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
02212           w->SetDirty();
02213         }
02214         return ES_HANDLED;
02215       }
02216 
02217       /* Find the item we want to move to and make sure it's inside bounds. */
02218       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
02219       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
02220       if (pos != sb->GetPosition()) {
02221         sb->SetPosition(pos);
02222         w->SetDirty();
02223       }
02224       return ES_HANDLED;
02225     }
02226   }
02227 
02228   return ES_NOT_HANDLED;
02229 }
02230 
02235 static EventState HandleViewportScroll()
02236 {
02237   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02238 
02239   if (!_scrolling_viewport) return ES_NOT_HANDLED;
02240 
02241   /* When we don't have a last scroll window we are starting to scroll.
02242    * When the last scroll window and this are not the same we went
02243    * outside of the window and should not left-mouse scroll anymore. */
02244   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
02245 
02246   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
02247     _cursor.fix_at = false;
02248     _scrolling_viewport = false;
02249     _last_scroll_window = NULL;
02250     return ES_NOT_HANDLED;
02251   }
02252 
02253   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
02254     /* If the main window is following a vehicle, then first let go of it! */
02255     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
02256     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
02257     return ES_NOT_HANDLED;
02258   }
02259 
02260   Point delta;
02261   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
02262     delta.x = -_cursor.delta.x;
02263     delta.y = -_cursor.delta.y;
02264   } else {
02265     delta.x = _cursor.delta.x;
02266     delta.y = _cursor.delta.y;
02267   }
02268 
02269   if (scrollwheel_scrolling) {
02270     /* We are using scrollwheels for scrolling */
02271     delta.x = _cursor.h_wheel;
02272     delta.y = _cursor.v_wheel;
02273     _cursor.v_wheel = 0;
02274     _cursor.h_wheel = 0;
02275   }
02276 
02277   /* Create a scroll-event and send it to the window */
02278   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
02279 
02280   _cursor.delta.x = 0;
02281   _cursor.delta.y = 0;
02282   return ES_HANDLED;
02283 }
02284 
02295 static bool MaybeBringWindowToFront(Window *w)
02296 {
02297   bool bring_to_front = false;
02298 
02299   if (w->window_class == WC_MAIN_WINDOW ||
02300       IsVitalWindow(w) ||
02301       w->window_class == WC_TOOLTIPS ||
02302       w->window_class == WC_DROPDOWN_MENU) {
02303     return true;
02304   }
02305 
02306   /* Use unshaded window size rather than current size for shaded windows. */
02307   int w_width  = w->width;
02308   int w_height = w->height;
02309   if (w->IsShaded()) {
02310     w_width  = w->unshaded_size.width;
02311     w_height = w->unshaded_size.height;
02312   }
02313 
02314   Window *u;
02315   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
02316     /* A modal child will prevent the activation of the parent window */
02317     if (u->parent == w && (u->window_desc->flags & WDF_MODAL)) {
02318       u->SetWhiteBorder();
02319       u->SetDirty();
02320       return false;
02321     }
02322 
02323     if (u->window_class == WC_MAIN_WINDOW ||
02324         IsVitalWindow(u) ||
02325         u->window_class == WC_TOOLTIPS ||
02326         u->window_class == WC_DROPDOWN_MENU) {
02327       continue;
02328     }
02329 
02330     /* Window sizes don't interfere, leave z-order alone */
02331     if (w->left + w_width <= u->left ||
02332         u->left + u->width <= w->left ||
02333         w->top  + w_height <= u->top ||
02334         u->top + u->height <= w->top) {
02335       continue;
02336     }
02337 
02338     bring_to_front = true;
02339   }
02340 
02341   if (bring_to_front) BringWindowToFront(w);
02342   return true;
02343 }
02344 
02353 EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
02354 {
02355   QueryString *query = this->GetQueryString(wid);
02356   if (query == NULL) return ES_NOT_HANDLED;
02357 
02358   int action = QueryString::ACTION_NOTHING;
02359 
02360   switch (query->text.HandleKeyPress(key, keycode)) {
02361     case HKPR_EDITING:
02362       this->SetWidgetDirty(wid);
02363       this->OnEditboxChanged(wid);
02364       break;
02365 
02366     case HKPR_CURSOR:
02367       this->SetWidgetDirty(wid);
02368       /* For the OSK also invalidate the parent window */
02369       if (this->window_class == WC_OSK) this->InvalidateData();
02370       break;
02371 
02372     case HKPR_CONFIRM:
02373       if (this->window_class == WC_OSK) {
02374         this->OnClick(Point(), WID_OSK_OK, 1);
02375       } else if (query->ok_button >= 0) {
02376         this->OnClick(Point(), query->ok_button, 1);
02377       } else {
02378         action = query->ok_button;
02379       }
02380       break;
02381 
02382     case HKPR_CANCEL:
02383       if (this->window_class == WC_OSK) {
02384         this->OnClick(Point(), WID_OSK_CANCEL, 1);
02385       } else if (query->cancel_button >= 0) {
02386         this->OnClick(Point(), query->cancel_button, 1);
02387       } else {
02388         action = query->cancel_button;
02389       }
02390       break;
02391 
02392     case HKPR_NOT_HANDLED:
02393       return ES_NOT_HANDLED;
02394 
02395     default: break;
02396   }
02397 
02398   switch (action) {
02399     case QueryString::ACTION_DESELECT:
02400       this->UnfocusFocusedWidget();
02401       break;
02402 
02403     case QueryString::ACTION_CLEAR:
02404       query->text.DeleteAll();
02405       this->SetWidgetDirty(wid);
02406       this->OnEditboxChanged(wid);
02407       break;
02408 
02409     default:
02410       break;
02411   }
02412 
02413   return ES_HANDLED;
02414 }
02415 
02420 void HandleKeypress(uint32 raw_key)
02421 {
02422   /* World generation is multithreaded and messes with companies.
02423    * But there is no company related window open anyway, so _current_company is not used. */
02424   assert(HasModalProgress() || IsLocalCompany());
02425 
02426   /* Setup event */
02427   uint16 key     = GB(raw_key,  0, 16);
02428   uint16 keycode = GB(raw_key, 16, 16);
02429 
02430   /*
02431    * The Unicode standard defines an area called the private use area. Code points in this
02432    * area are reserved for private use and thus not portable between systems. For instance,
02433    * Apple defines code points for the arrow keys in this area, but these are only printable
02434    * on a system running OS X. We don't want these keys to show up in text fields and such,
02435    * and thus we have to clear the unicode character when we encounter such a key.
02436    */
02437   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02438 
02439   /*
02440    * If both key and keycode is zero, we don't bother to process the event.
02441    */
02442   if (key == 0 && keycode == 0) return;
02443 
02444   /* Check if the focused window has a focused editbox */
02445   if (EditBoxInGlobalFocus()) {
02446     /* All input will in this case go to the focused editbox */
02447     if (_focused_window->window_class == WC_CONSOLE) {
02448       if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02449     } else {
02450       if (_focused_window->HandleEditBoxKey(_focused_window->nested_focus->index, key, keycode) == ES_HANDLED) return;
02451     }
02452   }
02453 
02454   /* Call the event, start with the uppermost window, but ignore the toolbar. */
02455   Window *w;
02456   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02457     if (w->window_class == WC_MAIN_TOOLBAR) continue;
02458     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02459   }
02460 
02461   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02462   /* When there is no toolbar w is null, check for that */
02463   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02464 
02465   HandleGlobalHotkeys(key, keycode);
02466 }
02467 
02471 void HandleCtrlChanged()
02472 {
02473   /* Call the event, start with the uppermost window. */
02474   Window *w;
02475   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02476     if (w->OnCTRLStateChange() == ES_HANDLED) return;
02477   }
02478 }
02479 
02486 static int _input_events_this_tick = 0;
02487 
02492 static void HandleAutoscroll()
02493 {
02494   if (_game_mode == GM_MENU || HasModalProgress()) return;
02495   if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
02496   if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
02497 
02498   int x = _cursor.pos.x;
02499   int y = _cursor.pos.y;
02500   Window *w = FindWindowFromPt(x, y);
02501   if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
02502   if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
02503 
02504   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02505   if (vp == NULL) return;
02506 
02507   x -= vp->left;
02508   y -= vp->top;
02509 
02510   /* here allows scrolling in both x and y axis */
02511 #define scrollspeed 3
02512   if (x - 15 < 0) {
02513     w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02514   } else if (15 - (vp->width - x) > 0) {
02515     w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02516   }
02517   if (y - 15 < 0) {
02518     w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02519   } else if (15 - (vp->height - y) > 0) {
02520     w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02521   }
02522 #undef scrollspeed
02523 }
02524 
02525 enum MouseClick {
02526   MC_NONE = 0,
02527   MC_LEFT,
02528   MC_RIGHT,
02529   MC_DOUBLE_LEFT,
02530   MC_HOVER,
02531 
02532   MAX_OFFSET_DOUBLE_CLICK = 5,     
02533   TIME_BETWEEN_DOUBLE_CLICK = 500, 
02534   MAX_OFFSET_HOVER = 5,            
02535 };
02536 extern EventState VpHandlePlaceSizingDrag();
02537 
02538 static void ScrollMainViewport(int x, int y)
02539 {
02540   if (_game_mode != GM_MENU) {
02541     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02542     assert(w);
02543 
02544     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02545     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02546   }
02547 }
02548 
02558 static const int8 scrollamt[16][2] = {
02559   { 0,  0}, 
02560   {-2,  0}, 
02561   { 0, -2}, 
02562   {-2, -1}, 
02563   { 2,  0}, 
02564   { 0,  0}, 
02565   { 2, -1}, 
02566   { 0, -2}, 
02567   { 0,  2}, 
02568   {-2,  1}, 
02569   { 0,  0}, 
02570   {-2,  0}, 
02571   { 2,  1}, 
02572   { 0,  2}, 
02573   { 2,  0}, 
02574   { 0,  0}, 
02575 };
02576 
02577 static void HandleKeyScrolling()
02578 {
02579   /*
02580    * Check that any of the dirkeys is pressed and that the focused window
02581    * doesn't have an edit-box as focused widget.
02582    */
02583   if (_dirkeys && !EditBoxInGlobalFocus()) {
02584     int factor = _shift_pressed ? 50 : 10;
02585     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02586   }
02587 }
02588 
02589 static void MouseLoop(MouseClick click, int mousewheel)
02590 {
02591   /* World generation is multithreaded and messes with companies.
02592    * But there is no company related window open anyway, so _current_company is not used. */
02593   assert(HasModalProgress() || IsLocalCompany());
02594 
02595   HandlePlacePresize();
02596   UpdateTileSelection();
02597 
02598   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
02599   if (HandleMouseDragDrop()      == ES_HANDLED) return;
02600   if (HandleWindowDragging()     == ES_HANDLED) return;
02601   if (HandleScrollbarScrolling() == ES_HANDLED) return;
02602   if (HandleViewportScroll()     == ES_HANDLED) return;
02603 
02604   HandleMouseOver();
02605 
02606   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02607   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02608 
02609   int x = _cursor.pos.x;
02610   int y = _cursor.pos.y;
02611   Window *w = FindWindowFromPt(x, y);
02612   if (w == NULL) return;
02613 
02614   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02615   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02616 
02617   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
02618   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
02619 
02620   if (mousewheel != 0) {
02621     /* Send mousewheel event to window */
02622     w->OnMouseWheel(mousewheel);
02623 
02624     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02625     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02626   }
02627 
02628   if (vp != NULL) {
02629     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02630     switch (click) {
02631       case MC_DOUBLE_LEFT:
02632       case MC_LEFT:
02633         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02634         if (!HandleViewportClicked(vp, x, y) &&
02635             !(w->flags & WF_DISABLE_VP_SCROLL) &&
02636             _settings_client.gui.left_mouse_btn_scrolling) {
02637           _scrolling_viewport = true;
02638           _cursor.fix_at = false;
02639         }
02640         break;
02641 
02642       case MC_RIGHT:
02643         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
02644           _scrolling_viewport = true;
02645           _cursor.fix_at = true;
02646 
02647           /* clear 2D scrolling caches before we start a 2D scroll */
02648           _cursor.h_wheel = 0;
02649           _cursor.v_wheel = 0;
02650         }
02651         break;
02652 
02653       default:
02654         break;
02655     }
02656   } else {
02657     switch (click) {
02658       case MC_LEFT:
02659       case MC_DOUBLE_LEFT:
02660         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02661         break;
02662 
02663       default:
02664         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02665         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02666          * Simulate a right button click so we can get started. */
02667         /* FALL THROUGH */
02668 
02669       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02670 
02671       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02672     }
02673   }
02674 }
02675 
02679 void HandleMouseEvents()
02680 {
02681   /* World generation is multithreaded and messes with companies.
02682    * But there is no company related window open anyway, so _current_company is not used. */
02683   assert(HasModalProgress() || IsLocalCompany());
02684 
02685   static int double_click_time = 0;
02686   static Point double_click_pos = {0, 0};
02687 
02688   /* Mouse event? */
02689   MouseClick click = MC_NONE;
02690   if (_left_button_down && !_left_button_clicked) {
02691     click = MC_LEFT;
02692     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02693         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
02694         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02695       click = MC_DOUBLE_LEFT;
02696     }
02697     double_click_time = _realtime_tick;
02698     double_click_pos = _cursor.pos;
02699     _left_button_clicked = true;
02700     _input_events_this_tick++;
02701   } else if (_right_button_clicked) {
02702     _right_button_clicked = false;
02703     click = MC_RIGHT;
02704     _input_events_this_tick++;
02705   }
02706 
02707   int mousewheel = 0;
02708   if (_cursor.wheel) {
02709     mousewheel = _cursor.wheel;
02710     _cursor.wheel = 0;
02711     _input_events_this_tick++;
02712   }
02713 
02714   static uint32 hover_time = 0;
02715   static Point hover_pos = {0, 0};
02716 
02717   if (_settings_client.gui.hover_delay > 0) {
02718     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02719         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
02720         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02721       hover_pos = _cursor.pos;
02722       hover_time = _realtime_tick;
02723       _mouse_hovering = false;
02724     } else {
02725       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02726         click = MC_HOVER;
02727         _input_events_this_tick++;
02728         _mouse_hovering = true;
02729       }
02730     }
02731   }
02732 
02733   /* Handle sprite picker before any GUI interaction */
02734   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02735     /* Next realtime tick? Then redraw has finished */
02736     _newgrf_debug_sprite_picker.mode = SPM_NONE;
02737     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02738   }
02739 
02740   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02741     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
02742     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
02743     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02744     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02745     _newgrf_debug_sprite_picker.sprites.Clear();
02746     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02747     MarkWholeScreenDirty();
02748   } else {
02749     MouseLoop(click, mousewheel);
02750   }
02751 
02752   /* We have moved the mouse the required distance,
02753    * no need to move it at any later time. */
02754   _cursor.delta.x = 0;
02755   _cursor.delta.y = 0;
02756 }
02757 
02761 static void CheckSoftLimit()
02762 {
02763   if (_settings_client.gui.window_soft_limit == 0) return;
02764 
02765   for (;;) {
02766     uint deletable_count = 0;
02767     Window *w, *last_deletable = NULL;
02768     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02769       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
02770 
02771       last_deletable = w;
02772       deletable_count++;
02773     }
02774 
02775     /* We've not reached the soft limit yet. */
02776     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02777 
02778     assert(last_deletable != NULL);
02779     delete last_deletable;
02780   }
02781 }
02782 
02786 void InputLoop()
02787 {
02788   /* World generation is multithreaded and messes with companies.
02789    * But there is no company related window open anyway, so _current_company is not used. */
02790   assert(HasModalProgress() || IsLocalCompany());
02791 
02792   CheckSoftLimit();
02793   HandleKeyScrolling();
02794 
02795   /* Do the actual free of the deleted windows. */
02796   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02797     Window *w = v;
02798     v = v->z_back;
02799 
02800     if (w->window_class != WC_INVALID) continue;
02801 
02802     RemoveWindowFromZOrdering(w);
02803     free(w);
02804   }
02805 
02806   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02807   DecreaseWindowCounters();
02808 
02809   if (_input_events_this_tick != 0) {
02810     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02811     _input_events_this_tick = 0;
02812     /* there were some inputs this tick, don't scroll ??? */
02813     return;
02814   }
02815 
02816   /* HandleMouseEvents was already called for this tick */
02817   HandleMouseEvents();
02818   HandleAutoscroll();
02819 }
02820 
02824 void UpdateWindows()
02825 {
02826   Window *w;
02827 
02828   static int highlight_timer = 1;
02829   if (--highlight_timer == 0) {
02830     highlight_timer = 15;
02831     _window_highlight_colour = !_window_highlight_colour;
02832   }
02833 
02834   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02835     w->ProcessScheduledInvalidations();
02836     w->ProcessHighlightedInvalidations();
02837   }
02838 
02839   static int we4_timer = 0;
02840   int t = we4_timer + 1;
02841 
02842   if (t >= 100) {
02843     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02844       w->OnHundredthTick();
02845     }
02846     t = 0;
02847   }
02848   we4_timer = t;
02849 
02850   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02851     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
02852       CLRBITS(w->flags, WF_WHITE_BORDER);
02853       w->SetDirty();
02854     }
02855   }
02856 
02857   DrawDirtyBlocks();
02858 
02859   FOR_ALL_WINDOWS_FROM_BACK(w) {
02860     /* Update viewport only if window is not shaded. */
02861     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02862   }
02863   NetworkDrawChatMessage();
02864   /* Redraw mouse cursor in case it was hidden */
02865   DrawMouseCursor();
02866 }
02867 
02873 void SetWindowDirty(WindowClass cls, WindowNumber number)
02874 {
02875   const Window *w;
02876   FOR_ALL_WINDOWS_FROM_BACK(w) {
02877     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02878   }
02879 }
02880 
02887 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02888 {
02889   const Window *w;
02890   FOR_ALL_WINDOWS_FROM_BACK(w) {
02891     if (w->window_class == cls && w->window_number == number) {
02892       w->SetWidgetDirty(widget_index);
02893     }
02894   }
02895 }
02896 
02901 void SetWindowClassesDirty(WindowClass cls)
02902 {
02903   Window *w;
02904   FOR_ALL_WINDOWS_FROM_BACK(w) {
02905     if (w->window_class == cls) w->SetDirty();
02906   }
02907 }
02908 
02914 void Window::InvalidateData(int data, bool gui_scope)
02915 {
02916   this->SetDirty();
02917   if (!gui_scope) {
02918     /* Schedule GUI-scope invalidation for next redraw. */
02919     *this->scheduled_invalidation_data.Append() = data;
02920   }
02921   this->OnInvalidateData(data, gui_scope);
02922 }
02923 
02927 void Window::ProcessScheduledInvalidations()
02928 {
02929   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
02930     this->OnInvalidateData(*data, true);
02931   }
02932   this->scheduled_invalidation_data.Clear();
02933 }
02934 
02938 void Window::ProcessHighlightedInvalidations()
02939 {
02940   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
02941 
02942   for (uint i = 0; i < this->nested_array_size; i++) {
02943     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
02944   }
02945 }
02946 
02973 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
02974 {
02975   Window *w;
02976   FOR_ALL_WINDOWS_FROM_BACK(w) {
02977     if (w->window_class == cls && w->window_number == number) {
02978       w->InvalidateData(data, gui_scope);
02979     }
02980   }
02981 }
02982 
02991 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
02992 {
02993   Window *w;
02994 
02995   FOR_ALL_WINDOWS_FROM_BACK(w) {
02996     if (w->window_class == cls) {
02997       w->InvalidateData(data, gui_scope);
02998     }
02999   }
03000 }
03001 
03005 void CallWindowTickEvent()
03006 {
03007   Window *w;
03008   FOR_ALL_WINDOWS_FROM_FRONT(w) {
03009     w->OnTick();
03010   }
03011 }
03012 
03019 void DeleteNonVitalWindows()
03020 {
03021   Window *w;
03022 
03023 restart_search:
03024   /* When we find the window to delete, we need to restart the search
03025    * as deleting this window could cascade in deleting (many) others
03026    * anywhere in the z-array */
03027   FOR_ALL_WINDOWS_FROM_BACK(w) {
03028     if (w->window_class != WC_MAIN_WINDOW &&
03029         w->window_class != WC_SELECT_GAME &&
03030         w->window_class != WC_MAIN_TOOLBAR &&
03031         w->window_class != WC_STATUS_BAR &&
03032         w->window_class != WC_TOOLTIPS &&
03033         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
03034 
03035       delete w;
03036       goto restart_search;
03037     }
03038   }
03039 }
03040 
03048 void DeleteAllNonVitalWindows()
03049 {
03050   Window *w;
03051 
03052   /* Delete every window except for stickied ones, then sticky ones as well */
03053   DeleteNonVitalWindows();
03054 
03055 restart_search:
03056   /* When we find the window to delete, we need to restart the search
03057    * as deleting this window could cascade in deleting (many) others
03058    * anywhere in the z-array */
03059   FOR_ALL_WINDOWS_FROM_BACK(w) {
03060     if (w->flags & WF_STICKY) {
03061       delete w;
03062       goto restart_search;
03063     }
03064   }
03065 }
03066 
03071 void DeleteConstructionWindows()
03072 {
03073   Window *w;
03074 
03075 restart_search:
03076   /* When we find the window to delete, we need to restart the search
03077    * as deleting this window could cascade in deleting (many) others
03078    * anywhere in the z-array */
03079   FOR_ALL_WINDOWS_FROM_BACK(w) {
03080     if (w->window_desc->flags & WDF_CONSTRUCTION) {
03081       delete w;
03082       goto restart_search;
03083     }
03084   }
03085 
03086   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
03087 }
03088 
03090 void HideVitalWindows()
03091 {
03092   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
03093   DeleteWindowById(WC_STATUS_BAR, 0);
03094 }
03095 
03097 void ReInitAllWindows()
03098 {
03099   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
03100   NWidgetScrollbar::InvalidateDimensionCache();
03101 
03102   Window *w;
03103   FOR_ALL_WINDOWS_FROM_BACK(w) {
03104     w->ReInit();
03105   }
03106 #ifdef ENABLE_NETWORK
03107   void NetworkReInitChatBoxSize();
03108   NetworkReInitChatBoxSize();
03109 #endif
03110 
03111   /* Make sure essential parts of all windows are visible */
03112   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
03113   MarkWholeScreenDirty();
03114 }
03115 
03123 static int PositionWindow(Window *w, WindowClass clss, int setting)
03124 {
03125   if (w == NULL || w->window_class != clss) {
03126     w = FindWindowById(clss, 0);
03127   }
03128   if (w == NULL) return 0;
03129 
03130   int old_left = w->left;
03131   switch (setting) {
03132     case 1:  w->left = (_screen.width - w->width) / 2; break;
03133     case 2:  w->left = _screen.width - w->width; break;
03134     default: w->left = 0; break;
03135   }
03136   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
03137   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
03138   return w->left;
03139 }
03140 
03146 int PositionMainToolbar(Window *w)
03147 {
03148   DEBUG(misc, 5, "Repositioning Main Toolbar...");
03149   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
03150 }
03151 
03157 int PositionStatusbar(Window *w)
03158 {
03159   DEBUG(misc, 5, "Repositioning statusbar...");
03160   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
03161 }
03162 
03168 int PositionNewsMessage(Window *w)
03169 {
03170   DEBUG(misc, 5, "Repositioning news message...");
03171   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
03172 }
03173 
03179 int PositionNetworkChatWindow(Window *w)
03180 {
03181   DEBUG(misc, 5, "Repositioning network chat window...");
03182   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
03183 }
03184 
03185 
03191 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
03192 {
03193   Window *w;
03194   FOR_ALL_WINDOWS_FROM_BACK(w) {
03195     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
03196       w->viewport->follow_vehicle = to_index;
03197       w->SetDirty();
03198     }
03199   }
03200 }
03201 
03202 
03208 void RelocateAllWindows(int neww, int newh)
03209 {
03210   Window *w;
03211 
03212   FOR_ALL_WINDOWS_FROM_BACK(w) {
03213     int left, top;
03214 
03215     if (w->window_class == WC_MAIN_WINDOW) {
03216       ViewPort *vp = w->viewport;
03217       vp->width = w->width = neww;
03218       vp->height = w->height = newh;
03219       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
03220       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
03221       continue; // don't modify top,left
03222     }
03223 
03224     /* XXX - this probably needs something more sane. For example specifying
03225      * in a 'backup'-desc that the window should always be centered. */
03226     switch (w->window_class) {
03227       case WC_BOOTSTRAP:
03228         ResizeWindow(w, neww, newh);
03229         continue;
03230 
03231       case WC_MAIN_TOOLBAR:
03232         ResizeWindow(w, min(neww, w->window_desc->default_width) - w->width, 0, false);
03233 
03234         top = w->top;
03235         left = PositionMainToolbar(w); // changes toolbar orientation
03236         break;
03237 
03238       case WC_NEWS_WINDOW:
03239         top = newh - w->height;
03240         left = PositionNewsMessage(w);
03241         break;
03242 
03243       case WC_STATUS_BAR:
03244         ResizeWindow(w, min(neww, w->window_desc->default_width) - w->width, 0, false);
03245 
03246         top = newh - w->height;
03247         left = PositionStatusbar(w);
03248         break;
03249 
03250       case WC_SEND_NETWORK_MSG:
03251         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
03252         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
03253         left = PositionNetworkChatWindow(w);
03254         break;
03255 
03256       case WC_CONSOLE:
03257         IConsoleResize(w);
03258         continue;
03259 
03260       default: {
03261         if (w->flags & WF_CENTERED) {
03262           top = (newh - w->height) >> 1;
03263           left = (neww - w->width) >> 1;
03264           break;
03265         }
03266 
03267         left = w->left;
03268         if (left + (w->width >> 1) >= neww) left = neww - w->width;
03269         if (left < 0) left = 0;
03270 
03271         top = w->top;
03272         if (top + (w->height >> 1) >= newh) top = newh - w->height;
03273         break;
03274       }
03275     }
03276 
03277     EnsureVisibleCaption(w, left, top);
03278   }
03279 }
03280 
03286 PickerWindowBase::~PickerWindowBase()
03287 {
03288   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
03289   ResetObjectToPlace();
03290 }