# window.cpp

Go to the documentation of this file.

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