window.cpp

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