window.cpp

Go to the documentation of this file.
00001 /* $Id: window.cpp 26392 2014-03-05 21:21:55Z alberth $ */
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 #include "video/video_driver.hpp"
00039 
00041 enum ViewportAutoscrolling {
00042   VA_DISABLED,                  
00043   VA_MAIN_VIEWPORT_FULLSCREEN,  
00044   VA_MAIN_VIEWPORT,             
00045   VA_EVERY_VIEWPORT,            
00046 };
00047 
00048 static Point _drag_delta; 
00049 static Window *_mouseover_last_w = NULL; 
00050 static Window *_last_scroll_window = NULL; 
00051 
00053 Window *_z_front_window = NULL;
00055 Window *_z_back_window  = NULL;
00056 
00058 bool _window_highlight_colour = false;
00059 
00060 /*
00061  * Window that currently has focus. - The main purpose is to generate
00062  * #FocusLost events, not to give next window in z-order focus when a
00063  * window is closed.
00064  */
00065 Window *_focused_window;
00066 
00067 Point _cursorpos_drag_start;
00068 
00069 int _scrollbar_start_pos;
00070 int _scrollbar_size;
00071 byte _scroller_click_timeout = 0;
00072 
00073 bool _scrolling_viewport;  
00074 bool _mouse_hovering;      
00075 
00076 SpecialMouseMode _special_mouse_mode; 
00077 
00082 static SmallVector<WindowDesc*, 16> *_window_descs = NULL;
00083 
00085 char *_windows_file;
00086 
00088 WindowDesc::WindowDesc(WindowPosition def_pos, const char *ini_key, int16 def_width, int16 def_height,
00089       WindowClass window_class, WindowClass parent_class, uint32 flags,
00090       const NWidgetPart *nwid_parts, int16 nwid_length, HotkeyList *hotkeys) :
00091   default_pos(def_pos),
00092   default_width(def_width),
00093   default_height(def_height),
00094   cls(window_class),
00095   parent_cls(parent_class),
00096   ini_key(ini_key),
00097   flags(flags),
00098   nwid_parts(nwid_parts),
00099   nwid_length(nwid_length),
00100   hotkeys(hotkeys),
00101   pref_sticky(false),
00102   pref_width(0),
00103   pref_height(0)
00104 {
00105   if (_window_descs == NULL) _window_descs = new SmallVector<WindowDesc*, 16>();
00106   *_window_descs->Append() = this;
00107 }
00108 
00109 WindowDesc::~WindowDesc()
00110 {
00111   _window_descs->Erase(_window_descs->Find(this));
00112 }
00113 
00117 void WindowDesc::LoadFromConfig()
00118 {
00119   IniFile *ini = new IniFile();
00120   ini->LoadFromDisk(_windows_file, BASE_DIR);
00121   for (WindowDesc **it = _window_descs->Begin(); it != _window_descs->End(); ++it) {
00122     if ((*it)->ini_key == NULL) continue;
00123     IniLoadWindowSettings(ini, (*it)->ini_key, *it);
00124   }
00125   delete ini;
00126 }
00127 
00131 static int CDECL DescSorter(WindowDesc * const *a, WindowDesc * const *b)
00132 {
00133   if ((*a)->ini_key != NULL && (*b)->ini_key != NULL) return strcmp((*a)->ini_key, (*b)->ini_key);
00134   return ((*b)->ini_key != NULL ? 1 : 0) - ((*a)->ini_key != NULL ? 1 : 0);
00135 }
00136 
00140 void WindowDesc::SaveToConfig()
00141 {
00142   /* Sort the stuff to get a nice ini file on first write */
00143   QSortT(_window_descs->Begin(), _window_descs->Length(), DescSorter);
00144 
00145   IniFile *ini = new IniFile();
00146   ini->LoadFromDisk(_windows_file, BASE_DIR);
00147   for (WindowDesc **it = _window_descs->Begin(); it != _window_descs->End(); ++it) {
00148     if ((*it)->ini_key == NULL) continue;
00149     IniSaveWindowSettings(ini, (*it)->ini_key, *it);
00150   }
00151   ini->SaveToDisk(_windows_file);
00152   delete ini;
00153 }
00154 
00158 void Window::ApplyDefaults()
00159 {
00160   if (this->nested_root != NULL && this->nested_root->GetWidgetOfType(WWT_STICKYBOX) != NULL) {
00161     if (this->window_desc->pref_sticky) this->flags |= WF_STICKY;
00162   } else {
00163     /* There is no stickybox; clear the preference in case someone tried to be funny */
00164     this->window_desc->pref_sticky = false;
00165   }
00166 }
00167 
00177 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00178 {
00179   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00180   if (line_height < 0) line_height = wid->resize_y;
00181   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00182   return (clickpos - (int)wid->pos_y - padding) / line_height;
00183 }
00184 
00188 void Window::DisableAllWidgetHighlight()
00189 {
00190   for (uint i = 0; i < this->nested_array_size; i++) {
00191     NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00192     if (nwid == NULL) continue;
00193 
00194     if (nwid->IsHighlighted()) {
00195       nwid->SetHighlighted(TC_INVALID);
00196       this->SetWidgetDirty(i);
00197     }
00198   }
00199 
00200   CLRBITS(this->flags, WF_HIGHLIGHTED);
00201 }
00202 
00208 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
00209 {
00210   assert(widget_index < this->nested_array_size);
00211 
00212   NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00213   if (nwid == NULL) return;
00214 
00215   nwid->SetHighlighted(highlighted_colour);
00216   this->SetWidgetDirty(widget_index);
00217 
00218   if (highlighted_colour != TC_INVALID) {
00219     /* If we set a highlight, the window has a highlight */
00220     this->flags |= WF_HIGHLIGHTED;
00221   } else {
00222     /* If we disable a highlight, check all widgets if anyone still has a highlight */
00223     bool valid = false;
00224     for (uint i = 0; i < this->nested_array_size; i++) {
00225       NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00226       if (nwid == NULL) continue;
00227       if (!nwid->IsHighlighted()) continue;
00228 
00229       valid = true;
00230     }
00231     /* If nobody has a highlight, disable the flag on the window */
00232     if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
00233   }
00234 }
00235 
00241 bool Window::IsWidgetHighlighted(byte widget_index) const
00242 {
00243   assert(widget_index < this->nested_array_size);
00244 
00245   const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00246   if (nwid == NULL) return false;
00247 
00248   return nwid->IsHighlighted();
00249 }
00250 
00258 void Window::OnDropdownClose(Point pt, int widget, int index, bool instant_close)
00259 {
00260   if (widget < 0) return;
00261 
00262   if (instant_close) {
00263     /* Send event for selected option if we're still
00264      * on the parent button of the dropdown (behaviour of the dropdowns in the main toolbar). */
00265     if (GetWidgetFromPos(this, pt.x, pt.y) == widget) {
00266       this->OnDropdownSelect(widget, index);
00267     }
00268   }
00269 
00270   /* Raise the dropdown button */
00271   NWidgetCore *nwi2 = this->GetWidget<NWidgetCore>(widget);
00272   if ((nwi2->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
00273     nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
00274   } else {
00275     this->RaiseWidget(widget);
00276   }
00277   this->SetWidgetDirty(widget);
00278 }
00279 
00285 const Scrollbar *Window::GetScrollbar(uint widnum) const
00286 {
00287   return this->GetWidget<NWidgetScrollbar>(widnum);
00288 }
00289 
00295 Scrollbar *Window::GetScrollbar(uint widnum)
00296 {
00297   return this->GetWidget<NWidgetScrollbar>(widnum);
00298 }
00299 
00305 const QueryString *Window::GetQueryString(uint widnum) const
00306 {
00307   const SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00308   return query != this->querystrings.End() ? query->second : NULL;
00309 }
00310 
00316 QueryString *Window::GetQueryString(uint widnum)
00317 {
00318   SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00319   return query != this->querystrings.End() ? query->second : NULL;
00320 }
00321 
00326 /* virtual */ const char *Window::GetFocusedText() const
00327 {
00328   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00329     return this->GetQueryString(this->nested_focus->index)->GetText();
00330   }
00331 
00332   return NULL;
00333 }
00334 
00339 /* virtual */ const char *Window::GetCaret() const
00340 {
00341   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00342     return this->GetQueryString(this->nested_focus->index)->GetCaret();
00343   }
00344 
00345   return NULL;
00346 }
00347 
00353 /* virtual */ const char *Window::GetMarkedText(size_t *length) const
00354 {
00355   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00356     return this->GetQueryString(this->nested_focus->index)->GetMarkedText(length);
00357   }
00358 
00359   return NULL;
00360 }
00361 
00366 /* virtual */ Point Window::GetCaretPosition() const
00367 {
00368   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00369     return this->GetQueryString(this->nested_focus->index)->GetCaretPosition(this, this->nested_focus->index);
00370   }
00371 
00372   Point pt = {0, 0};
00373   return pt;
00374 }
00375 
00382 /* virtual */ Rect Window::GetTextBoundingRect(const char *from, const char *to) const
00383 {
00384   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00385     return this->GetQueryString(this->nested_focus->index)->GetBoundingRect(this, this->nested_focus->index, from, to);
00386   }
00387 
00388   Rect r = {0, 0, 0, 0};
00389   return r;
00390 }
00391 
00397 /* virtual */ const char *Window::GetTextCharacterAtPosition(const Point &pt) const
00398 {
00399   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
00400     return this->GetQueryString(this->nested_focus->index)->GetCharAtPosition(this, this->nested_focus->index, pt);
00401   }
00402 
00403   return NULL;
00404 }
00405 
00410 void SetFocusedWindow(Window *w)
00411 {
00412   if (_focused_window == w) return;
00413 
00414   /* Invalidate focused widget */
00415   if (_focused_window != NULL) {
00416     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00417   }
00418 
00419   /* Remember which window was previously focused */
00420   Window *old_focused = _focused_window;
00421   _focused_window = w;
00422 
00423   /* So we can inform it that it lost focus */
00424   if (old_focused != NULL) old_focused->OnFocusLost();
00425   if (_focused_window != NULL) _focused_window->OnFocus();
00426 }
00427 
00433 bool EditBoxInGlobalFocus()
00434 {
00435   if (_focused_window == NULL) return false;
00436 
00437   /* The console does not have an edit box so a special case is needed. */
00438   if (_focused_window->window_class == WC_CONSOLE) return true;
00439 
00440   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00441 }
00442 
00446 void Window::UnfocusFocusedWidget()
00447 {
00448   if (this->nested_focus != NULL) {
00449     if (this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
00450 
00451     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00452     this->nested_focus->SetDirty(this);
00453     this->nested_focus = NULL;
00454   }
00455 }
00456 
00462 bool Window::SetFocusedWidget(int widget_index)
00463 {
00464   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00465   if ((uint)widget_index >= this->nested_array_size) return false;
00466 
00467   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00468   if (this->nested_focus != NULL) {
00469     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00470 
00471     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00472     this->nested_focus->SetDirty(this);
00473     if (this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
00474   }
00475   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00476   return true;
00477 }
00478 
00482 void Window::OnFocusLost()
00483 {
00484   if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
00485 }
00486 
00494 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00495 {
00496   va_list wdg_list;
00497 
00498   va_start(wdg_list, widgets);
00499 
00500   while (widgets != WIDGET_LIST_END) {
00501     SetWidgetDisabledState(widgets, disab_stat);
00502     widgets = va_arg(wdg_list, int);
00503   }
00504 
00505   va_end(wdg_list);
00506 }
00507 
00513 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00514 {
00515   va_list wdg_list;
00516 
00517   va_start(wdg_list, widgets);
00518 
00519   while (widgets != WIDGET_LIST_END) {
00520     SetWidgetLoweredState(widgets, lowered_stat);
00521     widgets = va_arg(wdg_list, int);
00522   }
00523 
00524   va_end(wdg_list);
00525 }
00526 
00531 void Window::RaiseButtons(bool autoraise)
00532 {
00533   for (uint i = 0; i < this->nested_array_size; i++) {
00534     if (this->nested_array[i] == NULL) continue;
00535     WidgetType type = this->nested_array[i]->type;
00536     if (((type & ~WWB_PUSHBUTTON) < WWT_LAST || type == NWID_PUSHBUTTON_DROPDOWN) &&
00537         (!autoraise || (type & WWB_PUSHBUTTON) || type == WWT_EDITBOX) && this->IsWidgetLowered(i)) {
00538       this->RaiseWidget(i);
00539       this->SetWidgetDirty(i);
00540     }
00541   }
00542 
00543   /* Special widgets without widget index */
00544   NWidgetCore *wid = this->nested_root != NULL ? (NWidgetCore*)this->nested_root->GetWidgetOfType(WWT_DEFSIZEBOX) : NULL;
00545   if (wid != NULL) {
00546     wid->SetLowered(false);
00547     wid->SetDirty(this);
00548   }
00549 }
00550 
00555 void Window::SetWidgetDirty(byte widget_index) const
00556 {
00557   /* Sometimes this function is called before the window is even fully initialized */
00558   if (this->nested_array == NULL) return;
00559 
00560   this->nested_array[widget_index]->SetDirty(this);
00561 }
00562 
00568 EventState Window::OnHotkey(int hotkey)
00569 {
00570   if (hotkey < 0) return ES_NOT_HANDLED;
00571 
00572   NWidgetCore *nw = this->GetWidget<NWidgetCore>(hotkey);
00573   if (nw == NULL || nw->IsDisabled()) return ES_NOT_HANDLED;
00574 
00575   if (nw->type == WWT_EDITBOX) {
00576     if (this->IsShaded()) return ES_NOT_HANDLED;
00577 
00578     /* Focus editbox */
00579     this->SetFocusedWidget(hotkey);
00580     SetFocusedWindow(this);
00581   } else {
00582     /* Click button */
00583     this->OnClick(Point(), hotkey, 1);
00584   }
00585   return ES_HANDLED;
00586 }
00587 
00593 void Window::HandleButtonClick(byte widget)
00594 {
00595   this->LowerWidget(widget);
00596   this->SetTimeout();
00597   this->SetWidgetDirty(widget);
00598 }
00599 
00600 static void StartWindowDrag(Window *w);
00601 static void StartWindowSizing(Window *w, bool to_left);
00602 
00610 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00611 {
00612   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00613   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00614 
00615   bool focused_widget_changed = false;
00616   /* If clicked on a window that previously did dot have focus */
00617   if (_focused_window != w &&                 // We already have focus, right?
00618       (w->window_desc->flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00619       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00620     focused_widget_changed = true;
00621     SetFocusedWindow(w);
00622   }
00623 
00624   if (nw == NULL) return; // exit if clicked outside of widgets
00625 
00626   /* don't allow any interaction if the button has been disabled */
00627   if (nw->IsDisabled()) return;
00628 
00629   int widget_index = nw->index; 
00630 
00631   /* Clicked on a widget that is not disabled.
00632    * So unless the clicked widget is the caption bar, change focus to this widget.
00633    * Exception: In the OSK we always want the editbox to stay focussed. */
00634   if (widget_type != WWT_CAPTION && w->window_class != WC_OSK) {
00635     /* focused_widget_changed is 'now' only true if the window this widget
00636      * is in gained focus. In that case it must remain true, also if the
00637      * local widget focus did not change. As such it's the logical-or of
00638      * both changed states.
00639      *
00640      * If this is not preserved, then the OSK window would be opened when
00641      * a user has the edit box focused and then click on another window and
00642      * then back again on the edit box (to type some text).
00643      */
00644     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00645   }
00646 
00647   /* Close any child drop down menus. If the button pressed was the drop down
00648    * list's own button, then we should not process the click any further. */
00649   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00650 
00651   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00652 
00653   Point pt = { x, y };
00654 
00655   switch (widget_type) {
00656     case NWID_VSCROLLBAR:
00657     case NWID_HSCROLLBAR:
00658       ScrollbarClickHandler(w, nw, x, y);
00659       break;
00660 
00661     case WWT_EDITBOX: {
00662       QueryString *query = w->GetQueryString(widget_index);
00663       if (query != NULL) query->ClickEditBox(w, pt, widget_index, click_count, focused_widget_changed);
00664       break;
00665     }
00666 
00667     case WWT_CLOSEBOX: // 'X'
00668       delete w;
00669       return;
00670 
00671     case WWT_CAPTION: // 'Title bar'
00672       StartWindowDrag(w);
00673       return;
00674 
00675     case WWT_RESIZEBOX:
00676       /* When the resize widget is on the left size of the window
00677        * we assume that that button is used to resize to the left. */
00678       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00679       nw->SetDirty(w);
00680       return;
00681 
00682     case WWT_DEFSIZEBOX: {
00683       if (_ctrl_pressed) {
00684         w->window_desc->pref_width = w->width;
00685         w->window_desc->pref_height = w->height;
00686       } else {
00687         int16 def_width = max<int16>(min(w->window_desc->GetDefaultWidth(), _screen.width), w->nested_root->smallest_x);
00688         int16 def_height = max<int16>(min(w->window_desc->GetDefaultHeight(), _screen.height - 50), w->nested_root->smallest_y);
00689 
00690         int dx = (w->resize.step_width  == 0) ? 0 : def_width  - w->width;
00691         int dy = (w->resize.step_height == 0) ? 0 : def_height - w->height;
00692         /* dx and dy has to go by step.. calculate it.
00693          * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00694         if (w->resize.step_width  > 1) dx -= dx % (int)w->resize.step_width;
00695         if (w->resize.step_height > 1) dy -= dy % (int)w->resize.step_height;
00696         ResizeWindow(w, dx, dy, false);
00697       }
00698 
00699       nw->SetLowered(true);
00700       nw->SetDirty(w);
00701       w->SetTimeout();
00702       break;
00703     }
00704 
00705     case WWT_DEBUGBOX:
00706       w->ShowNewGRFInspectWindow();
00707       break;
00708 
00709     case WWT_SHADEBOX:
00710       nw->SetDirty(w);
00711       w->SetShaded(!w->IsShaded());
00712       return;
00713 
00714     case WWT_STICKYBOX:
00715       w->flags ^= WF_STICKY;
00716       nw->SetDirty(w);
00717       if (_ctrl_pressed) w->window_desc->pref_sticky = (w->flags & WF_STICKY) != 0;
00718       return;
00719 
00720     default:
00721       break;
00722   }
00723 
00724   /* Widget has no index, so the window is not interested in it. */
00725   if (widget_index < 0) return;
00726 
00727   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
00728   if (w->IsWidgetHighlighted(widget_index)) {
00729     w->SetWidgetHighlight(widget_index, TC_INVALID);
00730     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
00731   }
00732 
00733   w->OnClick(pt, widget_index, click_count);
00734 }
00735 
00742 static void DispatchRightClickEvent(Window *w, int x, int y)
00743 {
00744   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00745   if (wid == NULL) return;
00746 
00747   /* No widget to handle, or the window is not interested in it. */
00748   if (wid->index >= 0) {
00749     Point pt = { x, y };
00750     if (w->OnRightClick(pt, wid->index)) return;
00751   }
00752 
00753   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00754 }
00755 
00762 static void DispatchHoverEvent(Window *w, int x, int y)
00763 {
00764   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00765 
00766   /* No widget to handle */
00767   if (wid == NULL) return;
00768 
00769   /* Show the tooltip if there is any */
00770   if (wid->tool_tip != 0) {
00771     GuiShowTooltips(w, wid->tool_tip);
00772     return;
00773   }
00774 
00775   /* Widget has no index, so the window is not interested in it. */
00776   if (wid->index < 0) return;
00777 
00778   Point pt = { x, y };
00779   w->OnHover(pt, wid->index);
00780 }
00781 
00789 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
00790 {
00791   if (nwid == NULL) return;
00792 
00793   /* Using wheel on caption/shade-box shades or unshades the window. */
00794   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00795     w->SetShaded(wheel < 0);
00796     return;
00797   }
00798 
00799   /* Wheeling a vertical scrollbar. */
00800   if (nwid->type == NWID_VSCROLLBAR) {
00801     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
00802     if (sb->GetCount() > sb->GetCapacity()) {
00803       sb->UpdatePosition(wheel);
00804       w->SetDirty();
00805     }
00806     return;
00807   }
00808 
00809   /* Scroll the widget attached to the scrollbar. */
00810   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00811   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00812     sb->UpdatePosition(wheel);
00813     w->SetDirty();
00814   }
00815 }
00816 
00822 static bool MayBeShown(const Window *w)
00823 {
00824   /* If we're not modal, everything is okay. */
00825   if (!HasModalProgress()) return true;
00826 
00827   switch (w->window_class) {
00828     case WC_MAIN_WINDOW:    
00829     case WC_MODAL_PROGRESS: 
00830     case WC_CONFIRM_POPUP_QUERY: 
00831       return true;
00832 
00833     default:
00834       return false;
00835   }
00836 }
00837 
00850 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00851 {
00852   const Window *v;
00853   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00854     if (MayBeShown(v) &&
00855         right > v->left &&
00856         bottom > v->top &&
00857         left < v->left + v->width &&
00858         top < v->top + v->height) {
00859       /* v and rectangle intersect with each other */
00860       int x;
00861 
00862       if (left < (x = v->left)) {
00863         DrawOverlappedWindow(w, left, top, x, bottom);
00864         DrawOverlappedWindow(w, x, top, right, bottom);
00865         return;
00866       }
00867 
00868       if (right > (x = v->left + v->width)) {
00869         DrawOverlappedWindow(w, left, top, x, bottom);
00870         DrawOverlappedWindow(w, x, top, right, bottom);
00871         return;
00872       }
00873 
00874       if (top < (x = v->top)) {
00875         DrawOverlappedWindow(w, left, top, right, x);
00876         DrawOverlappedWindow(w, left, x, right, bottom);
00877         return;
00878       }
00879 
00880       if (bottom > (x = v->top + v->height)) {
00881         DrawOverlappedWindow(w, left, top, right, x);
00882         DrawOverlappedWindow(w, left, x, right, bottom);
00883         return;
00884       }
00885 
00886       return;
00887     }
00888   }
00889 
00890   /* Setup blitter, and dispatch a repaint event to window *wz */
00891   DrawPixelInfo *dp = _cur_dpi;
00892   dp->width = right - left;
00893   dp->height = bottom - top;
00894   dp->left = left - w->left;
00895   dp->top = top - w->top;
00896   dp->pitch = _screen.pitch;
00897   dp->dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00898   dp->zoom = ZOOM_LVL_NORMAL;
00899   w->OnPaint();
00900 }
00901 
00910 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00911 {
00912   Window *w;
00913   DrawPixelInfo bk;
00914   _cur_dpi = &bk;
00915 
00916   FOR_ALL_WINDOWS_FROM_BACK(w) {
00917     if (MayBeShown(w) &&
00918         right > w->left &&
00919         bottom > w->top &&
00920         left < w->left + w->width &&
00921         top < w->top + w->height) {
00922       /* Window w intersects with the rectangle => needs repaint */
00923       DrawOverlappedWindow(w, left, top, right, bottom);
00924     }
00925   }
00926 }
00927 
00932 void Window::SetDirty() const
00933 {
00934   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00935 }
00936 
00943 void Window::ReInit(int rx, int ry)
00944 {
00945   this->SetDirty(); // Mark whole current window as dirty.
00946 
00947   /* Save current size. */
00948   int window_width  = this->width;
00949   int window_height = this->height;
00950 
00951   this->OnInit();
00952   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00953   this->nested_root->SetupSmallestSize(this, false);
00954   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00955   this->width  = this->nested_root->smallest_x;
00956   this->height = this->nested_root->smallest_y;
00957   this->resize.step_width  = this->nested_root->resize_x;
00958   this->resize.step_height = this->nested_root->resize_y;
00959 
00960   /* Resize as close to the original size + requested resize as possible. */
00961   window_width  = max(window_width  + rx, this->width);
00962   window_height = max(window_height + ry, this->height);
00963   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00964   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00965   /* dx and dy has to go by step.. calculate it.
00966    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00967   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00968   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00969 
00970   ResizeWindow(this, dx, dy);
00971   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00972 }
00973 
00979 void Window::SetShaded(bool make_shaded)
00980 {
00981   if (this->shade_select == NULL) return;
00982 
00983   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00984   if (this->shade_select->shown_plane != desired) {
00985     if (make_shaded) {
00986       if (this->nested_focus != NULL) this->UnfocusFocusedWidget();
00987       this->unshaded_size.width  = this->width;
00988       this->unshaded_size.height = this->height;
00989       this->shade_select->SetDisplayedPlane(desired);
00990       this->ReInit(0, -this->height);
00991     } else {
00992       this->shade_select->SetDisplayedPlane(desired);
00993       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00994       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00995       this->ReInit(dx, dy);
00996     }
00997   }
00998 }
00999 
01006 static Window *FindChildWindow(const Window *w, WindowClass wc)
01007 {
01008   Window *v;
01009   FOR_ALL_WINDOWS_FROM_BACK(v) {
01010     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
01011   }
01012 
01013   return NULL;
01014 }
01015 
01020 void Window::DeleteChildWindows(WindowClass wc) const
01021 {
01022   Window *child = FindChildWindow(this, wc);
01023   while (child != NULL) {
01024     delete child;
01025     child = FindChildWindow(this, wc);
01026   }
01027 }
01028 
01032 Window::~Window()
01033 {
01034   if (_thd.window_class == this->window_class &&
01035       _thd.window_number == this->window_number) {
01036     ResetObjectToPlace();
01037   }
01038 
01039   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
01040   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
01041 
01042   /* We can't scroll the window when it's closed. */
01043   if (_last_scroll_window == this) _last_scroll_window = NULL;
01044 
01045   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
01046   if (_focused_window == this) {
01047     this->OnFocusLost();
01048     _focused_window = NULL;
01049   }
01050 
01051   this->DeleteChildWindows();
01052 
01053   if (this->viewport != NULL) DeleteWindowViewport(this);
01054 
01055   this->SetDirty();
01056 
01057   free(this->nested_array); // Contents is released through deletion of #nested_root.
01058   delete this->nested_root;
01059 
01060   this->window_class = WC_INVALID;
01061 }
01062 
01069 Window *FindWindowById(WindowClass cls, WindowNumber number)
01070 {
01071   Window *w;
01072   FOR_ALL_WINDOWS_FROM_BACK(w) {
01073     if (w->window_class == cls && w->window_number == number) return w;
01074   }
01075 
01076   return NULL;
01077 }
01078 
01085 Window *FindWindowByClass(WindowClass cls)
01086 {
01087   Window *w;
01088   FOR_ALL_WINDOWS_FROM_BACK(w) {
01089     if (w->window_class == cls) return w;
01090   }
01091 
01092   return NULL;
01093 }
01094 
01101 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
01102 {
01103   Window *w = FindWindowById(cls, number);
01104   if (force || w == NULL ||
01105       (w->flags & WF_STICKY) == 0) {
01106     delete w;
01107   }
01108 }
01109 
01114 void DeleteWindowByClass(WindowClass cls)
01115 {
01116   Window *w;
01117 
01118 restart_search:
01119   /* When we find the window to delete, we need to restart the search
01120    * as deleting this window could cascade in deleting (many) others
01121    * anywhere in the z-array */
01122   FOR_ALL_WINDOWS_FROM_BACK(w) {
01123     if (w->window_class == cls) {
01124       delete w;
01125       goto restart_search;
01126     }
01127   }
01128 }
01129 
01136 void DeleteCompanyWindows(CompanyID id)
01137 {
01138   Window *w;
01139 
01140 restart_search:
01141   /* When we find the window to delete, we need to restart the search
01142    * as deleting this window could cascade in deleting (many) others
01143    * anywhere in the z-array */
01144   FOR_ALL_WINDOWS_FROM_BACK(w) {
01145     if (w->owner == id) {
01146       delete w;
01147       goto restart_search;
01148     }
01149   }
01150 
01151   /* Also delete the company specific windows that don't have a company-colour. */
01152   DeleteWindowById(WC_BUY_COMPANY, id);
01153 }
01154 
01162 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
01163 {
01164   Window *w;
01165   FOR_ALL_WINDOWS_FROM_BACK(w) {
01166     if (w->owner != old_owner) continue;
01167 
01168     switch (w->window_class) {
01169       case WC_COMPANY_COLOUR:
01170       case WC_FINANCES:
01171       case WC_STATION_LIST:
01172       case WC_TRAINS_LIST:
01173       case WC_ROADVEH_LIST:
01174       case WC_SHIPS_LIST:
01175       case WC_AIRCRAFT_LIST:
01176       case WC_BUY_COMPANY:
01177       case WC_COMPANY:
01178       case WC_COMPANY_INFRASTRUCTURE:
01179         continue;
01180 
01181       default:
01182         w->owner = new_owner;
01183         break;
01184     }
01185   }
01186 }
01187 
01188 static void BringWindowToFront(Window *w);
01189 
01197 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
01198 {
01199   Window *w = FindWindowById(cls, number);
01200 
01201   if (w != NULL) {
01202     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
01203 
01204     w->SetWhiteBorder();
01205     BringWindowToFront(w);
01206     w->SetDirty();
01207   }
01208 
01209   return w;
01210 }
01211 
01212 static inline bool IsVitalWindow(const Window *w)
01213 {
01214   switch (w->window_class) {
01215     case WC_MAIN_TOOLBAR:
01216     case WC_STATUS_BAR:
01217     case WC_NEWS_WINDOW:
01218     case WC_SEND_NETWORK_MSG:
01219       return true;
01220 
01221     default:
01222       return false;
01223   }
01224 }
01225 
01234 static uint GetWindowZPriority(const Window *w)
01235 {
01236   assert(w->window_class != WC_INVALID);
01237 
01238   uint z_priority = 0;
01239 
01240   switch (w->window_class) {
01241     case WC_ENDSCREEN:
01242       ++z_priority;
01243 
01244     case WC_HIGHSCORE:
01245       ++z_priority;
01246 
01247     case WC_TOOLTIPS:
01248       ++z_priority;
01249 
01250     case WC_DROPDOWN_MENU:
01251       ++z_priority;
01252 
01253     case WC_MAIN_TOOLBAR:
01254     case WC_STATUS_BAR:
01255       ++z_priority;
01256 
01257     case WC_OSK:
01258       ++z_priority;
01259 
01260     case WC_QUERY_STRING:
01261     case WC_SEND_NETWORK_MSG:
01262       ++z_priority;
01263 
01264     case WC_ERRMSG:
01265     case WC_CONFIRM_POPUP_QUERY:
01266     case WC_MODAL_PROGRESS:
01267     case WC_NETWORK_STATUS_WINDOW:
01268       ++z_priority;
01269 
01270     case WC_GENERATE_LANDSCAPE:
01271     case WC_SAVELOAD:
01272     case WC_GAME_OPTIONS:
01273     case WC_CUSTOM_CURRENCY:
01274     case WC_NETWORK_WINDOW:
01275     case WC_GRF_PARAMETERS:
01276     case WC_AI_LIST:
01277     case WC_AI_SETTINGS:
01278     case WC_TEXTFILE:
01279       ++z_priority;
01280 
01281     case WC_CONSOLE:
01282       ++z_priority;
01283 
01284     case WC_NEWS_WINDOW:
01285       ++z_priority;
01286 
01287     default:
01288       ++z_priority;
01289 
01290     case WC_MAIN_WINDOW:
01291       return z_priority;
01292   }
01293 }
01294 
01299 static void AddWindowToZOrdering(Window *w)
01300 {
01301   assert(w->z_front == NULL && w->z_back == NULL);
01302 
01303   if (_z_front_window == NULL) {
01304     /* It's the only window. */
01305     _z_front_window = _z_back_window = w;
01306     w->z_front = w->z_back = NULL;
01307   } else {
01308     /* Search down the z-ordering for its location. */
01309     Window *v = _z_front_window;
01310     uint last_z_priority = UINT_MAX;
01311     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
01312       if (v->window_class != WC_INVALID) {
01313         /* Sanity check z-ordering, while we're at it. */
01314         assert(last_z_priority >= GetWindowZPriority(v));
01315         last_z_priority = GetWindowZPriority(v);
01316       }
01317 
01318       v = v->z_back;
01319     }
01320 
01321     if (v == NULL) {
01322       /* It's the new back window. */
01323       w->z_front = _z_back_window;
01324       w->z_back = NULL;
01325       _z_back_window->z_back = w;
01326       _z_back_window = w;
01327     } else if (v == _z_front_window) {
01328       /* It's the new front window. */
01329       w->z_front = NULL;
01330       w->z_back = _z_front_window;
01331       _z_front_window->z_front = w;
01332       _z_front_window = w;
01333     } else {
01334       /* It's somewhere else in the z-ordering. */
01335       w->z_front = v->z_front;
01336       w->z_back = v;
01337       v->z_front->z_back = w;
01338       v->z_front = w;
01339     }
01340   }
01341 }
01342 
01343 
01348 static void RemoveWindowFromZOrdering(Window *w)
01349 {
01350   if (w->z_front == NULL) {
01351     assert(_z_front_window == w);
01352     _z_front_window = w->z_back;
01353   } else {
01354     w->z_front->z_back = w->z_back;
01355   }
01356 
01357   if (w->z_back == NULL) {
01358     assert(_z_back_window == w);
01359     _z_back_window = w->z_front;
01360   } else {
01361     w->z_back->z_front = w->z_front;
01362   }
01363 
01364   w->z_front = w->z_back = NULL;
01365 }
01366 
01372 static void BringWindowToFront(Window *w)
01373 {
01374   RemoveWindowFromZOrdering(w);
01375   AddWindowToZOrdering(w);
01376 
01377   w->SetDirty();
01378 }
01379 
01388 void Window::InitializeData(WindowNumber window_number)
01389 {
01390   /* Set up window properties; some of them are needed to set up smallest size below */
01391   this->window_class = this->window_desc->cls;
01392   this->SetWhiteBorder();
01393   if (this->window_desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
01394   this->owner = INVALID_OWNER;
01395   this->nested_focus = NULL;
01396   this->window_number = window_number;
01397 
01398   this->OnInit();
01399   /* Initialize nested widget tree. */
01400   if (this->nested_array == NULL) {
01401     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01402     this->nested_root->SetupSmallestSize(this, true);
01403   } else {
01404     this->nested_root->SetupSmallestSize(this, false);
01405   }
01406   /* Initialize to smallest size. */
01407   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
01408 
01409   /* Further set up window properties,
01410    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
01411   this->resize.step_width  = this->nested_root->resize_x;
01412   this->resize.step_height = this->nested_root->resize_y;
01413 
01414   /* Give focus to the opened window unless a text box
01415    * of focused window has focus (so we don't interrupt typing). But if the new
01416    * window has a text box, then take focus anyway. */
01417   if (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL) SetFocusedWindow(this);
01418 
01419   /* Insert the window into the correct location in the z-ordering. */
01420   AddWindowToZOrdering(this);
01421 }
01422 
01430 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
01431 {
01432   this->left = x;
01433   this->top = y;
01434   this->width = sm_width;
01435   this->height = sm_height;
01436 }
01437 
01448 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
01449 {
01450   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
01451   def_height = max(def_height, this->height);
01452   /* Try to make windows smaller when our window is too small.
01453    * w->(width|height) is normally the same as min_(width|height),
01454    * but this way the GUIs can be made a little more dynamic;
01455    * one can use the same spec for multiple windows and those
01456    * can then determine the real minimum size of the window. */
01457   if (this->width != def_width || this->height != def_height) {
01458     /* Think about the overlapping toolbars when determining the minimum window size */
01459     int free_height = _screen.height;
01460     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01461     if (wt != NULL) free_height -= wt->height;
01462     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01463     if (wt != NULL) free_height -= wt->height;
01464 
01465     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
01466     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
01467 
01468     /* X and Y has to go by step.. calculate it.
01469      * The cast to int is necessary else x/y are implicitly casted to
01470      * unsigned int, which won't work. */
01471     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01472     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01473 
01474     ResizeWindow(this, enlarge_x, enlarge_y);
01475     /* ResizeWindow() calls this->OnResize(). */
01476   } else {
01477     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
01478     this->OnResize();
01479   }
01480 
01481   int nx = this->left;
01482   int ny = this->top;
01483 
01484   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01485 
01486   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01487   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01488   nx = max(nx, 0);
01489 
01490   if (this->viewport != NULL) {
01491     this->viewport->left += nx - this->left;
01492     this->viewport->top  += ny - this->top;
01493   }
01494   this->left = nx;
01495   this->top = ny;
01496 
01497   this->SetDirty();
01498 }
01499 
01511 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01512 {
01513   int right  = width + left;
01514   int bottom = height + top;
01515 
01516   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01517   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01518 
01519   /* Make sure it is not obscured by any window. */
01520   const Window *w;
01521   FOR_ALL_WINDOWS_FROM_BACK(w) {
01522     if (w->window_class == WC_MAIN_WINDOW) continue;
01523 
01524     if (right > w->left &&
01525         w->left + w->width > left &&
01526         bottom > w->top &&
01527         w->top + w->height > top) {
01528       return false;
01529     }
01530   }
01531 
01532   pos.x = left;
01533   pos.y = top;
01534   return true;
01535 }
01536 
01548 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01549 {
01550   /* Left part of the rectangle may be at most 1/4 off-screen,
01551    * right part of the rectangle may be at most 1/2 off-screen
01552    */
01553   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01554   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01555   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01556 
01557   /* Make sure it is not obscured by any window. */
01558   const Window *w;
01559   FOR_ALL_WINDOWS_FROM_BACK(w) {
01560     if (w->window_class == WC_MAIN_WINDOW) continue;
01561 
01562     if (left + width > w->left &&
01563         w->left + w->width > left &&
01564         top + height > w->top &&
01565         w->top + w->height > top) {
01566       return false;
01567     }
01568   }
01569 
01570   pos.x = left;
01571   pos.y = top;
01572   return true;
01573 }
01574 
01581 static Point GetAutoPlacePosition(int width, int height)
01582 {
01583   Point pt;
01584 
01585   /* First attempt, try top-left of the screen */
01586   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01587   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01588 
01589   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01590    * The new window must be entirely on-screen, and not overlap with an existing window.
01591    * Eight starting points are tried, two at each corner.
01592    */
01593   const Window *w;
01594   FOR_ALL_WINDOWS_FROM_BACK(w) {
01595     if (w->window_class == WC_MAIN_WINDOW) continue;
01596 
01597     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01598     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01599     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01600     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01601     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01602     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01603     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01604     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01605   }
01606 
01607   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01608    * The new window may be partly off-screen, and must not overlap with an existing window.
01609    * Only four starting points are tried.
01610    */
01611   FOR_ALL_WINDOWS_FROM_BACK(w) {
01612     if (w->window_class == WC_MAIN_WINDOW) continue;
01613 
01614     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01615     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01616     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01617     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01618   }
01619 
01620   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01621    * of (+5, +5)
01622    */
01623   int left = 0, top = 24;
01624 
01625 restart:
01626   FOR_ALL_WINDOWS_FROM_BACK(w) {
01627     if (w->left == left && w->top == top) {
01628       left += 5;
01629       top += 5;
01630       goto restart;
01631     }
01632   }
01633 
01634   pt.x = left;
01635   pt.y = top;
01636   return pt;
01637 }
01638 
01645 Point GetToolbarAlignedWindowPosition(int window_width)
01646 {
01647   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01648   assert(w != NULL);
01649   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01650   return pt;
01651 }
01652 
01670 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01671 {
01672   Point pt;
01673   const Window *w;
01674 
01675   int16 default_width  = max(desc->GetDefaultWidth(),  sm_width);
01676   int16 default_height = max(desc->GetDefaultHeight(), sm_height);
01677 
01678   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01679       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01680       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01681 
01682     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01683     if (pt.x > _screen.width + 10 - default_width) {
01684       pt.x = (_screen.width + 10 - default_width) - 20;
01685     }
01686     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01687     return pt;
01688   }
01689 
01690   switch (desc->default_pos) {
01691     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01692       return GetToolbarAlignedWindowPosition(default_width);
01693 
01694     case WDP_AUTO: // Find a good automatic position for the window
01695       return GetAutoPlacePosition(default_width, default_height);
01696 
01697     case WDP_CENTER: // Centre the window horizontally
01698       pt.x = (_screen.width - default_width) / 2;
01699       pt.y = (_screen.height - default_height) / 2;
01700       break;
01701 
01702     case WDP_MANUAL:
01703       pt.x = 0;
01704       pt.y = 0;
01705       break;
01706 
01707     default:
01708       NOT_REACHED();
01709   }
01710 
01711   return pt;
01712 }
01713 
01714 /* virtual */ Point Window::OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
01715 {
01716   return LocalGetWindowPlacement(this->window_desc, sm_width, sm_height, window_number);
01717 }
01718 
01726 void Window::CreateNestedTree(bool fill_nested)
01727 {
01728   int biggest_index = -1;
01729   this->nested_root = MakeWindowNWidgetTree(this->window_desc->nwid_parts, this->window_desc->nwid_length, &biggest_index, &this->shade_select);
01730   this->nested_array_size = (uint)(biggest_index + 1);
01731 
01732   if (fill_nested) {
01733     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01734     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01735   }
01736 }
01737 
01742 void Window::FinishInitNested(WindowNumber window_number)
01743 {
01744   this->InitializeData(window_number);
01745   this->ApplyDefaults();
01746   Point pt = this->OnInitialPosition(this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01747   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01748   this->FindWindowPlacementAndResize(this->window_desc->GetDefaultWidth(), this->window_desc->GetDefaultHeight());
01749 }
01750 
01755 void Window::InitNested(WindowNumber window_number)
01756 {
01757   this->CreateNestedTree(false);
01758   this->FinishInitNested(window_number);
01759 }
01760 
01765 Window::Window(WindowDesc *desc) : window_desc(desc), scrolling_scrollbar(-1)
01766 {
01767 }
01768 
01776 Window *FindWindowFromPt(int x, int y)
01777 {
01778   Window *w;
01779   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01780     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01781       return w;
01782     }
01783   }
01784 
01785   return NULL;
01786 }
01787 
01791 void InitWindowSystem()
01792 {
01793   IConsoleClose();
01794 
01795   _z_back_window = NULL;
01796   _z_front_window = NULL;
01797   _focused_window = NULL;
01798   _mouseover_last_w = NULL;
01799   _last_scroll_window = NULL;
01800   _scrolling_viewport = false;
01801   _mouse_hovering = false;
01802 
01803   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01804   NWidgetScrollbar::InvalidateDimensionCache();
01805 
01806   ShowFirstError();
01807 }
01808 
01812 void UnInitWindowSystem()
01813 {
01814   UnshowCriticalError();
01815 
01816   Window *w;
01817   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01818 
01819   for (w = _z_front_window; w != NULL; /* nothing */) {
01820     Window *to_del = w;
01821     w = w->z_back;
01822     free(to_del);
01823   }
01824 
01825   _z_front_window = NULL;
01826   _z_back_window = NULL;
01827 }
01828 
01832 void ResetWindowSystem()
01833 {
01834   UnInitWindowSystem();
01835   InitWindowSystem();
01836   _thd.Reset();
01837 }
01838 
01839 static void DecreaseWindowCounters()
01840 {
01841   Window *w;
01842   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01843     if (_scroller_click_timeout == 0) {
01844       /* Unclick scrollbar buttons if they are pressed. */
01845       for (uint i = 0; i < w->nested_array_size; i++) {
01846         NWidgetBase *nwid = w->nested_array[i];
01847         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01848           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01849           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01850             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01851             w->scrolling_scrollbar = -1;
01852             sb->SetDirty(w);
01853           }
01854         }
01855       }
01856     }
01857 
01858     /* Handle editboxes */
01859     for (SmallMap<int, QueryString*>::Pair *it = w->querystrings.Begin(); it != w->querystrings.End(); ++it) {
01860       it->second->HandleEditBox(w, it->first);
01861     }
01862 
01863     w->OnMouseLoop();
01864   }
01865 
01866   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01867     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
01868       CLRBITS(w->flags, WF_TIMEOUT);
01869 
01870       w->OnTimeout();
01871       w->RaiseButtons(true);
01872     }
01873   }
01874 }
01875 
01876 static void HandlePlacePresize()
01877 {
01878   if (_special_mouse_mode != WSM_PRESIZE) return;
01879 
01880   Window *w = _thd.GetCallbackWnd();
01881   if (w == NULL) return;
01882 
01883   Point pt = GetTileBelowCursor();
01884   if (pt.x == -1) {
01885     _thd.selend.x = -1;
01886     return;
01887   }
01888 
01889   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01890 }
01891 
01896 static EventState HandleMouseDragDrop()
01897 {
01898   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01899 
01900   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
01901 
01902   Window *w = _thd.GetCallbackWnd();
01903   if (w != NULL) {
01904     /* Send an event in client coordinates. */
01905     Point pt;
01906     pt.x = _cursor.pos.x - w->left;
01907     pt.y = _cursor.pos.y - w->top;
01908     if (_left_button_down) {
01909       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01910     } else {
01911       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01912     }
01913   }
01914 
01915   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
01916   return ES_HANDLED;
01917 }
01918 
01920 static void HandleMouseOver()
01921 {
01922   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01923 
01924   /* We changed window, put a MOUSEOVER event to the last window */
01925   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01926     /* Reset mouse-over coordinates of previous window */
01927     Point pt = { -1, -1 };
01928     _mouseover_last_w->OnMouseOver(pt, 0);
01929   }
01930 
01931   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01932   _mouseover_last_w = w;
01933 
01934   if (w != NULL) {
01935     /* send an event in client coordinates. */
01936     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01937     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01938     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01939   }
01940 }
01941 
01943 static const int MIN_VISIBLE_TITLE_BAR = 13;
01944 
01946 enum PreventHideDirection {
01947   PHD_UP,   
01948   PHD_DOWN, 
01949 };
01950 
01961 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01962 {
01963   if (v == NULL) return;
01964 
01965   int v_bottom = v->top + v->height;
01966   int v_right = v->left + v->width;
01967   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.
01968 
01969   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01970   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01971 
01972   /* Vertically, the rectangle is hidden behind v. */
01973   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01974     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01975     return;
01976   }
01977   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01978     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01979     return;
01980   }
01981 
01982   /* Horizontally also hidden, force movement to a safe area. */
01983   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01984     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01985   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01986     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01987   } else {
01988     *ny = safe_y;
01989   }
01990 }
01991 
01999 static void EnsureVisibleCaption(Window *w, int nx, int ny)
02000 {
02001   /* Search for the title bar rectangle. */
02002   Rect caption_rect;
02003   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
02004   if (caption != NULL) {
02005     caption_rect.left   = caption->pos_x;
02006     caption_rect.right  = caption->pos_x + caption->current_x;
02007     caption_rect.top    = caption->pos_y;
02008     caption_rect.bottom = caption->pos_y + caption->current_y;
02009 
02010     /* Make sure the window doesn't leave the screen */
02011     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
02012     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
02013 
02014     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
02015     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
02016     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
02017   }
02018 
02019   if (w->viewport != NULL) {
02020     w->viewport->left += nx - w->left;
02021     w->viewport->top  += ny - w->top;
02022   }
02023 
02024   w->left = nx;
02025   w->top  = ny;
02026 }
02027 
02038 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
02039 {
02040   if (delta_x != 0 || delta_y != 0) {
02041     if (clamp_to_screen) {
02042       /* Determine the new right/bottom position. If that is outside of the bounds of
02043        * the resolution clamp it in such a manner that it stays within the bounds. */
02044       int new_right  = w->left + w->width  + delta_x;
02045       int new_bottom = w->top  + w->height + delta_y;
02046       if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
02047       if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
02048     }
02049 
02050     w->SetDirty();
02051 
02052     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);
02053     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);
02054     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
02055     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
02056 
02057     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);
02058     w->width  = w->nested_root->current_x;
02059     w->height = w->nested_root->current_y;
02060   }
02061 
02062   EnsureVisibleCaption(w, w->left, w->top);
02063 
02064   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
02065   w->OnResize();
02066   w->SetDirty();
02067 }
02068 
02074 int GetMainViewTop()
02075 {
02076   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02077   return (w == NULL) ? 0 : w->top + w->height;
02078 }
02079 
02085 int GetMainViewBottom()
02086 {
02087   Window *w = FindWindowById(WC_STATUS_BAR, 0);
02088   return (w == NULL) ? _screen.height : w->top;
02089 }
02090 
02091 static bool _dragging_window; 
02092 
02097 static EventState HandleWindowDragging()
02098 {
02099   /* Get out immediately if no window is being dragged at all. */
02100   if (!_dragging_window) return ES_NOT_HANDLED;
02101 
02102   /* If button still down, but cursor hasn't moved, there is nothing to do. */
02103   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
02104 
02105   /* Otherwise find the window... */
02106   Window *w;
02107   FOR_ALL_WINDOWS_FROM_BACK(w) {
02108     if (w->flags & WF_DRAGGING) {
02109       /* Stop the dragging if the left mouse button was released */
02110       if (!_left_button_down) {
02111         w->flags &= ~WF_DRAGGING;
02112         break;
02113       }
02114 
02115       w->SetDirty();
02116 
02117       int x = _cursor.pos.x + _drag_delta.x;
02118       int y = _cursor.pos.y + _drag_delta.y;
02119       int nx = x;
02120       int ny = y;
02121 
02122       if (_settings_client.gui.window_snap_radius != 0) {
02123         const Window *v;
02124 
02125         int hsnap = _settings_client.gui.window_snap_radius;
02126         int vsnap = _settings_client.gui.window_snap_radius;
02127         int delta;
02128 
02129         FOR_ALL_WINDOWS_FROM_BACK(v) {
02130           if (v == w) continue; // Don't snap at yourself
02131 
02132           if (y + w->height > v->top && y < v->top + v->height) {
02133             /* Your left border <-> other right border */
02134             delta = abs(v->left + v->width - x);
02135             if (delta <= hsnap) {
02136               nx = v->left + v->width;
02137               hsnap = delta;
02138             }
02139 
02140             /* Your right border <-> other left border */
02141             delta = abs(v->left - x - w->width);
02142             if (delta <= hsnap) {
02143               nx = v->left - w->width;
02144               hsnap = delta;
02145             }
02146           }
02147 
02148           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
02149             /* Your left border <-> other left border */
02150             delta = abs(v->left - x);
02151             if (delta <= hsnap) {
02152               nx = v->left;
02153               hsnap = delta;
02154             }
02155 
02156             /* Your right border <-> other right border */
02157             delta = abs(v->left + v->width - x - w->width);
02158             if (delta <= hsnap) {
02159               nx = v->left + v->width - w->width;
02160               hsnap = delta;
02161             }
02162           }
02163 
02164           if (x + w->width > v->left && x < v->left + v->width) {
02165             /* Your top border <-> other bottom border */
02166             delta = abs(v->top + v->height - y);
02167             if (delta <= vsnap) {
02168               ny = v->top + v->height;
02169               vsnap = delta;
02170             }
02171 
02172             /* Your bottom border <-> other top border */
02173             delta = abs(v->top - y - w->height);
02174             if (delta <= vsnap) {
02175               ny = v->top - w->height;
02176               vsnap = delta;
02177             }
02178           }
02179 
02180           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
02181             /* Your top border <-> other top border */
02182             delta = abs(v->top - y);
02183             if (delta <= vsnap) {
02184               ny = v->top;
02185               vsnap = delta;
02186             }
02187 
02188             /* Your bottom border <-> other bottom border */
02189             delta = abs(v->top + v->height - y - w->height);
02190             if (delta <= vsnap) {
02191               ny = v->top + v->height - w->height;
02192               vsnap = delta;
02193             }
02194           }
02195         }
02196       }
02197 
02198       EnsureVisibleCaption(w, nx, ny);
02199 
02200       w->SetDirty();
02201       return ES_HANDLED;
02202     } else if (w->flags & WF_SIZING) {
02203       /* Stop the sizing if the left mouse button was released */
02204       if (!_left_button_down) {
02205         w->flags &= ~WF_SIZING;
02206         w->SetDirty();
02207         break;
02208       }
02209 
02210       /* Compute difference in pixels between cursor position and reference point in the window.
02211        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
02212        */
02213       int x, y = _cursor.pos.y - _drag_delta.y;
02214       if (w->flags & WF_SIZING_LEFT) {
02215         x = _drag_delta.x - _cursor.pos.x;
02216       } else {
02217         x = _cursor.pos.x - _drag_delta.x;
02218       }
02219 
02220       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
02221       if (w->resize.step_width  == 0) x = 0;
02222       if (w->resize.step_height == 0) y = 0;
02223 
02224       /* Check the resize button won't go past the bottom of the screen */
02225       if (w->top + w->height + y > _screen.height) {
02226         y = _screen.height - w->height - w->top;
02227       }
02228 
02229       /* X and Y has to go by step.. calculate it.
02230        * The cast to int is necessary else x/y are implicitly casted to
02231        * unsigned int, which won't work. */
02232       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
02233       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
02234 
02235       /* Check that we don't go below the minimum set size */
02236       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
02237         x = w->nested_root->smallest_x - w->width;
02238       }
02239       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
02240         y = w->nested_root->smallest_y - w->height;
02241       }
02242 
02243       /* Window already on size */
02244       if (x == 0 && y == 0) return ES_HANDLED;
02245 
02246       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
02247       _drag_delta.y += y;
02248       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
02249         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
02250         w->SetDirty();
02251         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
02252         /* ResizeWindow() below ensures marking new position as dirty. */
02253       } else {
02254         _drag_delta.x += x;
02255       }
02256 
02257       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
02258       ResizeWindow(w, x, y);
02259       return ES_HANDLED;
02260     }
02261   }
02262 
02263   _dragging_window = false;
02264   return ES_HANDLED;
02265 }
02266 
02271 static void StartWindowDrag(Window *w)
02272 {
02273   w->flags |= WF_DRAGGING;
02274   w->flags &= ~WF_CENTERED;
02275   _dragging_window = true;
02276 
02277   _drag_delta.x = w->left - _cursor.pos.x;
02278   _drag_delta.y = w->top  - _cursor.pos.y;
02279 
02280   BringWindowToFront(w);
02281   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02282 }
02283 
02289 static void StartWindowSizing(Window *w, bool to_left)
02290 {
02291   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
02292   w->flags &= ~WF_CENTERED;
02293   _dragging_window = true;
02294 
02295   _drag_delta.x = _cursor.pos.x;
02296   _drag_delta.y = _cursor.pos.y;
02297 
02298   BringWindowToFront(w);
02299   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02300 }
02301 
02306 static EventState HandleScrollbarScrolling()
02307 {
02308   Window *w;
02309   FOR_ALL_WINDOWS_FROM_BACK(w) {
02310     if (w->scrolling_scrollbar >= 0) {
02311       /* Abort if no button is clicked any more. */
02312       if (!_left_button_down) {
02313         w->scrolling_scrollbar = -1;
02314         w->SetDirty();
02315         return ES_HANDLED;
02316       }
02317 
02318       int i;
02319       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
02320       bool rtl = false;
02321 
02322       if (sb->type == NWID_HSCROLLBAR) {
02323         i = _cursor.pos.x - _cursorpos_drag_start.x;
02324         rtl = _current_text_dir == TD_RTL;
02325       } else {
02326         i = _cursor.pos.y - _cursorpos_drag_start.y;
02327       }
02328 
02329       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
02330         if (_scroller_click_timeout == 1) {
02331           _scroller_click_timeout = 3;
02332           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
02333           w->SetDirty();
02334         }
02335         return ES_HANDLED;
02336       }
02337 
02338       /* Find the item we want to move to and make sure it's inside bounds. */
02339       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
02340       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
02341       if (pos != sb->GetPosition()) {
02342         sb->SetPosition(pos);
02343         w->SetDirty();
02344       }
02345       return ES_HANDLED;
02346     }
02347   }
02348 
02349   return ES_NOT_HANDLED;
02350 }
02351 
02356 static EventState HandleViewportScroll()
02357 {
02358   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02359 
02360   if (!_scrolling_viewport) return ES_NOT_HANDLED;
02361 
02362   /* When we don't have a last scroll window we are starting to scroll.
02363    * When the last scroll window and this are not the same we went
02364    * outside of the window and should not left-mouse scroll anymore. */
02365   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
02366 
02367   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
02368     _cursor.fix_at = false;
02369     _scrolling_viewport = false;
02370     _last_scroll_window = NULL;
02371     return ES_NOT_HANDLED;
02372   }
02373 
02374   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
02375     /* If the main window is following a vehicle, then first let go of it! */
02376     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
02377     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
02378     return ES_NOT_HANDLED;
02379   }
02380 
02381   Point delta;
02382   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
02383     delta.x = -_cursor.delta.x;
02384     delta.y = -_cursor.delta.y;
02385   } else {
02386     delta.x = _cursor.delta.x;
02387     delta.y = _cursor.delta.y;
02388   }
02389 
02390   if (scrollwheel_scrolling) {
02391     /* We are using scrollwheels for scrolling */
02392     delta.x = _cursor.h_wheel;
02393     delta.y = _cursor.v_wheel;
02394     _cursor.v_wheel = 0;
02395     _cursor.h_wheel = 0;
02396   }
02397 
02398   /* Create a scroll-event and send it to the window */
02399   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
02400 
02401   _cursor.delta.x = 0;
02402   _cursor.delta.y = 0;
02403   return ES_HANDLED;
02404 }
02405 
02416 static bool MaybeBringWindowToFront(Window *w)
02417 {
02418   bool bring_to_front = false;
02419 
02420   if (w->window_class == WC_MAIN_WINDOW ||
02421       IsVitalWindow(w) ||
02422       w->window_class == WC_TOOLTIPS ||
02423       w->window_class == WC_DROPDOWN_MENU) {
02424     return true;
02425   }
02426 
02427   /* Use unshaded window size rather than current size for shaded windows. */
02428   int w_width  = w->width;
02429   int w_height = w->height;
02430   if (w->IsShaded()) {
02431     w_width  = w->unshaded_size.width;
02432     w_height = w->unshaded_size.height;
02433   }
02434 
02435   Window *u;
02436   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
02437     /* A modal child will prevent the activation of the parent window */
02438     if (u->parent == w && (u->window_desc->flags & WDF_MODAL)) {
02439       u->SetWhiteBorder();
02440       u->SetDirty();
02441       return false;
02442     }
02443 
02444     if (u->window_class == WC_MAIN_WINDOW ||
02445         IsVitalWindow(u) ||
02446         u->window_class == WC_TOOLTIPS ||
02447         u->window_class == WC_DROPDOWN_MENU) {
02448       continue;
02449     }
02450 
02451     /* Window sizes don't interfere, leave z-order alone */
02452     if (w->left + w_width <= u->left ||
02453         u->left + u->width <= w->left ||
02454         w->top  + w_height <= u->top ||
02455         u->top + u->height <= w->top) {
02456       continue;
02457     }
02458 
02459     bring_to_front = true;
02460   }
02461 
02462   if (bring_to_front) BringWindowToFront(w);
02463   return true;
02464 }
02465 
02474 EventState Window::HandleEditBoxKey(int wid, WChar key, uint16 keycode)
02475 {
02476   QueryString *query = this->GetQueryString(wid);
02477   if (query == NULL) return ES_NOT_HANDLED;
02478 
02479   int action = QueryString::ACTION_NOTHING;
02480 
02481   switch (query->text.HandleKeyPress(key, keycode)) {
02482     case HKPR_EDITING:
02483       this->SetWidgetDirty(wid);
02484       this->OnEditboxChanged(wid);
02485       break;
02486 
02487     case HKPR_CURSOR:
02488       this->SetWidgetDirty(wid);
02489       /* For the OSK also invalidate the parent window */
02490       if (this->window_class == WC_OSK) this->InvalidateData();
02491       break;
02492 
02493     case HKPR_CONFIRM:
02494       if (this->window_class == WC_OSK) {
02495         this->OnClick(Point(), WID_OSK_OK, 1);
02496       } else if (query->ok_button >= 0) {
02497         this->OnClick(Point(), query->ok_button, 1);
02498       } else {
02499         action = query->ok_button;
02500       }
02501       break;
02502 
02503     case HKPR_CANCEL:
02504       if (this->window_class == WC_OSK) {
02505         this->OnClick(Point(), WID_OSK_CANCEL, 1);
02506       } else if (query->cancel_button >= 0) {
02507         this->OnClick(Point(), query->cancel_button, 1);
02508       } else {
02509         action = query->cancel_button;
02510       }
02511       break;
02512 
02513     case HKPR_NOT_HANDLED:
02514       return ES_NOT_HANDLED;
02515 
02516     default: break;
02517   }
02518 
02519   switch (action) {
02520     case QueryString::ACTION_DESELECT:
02521       this->UnfocusFocusedWidget();
02522       break;
02523 
02524     case QueryString::ACTION_CLEAR:
02525       if (query->text.bytes <= 1) {
02526         /* If already empty, unfocus instead */
02527         this->UnfocusFocusedWidget();
02528       } else {
02529         query->text.DeleteAll();
02530         this->SetWidgetDirty(wid);
02531         this->OnEditboxChanged(wid);
02532       }
02533       break;
02534 
02535     default:
02536       break;
02537   }
02538 
02539   return ES_HANDLED;
02540 }
02541 
02547 void HandleKeypress(uint keycode, WChar key)
02548 {
02549   /* World generation is multithreaded and messes with companies.
02550    * But there is no company related window open anyway, so _current_company is not used. */
02551   assert(HasModalProgress() || IsLocalCompany());
02552 
02553   /*
02554    * The Unicode standard defines an area called the private use area. Code points in this
02555    * area are reserved for private use and thus not portable between systems. For instance,
02556    * Apple defines code points for the arrow keys in this area, but these are only printable
02557    * on a system running OS X. We don't want these keys to show up in text fields and such,
02558    * and thus we have to clear the unicode character when we encounter such a key.
02559    */
02560   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02561 
02562   /*
02563    * If both key and keycode is zero, we don't bother to process the event.
02564    */
02565   if (key == 0 && keycode == 0) return;
02566 
02567   /* Check if the focused window has a focused editbox */
02568   if (EditBoxInGlobalFocus()) {
02569     /* All input will in this case go to the focused editbox */
02570     if (_focused_window->window_class == WC_CONSOLE) {
02571       if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02572     } else {
02573       if (_focused_window->HandleEditBoxKey(_focused_window->nested_focus->index, key, keycode) == ES_HANDLED) return;
02574     }
02575   }
02576 
02577   /* Call the event, start with the uppermost window, but ignore the toolbar. */
02578   Window *w;
02579   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02580     if (w->window_class == WC_MAIN_TOOLBAR) continue;
02581     if (w->window_desc->hotkeys != NULL) {
02582       int hotkey = w->window_desc->hotkeys->CheckMatch(keycode);
02583       if (hotkey >= 0 && w->OnHotkey(hotkey) == ES_HANDLED) return;
02584     }
02585     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02586   }
02587 
02588   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02589   /* When there is no toolbar w is null, check for that */
02590   if (w != NULL) {
02591     if (w->window_desc->hotkeys != NULL) {
02592       int hotkey = w->window_desc->hotkeys->CheckMatch(keycode);
02593       if (hotkey >= 0 && w->OnHotkey(hotkey) == ES_HANDLED) return;
02594     }
02595     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02596   }
02597 
02598   HandleGlobalHotkeys(key, keycode);
02599 }
02600 
02604 void HandleCtrlChanged()
02605 {
02606   /* Call the event, start with the uppermost window. */
02607   Window *w;
02608   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02609     if (w->OnCTRLStateChange() == ES_HANDLED) return;
02610   }
02611 }
02612 
02618 /* virtual */ void Window::InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
02619 {
02620   QueryString *query = this->GetQueryString(wid);
02621   if (query == NULL) return;
02622 
02623   if (query->text.InsertString(str, marked, caret, insert_location, replacement_end) || marked) {
02624     this->SetWidgetDirty(wid);
02625     this->OnEditboxChanged(wid);
02626   }
02627 }
02628 
02635 void HandleTextInput(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
02636 {
02637   if (!EditBoxInGlobalFocus()) return;
02638 
02639   _focused_window->InsertTextString(_focused_window->window_class == WC_CONSOLE ? 0 : _focused_window->nested_focus->index, str, marked, caret, insert_location, replacement_end);
02640 }
02641 
02648 static int _input_events_this_tick = 0;
02649 
02654 static void HandleAutoscroll()
02655 {
02656   if (_game_mode == GM_MENU || HasModalProgress()) return;
02657   if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
02658   if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
02659 
02660   int x = _cursor.pos.x;
02661   int y = _cursor.pos.y;
02662   Window *w = FindWindowFromPt(x, y);
02663   if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
02664   if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
02665 
02666   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02667   if (vp == NULL) return;
02668 
02669   x -= vp->left;
02670   y -= vp->top;
02671 
02672   /* here allows scrolling in both x and y axis */
02673 #define scrollspeed 3
02674   if (x - 15 < 0) {
02675     w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02676   } else if (15 - (vp->width - x) > 0) {
02677     w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02678   }
02679   if (y - 15 < 0) {
02680     w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02681   } else if (15 - (vp->height - y) > 0) {
02682     w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02683   }
02684 #undef scrollspeed
02685 }
02686 
02687 enum MouseClick {
02688   MC_NONE = 0,
02689   MC_LEFT,
02690   MC_RIGHT,
02691   MC_DOUBLE_LEFT,
02692   MC_HOVER,
02693 
02694   MAX_OFFSET_DOUBLE_CLICK = 5,     
02695   TIME_BETWEEN_DOUBLE_CLICK = 500, 
02696   MAX_OFFSET_HOVER = 5,            
02697 };
02698 extern EventState VpHandlePlaceSizingDrag();
02699 
02700 static void ScrollMainViewport(int x, int y)
02701 {
02702   if (_game_mode != GM_MENU) {
02703     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02704     assert(w);
02705 
02706     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02707     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02708   }
02709 }
02710 
02720 static const int8 scrollamt[16][2] = {
02721   { 0,  0}, 
02722   {-2,  0}, 
02723   { 0, -2}, 
02724   {-2, -1}, 
02725   { 2,  0}, 
02726   { 0,  0}, 
02727   { 2, -1}, 
02728   { 0, -2}, 
02729   { 0,  2}, 
02730   {-2,  1}, 
02731   { 0,  0}, 
02732   {-2,  0}, 
02733   { 2,  1}, 
02734   { 0,  2}, 
02735   { 2,  0}, 
02736   { 0,  0}, 
02737 };
02738 
02739 static void HandleKeyScrolling()
02740 {
02741   /*
02742    * Check that any of the dirkeys is pressed and that the focused window
02743    * doesn't have an edit-box as focused widget.
02744    */
02745   if (_dirkeys && !EditBoxInGlobalFocus()) {
02746     int factor = _shift_pressed ? 50 : 10;
02747     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02748   }
02749 }
02750 
02751 static void MouseLoop(MouseClick click, int mousewheel)
02752 {
02753   /* World generation is multithreaded and messes with companies.
02754    * But there is no company related window open anyway, so _current_company is not used. */
02755   assert(HasModalProgress() || IsLocalCompany());
02756 
02757   HandlePlacePresize();
02758   UpdateTileSelection();
02759 
02760   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
02761   if (HandleMouseDragDrop()      == ES_HANDLED) return;
02762   if (HandleWindowDragging()     == ES_HANDLED) return;
02763   if (HandleScrollbarScrolling() == ES_HANDLED) return;
02764   if (HandleViewportScroll()     == ES_HANDLED) return;
02765 
02766   HandleMouseOver();
02767 
02768   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02769   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02770 
02771   int x = _cursor.pos.x;
02772   int y = _cursor.pos.y;
02773   Window *w = FindWindowFromPt(x, y);
02774   if (w == NULL) return;
02775 
02776   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02777   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02778 
02779   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
02780   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
02781 
02782   if (mousewheel != 0) {
02783     /* Send mousewheel event to window */
02784     w->OnMouseWheel(mousewheel);
02785 
02786     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02787     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02788   }
02789 
02790   if (vp != NULL) {
02791     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02792     switch (click) {
02793       case MC_DOUBLE_LEFT:
02794       case MC_LEFT:
02795         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02796         if (!HandleViewportClicked(vp, x, y) &&
02797             !(w->flags & WF_DISABLE_VP_SCROLL) &&
02798             _settings_client.gui.left_mouse_btn_scrolling) {
02799           _scrolling_viewport = true;
02800           _cursor.fix_at = false;
02801         }
02802         break;
02803 
02804       case MC_RIGHT:
02805         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
02806           _scrolling_viewport = true;
02807           _cursor.fix_at = true;
02808 
02809           /* clear 2D scrolling caches before we start a 2D scroll */
02810           _cursor.h_wheel = 0;
02811           _cursor.v_wheel = 0;
02812         }
02813         break;
02814 
02815       default:
02816         break;
02817     }
02818   } else {
02819     switch (click) {
02820       case MC_LEFT:
02821       case MC_DOUBLE_LEFT:
02822         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02823         break;
02824 
02825       default:
02826         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02827         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02828          * Simulate a right button click so we can get started. */
02829         /* FALL THROUGH */
02830 
02831       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02832 
02833       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02834     }
02835   }
02836 }
02837 
02841 void HandleMouseEvents()
02842 {
02843   /* World generation is multithreaded and messes with companies.
02844    * But there is no company related window open anyway, so _current_company is not used. */
02845   assert(HasModalProgress() || IsLocalCompany());
02846 
02847   static int double_click_time = 0;
02848   static Point double_click_pos = {0, 0};
02849 
02850   /* Mouse event? */
02851   MouseClick click = MC_NONE;
02852   if (_left_button_down && !_left_button_clicked) {
02853     click = MC_LEFT;
02854     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02855         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
02856         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02857       click = MC_DOUBLE_LEFT;
02858     }
02859     double_click_time = _realtime_tick;
02860     double_click_pos = _cursor.pos;
02861     _left_button_clicked = true;
02862     _input_events_this_tick++;
02863   } else if (_right_button_clicked) {
02864     _right_button_clicked = false;
02865     click = MC_RIGHT;
02866     _input_events_this_tick++;
02867   }
02868 
02869   int mousewheel = 0;
02870   if (_cursor.wheel) {
02871     mousewheel = _cursor.wheel;
02872     _cursor.wheel = 0;
02873     _input_events_this_tick++;
02874   }
02875 
02876   static uint32 hover_time = 0;
02877   static Point hover_pos = {0, 0};
02878 
02879   if (_settings_client.gui.hover_delay > 0) {
02880     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02881         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
02882         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02883       hover_pos = _cursor.pos;
02884       hover_time = _realtime_tick;
02885       _mouse_hovering = false;
02886     } else {
02887       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02888         click = MC_HOVER;
02889         _input_events_this_tick++;
02890         _mouse_hovering = true;
02891       }
02892     }
02893   }
02894 
02895   /* Handle sprite picker before any GUI interaction */
02896   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02897     /* Next realtime tick? Then redraw has finished */
02898     _newgrf_debug_sprite_picker.mode = SPM_NONE;
02899     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02900   }
02901 
02902   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02903     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
02904     Blitter *blitter = BlitterFactory::GetCurrentBlitter();
02905     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02906     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02907     _newgrf_debug_sprite_picker.sprites.Clear();
02908     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02909     MarkWholeScreenDirty();
02910   } else {
02911     MouseLoop(click, mousewheel);
02912   }
02913 
02914   /* We have moved the mouse the required distance,
02915    * no need to move it at any later time. */
02916   _cursor.delta.x = 0;
02917   _cursor.delta.y = 0;
02918 }
02919 
02923 static void CheckSoftLimit()
02924 {
02925   if (_settings_client.gui.window_soft_limit == 0) return;
02926 
02927   for (;;) {
02928     uint deletable_count = 0;
02929     Window *w, *last_deletable = NULL;
02930     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02931       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
02932 
02933       last_deletable = w;
02934       deletable_count++;
02935     }
02936 
02937     /* We've not reached the soft limit yet. */
02938     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02939 
02940     assert(last_deletable != NULL);
02941     delete last_deletable;
02942   }
02943 }
02944 
02948 void InputLoop()
02949 {
02950   /* World generation is multithreaded and messes with companies.
02951    * But there is no company related window open anyway, so _current_company is not used. */
02952   assert(HasModalProgress() || IsLocalCompany());
02953 
02954   CheckSoftLimit();
02955   HandleKeyScrolling();
02956 
02957   /* Do the actual free of the deleted windows. */
02958   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02959     Window *w = v;
02960     v = v->z_back;
02961 
02962     if (w->window_class != WC_INVALID) continue;
02963 
02964     RemoveWindowFromZOrdering(w);
02965     free(w);
02966   }
02967 
02968   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02969   DecreaseWindowCounters();
02970 
02971   if (_input_events_this_tick != 0) {
02972     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02973     _input_events_this_tick = 0;
02974     /* there were some inputs this tick, don't scroll ??? */
02975     return;
02976   }
02977 
02978   /* HandleMouseEvents was already called for this tick */
02979   HandleMouseEvents();
02980   HandleAutoscroll();
02981 }
02982 
02986 void UpdateWindows()
02987 {
02988   Window *w;
02989 
02990   static int highlight_timer = 1;
02991   if (--highlight_timer == 0) {
02992     highlight_timer = 15;
02993     _window_highlight_colour = !_window_highlight_colour;
02994   }
02995 
02996   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02997     w->ProcessScheduledInvalidations();
02998     w->ProcessHighlightedInvalidations();
02999   }
03000 
03001   static int we4_timer = 0;
03002   int t = we4_timer + 1;
03003 
03004   if (t >= 100) {
03005     FOR_ALL_WINDOWS_FROM_FRONT(w) {
03006       w->OnHundredthTick();
03007     }
03008     t = 0;
03009   }
03010   we4_timer = t;
03011 
03012   FOR_ALL_WINDOWS_FROM_FRONT(w) {
03013     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
03014       CLRBITS(w->flags, WF_WHITE_BORDER);
03015       w->SetDirty();
03016     }
03017   }
03018 
03019   DrawDirtyBlocks();
03020 
03021   FOR_ALL_WINDOWS_FROM_BACK(w) {
03022     /* Update viewport only if window is not shaded. */
03023     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
03024   }
03025   NetworkDrawChatMessage();
03026   /* Redraw mouse cursor in case it was hidden */
03027   DrawMouseCursor();
03028 }
03029 
03035 void SetWindowDirty(WindowClass cls, WindowNumber number)
03036 {
03037   const Window *w;
03038   FOR_ALL_WINDOWS_FROM_BACK(w) {
03039     if (w->window_class == cls && w->window_number == number) w->SetDirty();
03040   }
03041 }
03042 
03049 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
03050 {
03051   const Window *w;
03052   FOR_ALL_WINDOWS_FROM_BACK(w) {
03053     if (w->window_class == cls && w->window_number == number) {
03054       w->SetWidgetDirty(widget_index);
03055     }
03056   }
03057 }
03058 
03063 void SetWindowClassesDirty(WindowClass cls)
03064 {
03065   Window *w;
03066   FOR_ALL_WINDOWS_FROM_BACK(w) {
03067     if (w->window_class == cls) w->SetDirty();
03068   }
03069 }
03070 
03076 void Window::InvalidateData(int data, bool gui_scope)
03077 {
03078   this->SetDirty();
03079   if (!gui_scope) {
03080     /* Schedule GUI-scope invalidation for next redraw. */
03081     *this->scheduled_invalidation_data.Append() = data;
03082   }
03083   this->OnInvalidateData(data, gui_scope);
03084 }
03085 
03089 void Window::ProcessScheduledInvalidations()
03090 {
03091   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
03092     this->OnInvalidateData(*data, true);
03093   }
03094   this->scheduled_invalidation_data.Clear();
03095 }
03096 
03100 void Window::ProcessHighlightedInvalidations()
03101 {
03102   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
03103 
03104   for (uint i = 0; i < this->nested_array_size; i++) {
03105     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
03106   }
03107 }
03108 
03135 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
03136 {
03137   Window *w;
03138   FOR_ALL_WINDOWS_FROM_BACK(w) {
03139     if (w->window_class == cls && w->window_number == number) {
03140       w->InvalidateData(data, gui_scope);
03141     }
03142   }
03143 }
03144 
03153 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
03154 {
03155   Window *w;
03156 
03157   FOR_ALL_WINDOWS_FROM_BACK(w) {
03158     if (w->window_class == cls) {
03159       w->InvalidateData(data, gui_scope);
03160     }
03161   }
03162 }
03163 
03167 void CallWindowTickEvent()
03168 {
03169   Window *w;
03170   FOR_ALL_WINDOWS_FROM_FRONT(w) {
03171     w->OnTick();
03172   }
03173 }
03174 
03181 void DeleteNonVitalWindows()
03182 {
03183   Window *w;
03184 
03185 restart_search:
03186   /* When we find the window to delete, we need to restart the search
03187    * as deleting this window could cascade in deleting (many) others
03188    * anywhere in the z-array */
03189   FOR_ALL_WINDOWS_FROM_BACK(w) {
03190     if (w->window_class != WC_MAIN_WINDOW &&
03191         w->window_class != WC_SELECT_GAME &&
03192         w->window_class != WC_MAIN_TOOLBAR &&
03193         w->window_class != WC_STATUS_BAR &&
03194         w->window_class != WC_TOOLTIPS &&
03195         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
03196 
03197       delete w;
03198       goto restart_search;
03199     }
03200   }
03201 }
03202 
03210 void DeleteAllNonVitalWindows()
03211 {
03212   Window *w;
03213 
03214   /* Delete every window except for stickied ones, then sticky ones as well */
03215   DeleteNonVitalWindows();
03216 
03217 restart_search:
03218   /* When we find the window to delete, we need to restart the search
03219    * as deleting this window could cascade in deleting (many) others
03220    * anywhere in the z-array */
03221   FOR_ALL_WINDOWS_FROM_BACK(w) {
03222     if (w->flags & WF_STICKY) {
03223       delete w;
03224       goto restart_search;
03225     }
03226   }
03227 }
03228 
03233 void DeleteConstructionWindows()
03234 {
03235   Window *w;
03236 
03237 restart_search:
03238   /* When we find the window to delete, we need to restart the search
03239    * as deleting this window could cascade in deleting (many) others
03240    * anywhere in the z-array */
03241   FOR_ALL_WINDOWS_FROM_BACK(w) {
03242     if (w->window_desc->flags & WDF_CONSTRUCTION) {
03243       delete w;
03244       goto restart_search;
03245     }
03246   }
03247 
03248   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
03249 }
03250 
03252 void HideVitalWindows()
03253 {
03254   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
03255   DeleteWindowById(WC_STATUS_BAR, 0);
03256 }
03257 
03259 void ReInitAllWindows()
03260 {
03261   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
03262   NWidgetScrollbar::InvalidateDimensionCache();
03263 
03264   Window *w;
03265   FOR_ALL_WINDOWS_FROM_BACK(w) {
03266     w->ReInit();
03267   }
03268 #ifdef ENABLE_NETWORK
03269   void NetworkReInitChatBoxSize();
03270   NetworkReInitChatBoxSize();
03271 #endif
03272 
03273   /* Make sure essential parts of all windows are visible */
03274   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
03275   MarkWholeScreenDirty();
03276 }
03277 
03285 static int PositionWindow(Window *w, WindowClass clss, int setting)
03286 {
03287   if (w == NULL || w->window_class != clss) {
03288     w = FindWindowById(clss, 0);
03289   }
03290   if (w == NULL) return 0;
03291 
03292   int old_left = w->left;
03293   switch (setting) {
03294     case 1:  w->left = (_screen.width - w->width) / 2; break;
03295     case 2:  w->left = _screen.width - w->width; break;
03296     default: w->left = 0; break;
03297   }
03298   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
03299   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
03300   return w->left;
03301 }
03302 
03308 int PositionMainToolbar(Window *w)
03309 {
03310   DEBUG(misc, 5, "Repositioning Main Toolbar...");
03311   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
03312 }
03313 
03319 int PositionStatusbar(Window *w)
03320 {
03321   DEBUG(misc, 5, "Repositioning statusbar...");
03322   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
03323 }
03324 
03330 int PositionNewsMessage(Window *w)
03331 {
03332   DEBUG(misc, 5, "Repositioning news message...");
03333   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
03334 }
03335 
03341 int PositionNetworkChatWindow(Window *w)
03342 {
03343   DEBUG(misc, 5, "Repositioning network chat window...");
03344   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
03345 }
03346 
03347 
03353 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
03354 {
03355   Window *w;
03356   FOR_ALL_WINDOWS_FROM_BACK(w) {
03357     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
03358       w->viewport->follow_vehicle = to_index;
03359       w->SetDirty();
03360     }
03361   }
03362 }
03363 
03364 
03370 void RelocateAllWindows(int neww, int newh)
03371 {
03372   Window *w;
03373 
03374   FOR_ALL_WINDOWS_FROM_BACK(w) {
03375     int left, top;
03376     /* XXX - this probably needs something more sane. For example specifying
03377      * in a 'backup'-desc that the window should always be centered. */
03378     switch (w->window_class) {
03379       case WC_MAIN_WINDOW:
03380       case WC_BOOTSTRAP:
03381         ResizeWindow(w, neww, newh);
03382         continue;
03383 
03384       case WC_MAIN_TOOLBAR:
03385         ResizeWindow(w, min(neww, w->window_desc->default_width) - w->width, 0, false);
03386 
03387         top = w->top;
03388         left = PositionMainToolbar(w); // changes toolbar orientation
03389         break;
03390 
03391       case WC_NEWS_WINDOW:
03392         top = newh - w->height;
03393         left = PositionNewsMessage(w);
03394         break;
03395 
03396       case WC_STATUS_BAR:
03397         ResizeWindow(w, min(neww, w->window_desc->default_width) - w->width, 0, false);
03398 
03399         top = newh - w->height;
03400         left = PositionStatusbar(w);
03401         break;
03402 
03403       case WC_SEND_NETWORK_MSG:
03404         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
03405         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
03406         left = PositionNetworkChatWindow(w);
03407         break;
03408 
03409       case WC_CONSOLE:
03410         IConsoleResize(w);
03411         continue;
03412 
03413       default: {
03414         if (w->flags & WF_CENTERED) {
03415           top = (newh - w->height) >> 1;
03416           left = (neww - w->width) >> 1;
03417           break;
03418         }
03419 
03420         left = w->left;
03421         if (left + (w->width >> 1) >= neww) left = neww - w->width;
03422         if (left < 0) left = 0;
03423 
03424         top = w->top;
03425         if (top + (w->height >> 1) >= newh) top = newh - w->height;
03426         break;
03427       }
03428     }
03429 
03430     EnsureVisibleCaption(w, left, top);
03431   }
03432 }
03433 
03439 PickerWindowBase::~PickerWindowBase()
03440 {
03441   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
03442   ResetObjectToPlace();
03443 }