# 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 "newgrf_debug.h"
    00031 #include "hotkeys.h"
    00032 #include "toolbar_gui.h"
    00033 #include "statusbar_gui.h"
    00034 #include "error.h"
    00035 #include "game/game.hpp"
    00036
    00038 enum ViewportAutoscrolling {
    00039   VA_DISABLED,
    00040   VA_MAIN_VIEWPORT_FULLSCREEN,
    00041   VA_MAIN_VIEWPORT,
    00042   VA_EVERY_VIEWPORT,
    00043 };
    00044
    00045 static Point _drag_delta;
    00046 static Window *_mouseover_last_w = NULL;
    00047 static Window *_last_scroll_window = NULL;
    00048
    00050 Window *_z_front_window = NULL;
    00052 Window *_z_back_window  = NULL;
    00053
    00055 bool _window_highlight_colour = false;
    00056
    00057 /*
    00058  * Window that currently has focus. - The main purpose is to generate
    00059  * #FocusLost events, not to give next window in z-order focus when a
    00060  * window is closed.
    00061  */
    00062 Window *_focused_window;
    00063
    00064 Point _cursorpos_drag_start;
    00065
    00066 int _scrollbar_start_pos;
    00067 int _scrollbar_size;
    00068 byte _scroller_click_timeout = 0;
    00069
    00070 bool _scrolling_viewport;
    00071 bool _mouse_hovering;
    00072
    00073 SpecialMouseMode _special_mouse_mode;
    00074
    00076 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
    00077       WindowClass window_class, WindowClass parent_class, uint32 flags,
    00078       const NWidgetPart *nwid_parts, int16 nwid_length) :
    00079   default_pos(def_pos),
    00080   default_width(def_width),
    00081   default_height(def_height),
    00082   cls(window_class),
    00083   parent_cls(parent_class),
    00084   flags(flags),
    00085   nwid_parts(nwid_parts),
    00086   nwid_length(nwid_length)
    00087 {
    00088 }
    00089
    00090 WindowDesc::~WindowDesc()
    00091 {
    00092 }
    00093
    00103 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
    00104 {
    00105   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
    00106   if (line_height < 0) line_height = wid->resize_y;
    00107   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
    00108   return (clickpos - (int)wid->pos_y - padding) / line_height;
    00109 }
    00110
    00114 void Window::DisableAllWidgetHighlight()
    00115 {
    00116   for (uint i = 0; i < this->nested_array_size; i++) {
    00117     NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
    00118     if (nwid == NULL) continue;
    00119
    00120     if (nwid->IsHighlighted()) {
    00121       nwid->SetHighlighted(TC_INVALID);
    00122       this->SetWidgetDirty(i);
    00123     }
    00124   }
    00125
    00126   CLRBITS(this->flags, WF_HIGHLIGHTED);
    00127 }
    00128
    00134 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
    00135 {
    00136   assert(widget_index < this->nested_array_size);
    00137
    00138   NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
    00139   if (nwid == NULL) return;
    00140
    00141   nwid->SetHighlighted(highlighted_colour);
    00142   this->SetWidgetDirty(widget_index);
    00143
    00144   if (highlighted_colour != TC_INVALID) {
    00145     /* If we set a highlight, the window has a highlight */
    00146     this->flags |= WF_HIGHLIGHTED;
    00147   } else {
    00148     /* If we disable a highlight, check all widgets if anyone still has a highlight */
    00149     bool valid = false;
    00150     for (uint i = 0; i < this->nested_array_size; i++) {
    00151       NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
    00152       if (nwid == NULL) continue;
    00153       if (!nwid->IsHighlighted()) continue;
    00154
    00155       valid = true;
    00156     }
    00157     /* If nobody has a highlight, disable the flag on the window */
    00158     if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
    00159   }
    00160 }
    00161
    00167 bool Window::IsWidgetHighlighted(byte widget_index) const
    00168 {
    00169   assert(widget_index < this->nested_array_size);
    00170
    00171   const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
    00172   if (nwid == NULL) return false;
    00173
    00174   return nwid->IsHighlighted();
    00175 }
    00176
    00184 void Window::OnDropdownClose(Point pt, int widget, int index, bool instant_close)
    00185 {
    00186   if (widget < 0) return;
    00187
    00188   if (instant_close) {
    00189     /* Send event for selected option if we're still
    00190      * on the parent button of the dropdown (behaviour of the dropdowns in the main toolbar). */
    00191     if (GetWidgetFromPos(this, pt.x, pt.y) == widget) {
    00192       this->OnDropdownSelect(widget, index);
    00193     }
    00194   }
    00195
    00196   /* Raise the dropdown button */
    00197   if (this->nested_array != NULL) {
    00198     NWidgetCore *nwi2 = this->GetWidget<NWidgetCore>(widget);
    00199     if ((nwi2->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
    00200       nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
    00201     } else {
    00202       this->RaiseWidget(widget);
    00203     }
    00204   } else {
    00205     this->RaiseWidget(widget);
    00206   }
    00207   this->SetWidgetDirty(widget);
    00208 }
    00209
    00215 const Scrollbar *Window::GetScrollbar(uint widnum) const
    00216 {
    00217   return this->GetWidget<NWidgetScrollbar>(widnum);
    00218 }
    00219
    00225 Scrollbar *Window::GetScrollbar(uint widnum)
    00226 {
    00227   return this->GetWidget<NWidgetScrollbar>(widnum);
    00228 }
    00229
    00230
    00235 void SetFocusedWindow(Window *w)
    00236 {
    00237   if (_focused_window == w) return;
    00238
    00239   /* Invalidate focused widget */
    00240   if (_focused_window != NULL) {
    00241     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
    00242   }
    00243
    00244   /* Remember which window was previously focused */
    00245   Window *old_focused = _focused_window;
    00246   _focused_window = w;
    00247
    00248   /* So we can inform it that it lost focus */
    00249   if (old_focused != NULL) old_focused->OnFocusLost();
    00250   if (_focused_window != NULL) _focused_window->OnFocus();
    00251 }
    00252
    00258 static bool EditBoxInGlobalFocus()
    00259 {
    00260   if (_focused_window == NULL) return false;
    00261
    00262   /* The console does not have an edit box so a special case is needed. */
    00263   if (_focused_window->window_class == WC_CONSOLE) return true;
    00264
    00265   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
    00266 }
    00267
    00271 void Window::UnfocusFocusedWidget()
    00272 {
    00273   if (this->nested_focus != NULL) {
    00274     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00275     this->nested_focus->SetDirty(this);
    00276     this->nested_focus = NULL;
    00277   }
    00278 }
    00279
    00285 bool Window::SetFocusedWidget(byte widget_index)
    00286 {
    00287   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
    00288   if (widget_index >= this->nested_array_size) return false;
    00289
    00290   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
    00291   if (this->nested_focus != NULL) {
    00292     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
    00293
    00294     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00295     this->nested_focus->SetDirty(this);
    00296   }
    00297   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
    00298   return true;
    00299 }
    00300
    00308 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
    00309 {
    00310   va_list wdg_list;
    00311
    00312   va_start(wdg_list, widgets);
    00313
    00314   while (widgets != WIDGET_LIST_END) {
    00315     SetWidgetDisabledState(widgets, disab_stat);
    00316     widgets = va_arg(wdg_list, int);
    00317   }
    00318
    00319   va_end(wdg_list);
    00320 }
    00321
    00327 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
    00328 {
    00329   va_list wdg_list;
    00330
    00331   va_start(wdg_list, widgets);
    00332
    00333   while (widgets != WIDGET_LIST_END) {
    00334     SetWidgetLoweredState(widgets, lowered_stat);
    00335     widgets = va_arg(wdg_list, int);
    00336   }
    00337
    00338   va_end(wdg_list);
    00339 }
    00340
    00345 void Window::RaiseButtons(bool autoraise)
    00346 {
    00347   for (uint i = 0; i < this->nested_array_size; i++) {
    00348     if (this->nested_array[i] != NULL && ((this->nested_array[i]->type & ~WWB_PUSHBUTTON) < WWT_LAST || this->nested_array[i]->type == NWID_PUSHBUTTON_DROPDOWN) &&
    00349         (!autoraise || (this->nested_array[i]->type & WWB_PUSHBUTTON)) && this->IsWidgetLowered(i)) {
    00350       this->RaiseWidget(i);
    00351       this->SetWidgetDirty(i);
    00352     }
    00353   }
    00354 }
    00355
    00360 void Window::SetWidgetDirty(byte widget_index) const
    00361 {
    00362   /* Sometimes this function is called before the window is even fully initialized */
    00363   if (this->nested_array == NULL) return;
    00364
    00365   this->nested_array[widget_index]->SetDirty(this);
    00366 }
    00367
    00373 void Window::HandleButtonClick(byte widget)
    00374 {
    00375   this->LowerWidget(widget);
    00376   this->SetTimeout();
    00377   this->SetWidgetDirty(widget);
    00378 }
    00379
    00380 static void StartWindowDrag(Window *w);
    00381 static void StartWindowSizing(Window *w, bool to_left);
    00382
    00390 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
    00391 {
    00392   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
    00393   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
    00394
    00395   bool focused_widget_changed = false;
    00396   /* If clicked on a window that previously did dot have focus */
    00397   if (_focused_window != w &&                 // We already have focus, right?
    00398       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
    00399       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
    00400     focused_widget_changed = true;
    00401     if (_focused_window != NULL) {
    00402       _focused_window->OnFocusLost();
    00403
    00404       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
    00405       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
    00406     }
    00407     SetFocusedWindow(w);
    00408     w->OnFocus();
    00409   }
    00410
    00411   if (nw == NULL) return; // exit if clicked outside of widgets
    00412
    00413   /* don't allow any interaction if the button has been disabled */
    00414   if (nw->IsDisabled()) return;
    00415
    00416   int widget_index = nw->index;
    00417
    00418   /* Clicked on a widget that is not disabled.
    00419    * So unless the clicked widget is the caption bar, change focus to this widget */
    00420   if (widget_type != WWT_CAPTION) {
    00421     /* Close the OSK window if a edit box loses focus */
    00422     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
    00423       DeleteWindowById(WC_OSK, 0);
    00424     }
    00425
    00426     /* focused_widget_changed is 'now' only true if the window this widget
    00427      * is in gained focus. In that case it must remain true, also if the
    00428      * local widget focus did not change. As such it's the logical-or of
    00429      * both changed states.
    00430      *
    00431      * If this is not preserved, then the OSK window would be opened when
    00432      * a user has the edit box focused and then click on another window and
    00433      * then back again on the edit box (to type some text).
    00434      */
    00435     focused_widget_changed |= w->SetFocusedWidget(widget_index);
    00436   }
    00437
    00438   /* Close any child drop down menus. If the button pressed was the drop down
    00439    * list's own button, then we should not process the click any further. */
    00440   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
    00441
    00442   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
    00443
    00444   switch (widget_type) {
    00445     case NWID_VSCROLLBAR:
    00446     case NWID_HSCROLLBAR:
    00447       ScrollbarClickHandler(w, nw, x, y);
    00448       break;
    00449
    00450     case WWT_EDITBOX:
    00451       if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
    00452         /* Open the OSK window if clicked on an edit box */
    00453         QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
    00454         if (qs != NULL) {
    00455           qs->OnOpenOSKWindow(widget_index);
    00456         }
    00457       }
    00458       break;
    00459
    00460     case WWT_CLOSEBOX: // 'X'
    00461       delete w;
    00462       return;
    00463
    00464     case WWT_CAPTION: // 'Title bar'
    00465       StartWindowDrag(w);
    00466       return;
    00467
    00468     case WWT_RESIZEBOX:
    00469       /* When the resize widget is on the left size of the window
    00470        * we assume that that button is used to resize to the left. */
    00471       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
    00472       nw->SetDirty(w);
    00473       return;
    00474
    00475     case WWT_DEBUGBOX:
    00476       w->ShowNewGRFInspectWindow();
    00477       break;
    00478
    00479     case WWT_SHADEBOX:
    00480       nw->SetDirty(w);
    00481       w->SetShaded(!w->IsShaded());
    00482       return;
    00483
    00484     case WWT_STICKYBOX:
    00485       w->flags ^= WF_STICKY;
    00486       nw->SetDirty(w);
    00487       return;
    00488
    00489     default:
    00490       break;
    00491   }
    00492
    00493   /* Widget has no index, so the window is not interested in it. */
    00494   if (widget_index < 0) return;
    00495
    00496   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
    00497   if (w->IsWidgetHighlighted(widget_index)) {
    00498     w->SetWidgetHighlight(widget_index, TC_INVALID);
    00499     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
    00500   }
    00501
    00502   Point pt = { x, y };
    00503   w->OnClick(pt, widget_index, click_count);
    00504 }
    00505
    00512 static void DispatchRightClickEvent(Window *w, int x, int y)
    00513 {
    00514   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00515   if (wid == NULL) return;
    00516
    00517   /* No widget to handle, or the window is not interested in it. */
    00518   if (wid->index >= 0) {
    00519     Point pt = { x, y };
    00520     if (w->OnRightClick(pt, wid->index)) return;
    00521   }
    00522
    00523   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
    00524 }
    00525
    00532 static void DispatchHoverEvent(Window *w, int x, int y)
    00533 {
    00534   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00535
    00536   /* No widget to handle */
    00537   if (wid == NULL) return;
    00538
    00539   /* Show the tooltip if there is any */
    00540   if (wid->tool_tip != 0) {
    00541     GuiShowTooltips(w, wid->tool_tip);
    00542     return;
    00543   }
    00544
    00545   /* Widget has no index, so the window is not interested in it. */
    00546   if (wid->index < 0) return;
    00547
    00548   Point pt = { x, y };
    00549   w->OnHover(pt, wid->index);
    00550 }
    00551
    00559 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
    00560 {
    00561   if (nwid == NULL) return;
    00562
    00563   /* Using wheel on caption/shade-box shades or unshades the window. */
    00564   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
    00565     w->SetShaded(wheel < 0);
    00566     return;
    00567   }
    00568
    00569   /* Wheeling a vertical scrollbar. */
    00570   if (nwid->type == NWID_VSCROLLBAR) {
    00571     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
    00572     if (sb->GetCount() > sb->GetCapacity()) {
    00573       sb->UpdatePosition(wheel);
    00574       w->SetDirty();
    00575     }
    00576     return;
    00577   }
    00578
    00579   /* Scroll the widget attached to the scrollbar. */
    00580   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
    00581   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
    00582     sb->UpdatePosition(wheel);
    00583     w->SetDirty();
    00584   }
    00585 }
    00586
    00592 static bool MayBeShown(const Window *w)
    00593 {
    00594   /* If we're not modal, everything is okay. */
    00595   if (!HasModalProgress()) return true;
    00596
    00597   switch (w->window_class) {
    00598     case WC_MAIN_WINDOW:
    00599     case WC_MODAL_PROGRESS:
    00600     case WC_CONFIRM_POPUP_QUERY:
    00601       return true;
    00602
    00603     default:
    00604       return false;
    00605   }
    00606 }
    00607
    00620 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
    00621 {
    00622   const Window *v;
    00623   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
    00624     if (MayBeShown(v) &&
    00625         right > v->left &&
    00626         bottom > v->top &&
    00627         left < v->left + v->width &&
    00628         top < v->top + v->height) {
    00629       /* v and rectangle intersect with each other */
    00630       int x;
    00631
    00632       if (left < (x = v->left)) {
    00633         DrawOverlappedWindow(w, left, top, x, bottom);
    00634         DrawOverlappedWindow(w, x, top, right, bottom);
    00635         return;
    00636       }
    00637
    00638       if (right > (x = v->left + v->width)) {
    00639         DrawOverlappedWindow(w, left, top, x, bottom);
    00640         DrawOverlappedWindow(w, x, top, right, bottom);
    00641         return;
    00642       }
    00643
    00644       if (top < (x = v->top)) {
    00645         DrawOverlappedWindow(w, left, top, right, x);
    00646         DrawOverlappedWindow(w, left, x, right, bottom);
    00647         return;
    00648       }
    00649
    00650       if (bottom > (x = v->top + v->height)) {
    00651         DrawOverlappedWindow(w, left, top, right, x);
    00652         DrawOverlappedWindow(w, left, x, right, bottom);
    00653         return;
    00654       }
    00655
    00656       return;
    00657     }
    00658   }
    00659
    00660   /* Setup blitter, and dispatch a repaint event to window *wz */
    00661   DrawPixelInfo *dp = _cur_dpi;
    00662   dp->width = right - left;
    00663   dp->height = bottom - top;
    00664   dp->left = left - w->left;
    00665   dp->top = top - w->top;
    00666   dp->pitch = _screen.pitch;
    00667   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
    00668   dp->zoom = ZOOM_LVL_NORMAL;
    00669   w->OnPaint();
    00670 }
    00671
    00680 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
    00681 {
    00682   Window *w;
    00683   DrawPixelInfo bk;
    00684   _cur_dpi = &bk;
    00685
    00686   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00687     if (MayBeShown(w) &&
    00688         right > w->left &&
    00689         bottom > w->top &&
    00690         left < w->left + w->width &&
    00691         top < w->top + w->height) {
    00692       /* Window w intersects with the rectangle => needs repaint */
    00693       DrawOverlappedWindow(w, left, top, right, bottom);
    00694     }
    00695   }
    00696 }
    00697
    00702 void Window::SetDirty() const
    00703 {
    00704   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
    00705 }
    00706
    00713 void Window::ReInit(int rx, int ry)
    00714 {
    00715   this->SetDirty(); // Mark whole current window as dirty.
    00716
    00717   /* Save current size. */
    00718   int window_width  = this->width;
    00719   int window_height = this->height;
    00720
    00721   this->OnInit();
    00722   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
    00723   this->nested_root->SetupSmallestSize(this, false);
    00724   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
    00725   this->width  = this->nested_root->smallest_x;
    00726   this->height = this->nested_root->smallest_y;
    00727   this->resize.step_width  = this->nested_root->resize_x;
    00728   this->resize.step_height = this->nested_root->resize_y;
    00729
    00730   /* Resize as close to the original size + requested resize as possible. */
    00731   window_width  = max(window_width  + rx, this->width);
    00732   window_height = max(window_height + ry, this->height);
    00733   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
    00734   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
    00735   /* dx and dy has to go by step.. calculate it.
    00736    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
    00737   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
    00738   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
    00739
    00740   ResizeWindow(this, dx, dy);
    00741   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
    00742 }
    00743
    00749 void Window::SetShaded(bool make_shaded)
    00750 {
    00751   if (this->shade_select == NULL) return;
    00752
    00753   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
    00754   if (this->shade_select->shown_plane != desired) {
    00755     if (make_shaded) {
    00756       this->unshaded_size.width  = this->width;
    00757       this->unshaded_size.height = this->height;
    00758       this->shade_select->SetDisplayedPlane(desired);
    00759       this->ReInit(0, -this->height);
    00760     } else {
    00761       this->shade_select->SetDisplayedPlane(desired);
    00762       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
    00763       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
    00764       this->ReInit(dx, dy);
    00765     }
    00766   }
    00767 }
    00768
    00775 static Window *FindChildWindow(const Window *w, WindowClass wc)
    00776 {
    00777   Window *v;
    00778   FOR_ALL_WINDOWS_FROM_BACK(v) {
    00779     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
    00780   }
    00781
    00782   return NULL;
    00783 }
    00784
    00789 void Window::DeleteChildWindows(WindowClass wc) const
    00790 {
    00791   Window *child = FindChildWindow(this, wc);
    00792   while (child != NULL) {
    00793     delete child;
    00794     child = FindChildWindow(this, wc);
    00795   }
    00796 }
    00797
    00801 Window::~Window()
    00802 {
    00803   if (_thd.window_class == this->window_class &&
    00804       _thd.window_number == this->window_number) {
    00805     ResetObjectToPlace();
    00806   }
    00807
    00808   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
    00809   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
    00810
    00811   /* We can't scroll the window when it's closed. */
    00812   if (_last_scroll_window == this) _last_scroll_window = NULL;
    00813
    00814   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
    00815   if (_focused_window == this) _focused_window = NULL;
    00816
    00817   this->DeleteChildWindows();
    00818
    00819   if (this->viewport != NULL) DeleteWindowViewport(this);
    00820
    00821   this->SetDirty();
    00822
    00823   free(this->nested_array); // Contents is released through deletion of #nested_root.
    00824   delete this->nested_root;
    00825
    00826   this->window_class = WC_INVALID;
    00827 }
    00828
    00835 Window *FindWindowById(WindowClass cls, WindowNumber number)
    00836 {
    00837   Window *w;
    00838   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00839     if (w->window_class == cls && w->window_number == number) return w;
    00840   }
    00841
    00842   return NULL;
    00843 }
    00844
    00851 Window *FindWindowByClass(WindowClass cls)
    00852 {
    00853   Window *w;
    00854   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00855     if (w->window_class == cls) return w;
    00856   }
    00857
    00858   return NULL;
    00859 }
    00860
    00867 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
    00868 {
    00869   Window *w = FindWindowById(cls, number);
    00870   if (force || w == NULL ||
    00871       (w->flags & WF_STICKY) == 0) {
    00872     delete w;
    00873   }
    00874 }
    00875
    00880 void DeleteWindowByClass(WindowClass cls)
    00881 {
    00882   Window *w;
    00883
    00884 restart_search:
    00885   /* When we find the window to delete, we need to restart the search
    00886    * as deleting this window could cascade in deleting (many) others
    00887    * anywhere in the z-array */
    00888   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00889     if (w->window_class == cls) {
    00890       delete w;
    00891       goto restart_search;
    00892     }
    00893   }
    00894 }
    00895
    00902 void DeleteCompanyWindows(CompanyID id)
    00903 {
    00904   Window *w;
    00905
    00906 restart_search:
    00907   /* When we find the window to delete, we need to restart the search
    00908    * as deleting this window could cascade in deleting (many) others
    00909    * anywhere in the z-array */
    00910   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00911     if (w->owner == id) {
    00912       delete w;
    00913       goto restart_search;
    00914     }
    00915   }
    00916
    00917   /* Also delete the company specific windows that don't have a company-colour. */
    00918   DeleteWindowById(WC_BUY_COMPANY, id);
    00919 }
    00920
    00928 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
    00929 {
    00930   Window *w;
    00931   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00932     if (w->owner != old_owner) continue;
    00933
    00934     switch (w->window_class) {
    00935       case WC_COMPANY_COLOUR:
    00936       case WC_FINANCES:
    00937       case WC_STATION_LIST:
    00938       case WC_TRAINS_LIST:
    00939       case WC_ROADVEH_LIST:
    00940       case WC_SHIPS_LIST:
    00941       case WC_AIRCRAFT_LIST:
    00942       case WC_BUY_COMPANY:
    00943       case WC_COMPANY:
    00944       case WC_COMPANY_INFRASTRUCTURE:
    00945         continue;
    00946
    00947       default:
    00948         w->owner = new_owner;
    00949         break;
    00950     }
    00951   }
    00952 }
    00953
    00954 static void BringWindowToFront(Window *w);
    00955
    00963 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
    00964 {
    00965   Window *w = FindWindowById(cls, number);
    00966
    00967   if (w != NULL) {
    00968     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
    00969
    00970     w->SetWhiteBorder();
    00971     BringWindowToFront(w);
    00972     w->SetDirty();
    00973   }
    00974
    00975   return w;
    00976 }
    00977
    00978 static inline bool IsVitalWindow(const Window *w)
    00979 {
    00980   switch (w->window_class) {
    00981     case WC_MAIN_TOOLBAR:
    00982     case WC_STATUS_BAR:
    00983     case WC_NEWS_WINDOW:
    00984     case WC_SEND_NETWORK_MSG:
    00985       return true;
    00986
    00987     default:
    00988       return false;
    00989   }
    00990 }
    00991
    01000 static uint GetWindowZPriority(const Window *w)
    01001 {
    01002   assert(w->window_class != WC_INVALID);
    01003
    01004   uint z_priority = 0;
    01005
    01006   switch (w->window_class) {
    01007     case WC_ENDSCREEN:
    01008       ++z_priority;
    01009
    01010     case WC_HIGHSCORE:
    01011       ++z_priority;
    01012
    01013     case WC_TOOLTIPS:
    01014       ++z_priority;
    01015
    01016     case WC_DROPDOWN_MENU:
    01017       ++z_priority;
    01018
    01019     case WC_MAIN_TOOLBAR:
    01020     case WC_STATUS_BAR:
    01021       ++z_priority;
    01022
    01023     case WC_OSK:
    01024       ++z_priority;
    01025
    01026     case WC_QUERY_STRING:
    01027     case WC_SEND_NETWORK_MSG:
    01028       ++z_priority;
    01029
    01030     case WC_ERRMSG:
    01031     case WC_CONFIRM_POPUP_QUERY:
    01032     case WC_MODAL_PROGRESS:
    01033     case WC_NETWORK_STATUS_WINDOW:
    01034       ++z_priority;
    01035
    01036     case WC_GENERATE_LANDSCAPE:
    01037     case WC_SAVELOAD:
    01038     case WC_GAME_OPTIONS:
    01039     case WC_CUSTOM_CURRENCY:
    01040     case WC_NETWORK_WINDOW:
    01041     case WC_GRF_PARAMETERS:
    01042     case WC_AI_LIST:
    01043     case WC_AI_SETTINGS:
    01044     case WC_TEXTFILE:
    01045       ++z_priority;
    01046
    01047     case WC_CONSOLE:
    01048       ++z_priority;
    01049
    01050     case WC_NEWS_WINDOW:
    01051       ++z_priority;
    01052
    01053     default:
    01054       ++z_priority;
    01055
    01056     case WC_MAIN_WINDOW:
    01057       return z_priority;
    01058   }
    01059 }
    01060
    01065 static void AddWindowToZOrdering(Window *w)
    01066 {
    01067   assert(w->z_front == NULL && w->z_back == NULL);
    01068
    01069   if (_z_front_window == NULL) {
    01070     /* It's the only window. */
    01071     _z_front_window = _z_back_window = w;
    01072     w->z_front = w->z_back = NULL;
    01073   } else {
    01074     /* Search down the z-ordering for its location. */
    01075     Window *v = _z_front_window;
    01076     uint last_z_priority = UINT_MAX;
    01077     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
    01078       if (v->window_class != WC_INVALID) {
    01079         /* Sanity check z-ordering, while we're at it. */
    01080         assert(last_z_priority >= GetWindowZPriority(v));
    01081         last_z_priority = GetWindowZPriority(v);
    01082       }
    01083
    01084       v = v->z_back;
    01085     }
    01086
    01087     if (v == NULL) {
    01088       /* It's the new back window. */
    01089       w->z_front = _z_back_window;
    01090       w->z_back = NULL;
    01091       _z_back_window->z_back = w;
    01092       _z_back_window = w;
    01093     } else if (v == _z_front_window) {
    01094       /* It's the new front window. */
    01095       w->z_front = NULL;
    01096       w->z_back = _z_front_window;
    01097       _z_front_window->z_front = w;
    01098       _z_front_window = w;
    01099     } else {
    01100       /* It's somewhere else in the z-ordering. */
    01101       w->z_front = v->z_front;
    01102       w->z_back = v;
    01103       v->z_front->z_back = w;
    01104       v->z_front = w;
    01105     }
    01106   }
    01107 }
    01108
    01109
    01114 static void RemoveWindowFromZOrdering(Window *w)
    01115 {
    01116   if (w->z_front == NULL) {
    01117     assert(_z_front_window == w);
    01118     _z_front_window = w->z_back;
    01119   } else {
    01120     w->z_front->z_back = w->z_back;
    01121   }
    01122
    01123   if (w->z_back == NULL) {
    01124     assert(_z_back_window == w);
    01125     _z_back_window = w->z_front;
    01126   } else {
    01127     w->z_back->z_front = w->z_front;
    01128   }
    01129
    01130   w->z_front = w->z_back = NULL;
    01131 }
    01132
    01138 static void BringWindowToFront(Window *w)
    01139 {
    01140   RemoveWindowFromZOrdering(w);
    01141   AddWindowToZOrdering(w);
    01142
    01143   w->SetDirty();
    01144 }
    01145
    01154 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
    01155 {
    01156   /* Set up window properties; some of them are needed to set up smallest size below */
    01157   this->window_class = desc->cls;
    01158   this->SetWhiteBorder();
    01159   if (desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
    01160   this->owner = INVALID_OWNER;
    01161   this->nested_focus = NULL;
    01162   this->window_number = window_number;
    01163   this->desc_flags = desc->flags;
    01164
    01165   this->OnInit();
    01166   /* Initialize nested widget tree. */
    01167   if (this->nested_array == NULL) {
    01168     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01169     this->nested_root->SetupSmallestSize(this, true);
    01170   } else {
    01171     this->nested_root->SetupSmallestSize(this, false);
    01172   }
    01173   /* Initialize to smallest size. */
    01174   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
    01175
    01176   /* Further set up window properties,
    01177    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
    01178   this->resize.step_width  = this->nested_root->resize_x;
    01179   this->resize.step_height = this->nested_root->resize_y;
    01180
    01181   /* Give focus to the opened window unless it is the OSK window or a text box
    01182    * of focused window has focus (so we don't interrupt typing). But if the new
    01183    * window has a text box, then take focus anyway. */
    01184   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
    01185
    01186   /* Insert the window into the correct location in the z-ordering. */
    01187   AddWindowToZOrdering(this);
    01188 }
    01189
    01197 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
    01198 {
    01199   this->left = x;
    01200   this->top = y;
    01201   this->width = sm_width;
    01202   this->height = sm_height;
    01203 }
    01204
    01215 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
    01216 {
    01217   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
    01218   def_height = max(def_height, this->height);
    01219   /* Try to make windows smaller when our window is too small.
    01220    * w->(width|height) is normally the same as min_(width|height),
    01221    * but this way the GUIs can be made a little more dynamic;
    01222    * one can use the same spec for multiple windows and those
    01223    * can then determine the real minimum size of the window. */
    01224   if (this->width != def_width || this->height != def_height) {
    01225     /* Think about the overlapping toolbars when determining the minimum window size */
    01226     int free_height = _screen.height;
    01227     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
    01228     if (wt != NULL) free_height -= wt->height;
    01229     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01230     if (wt != NULL) free_height -= wt->height;
    01231
    01232     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
    01233     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
    01234
    01235     /* X and Y has to go by step.. calculate it.
    01236      * The cast to int is necessary else x/y are implicitly casted to
    01237      * unsigned int, which won't work. */
    01238     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
    01239     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
    01240
    01241     ResizeWindow(this, enlarge_x, enlarge_y);
    01242     /* ResizeWindow() calls this->OnResize(). */
    01243   } else {
    01244     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
    01245     this->OnResize();
    01246   }
    01247
    01248   int nx = this->left;
    01249   int ny = this->top;
    01250
    01251   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
    01252
    01253   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01254   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
    01255   nx = max(nx, 0);
    01256
    01257   if (this->viewport != NULL) {
    01258     this->viewport->left += nx - this->left;
    01259     this->viewport->top  += ny - this->top;
    01260   }
    01261   this->left = nx;
    01262   this->top = ny;
    01263
    01264   this->SetDirty();
    01265 }
    01266
    01278 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
    01279 {
    01280   int right  = width + left;
    01281   int bottom = height + top;
    01282
    01283   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
    01284   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
    01285
    01286   /* Make sure it is not obscured by any window. */
    01287   const Window *w;
    01288   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01289     if (w->window_class == WC_MAIN_WINDOW) continue;
    01290
    01291     if (right > w->left &&
    01292         w->left + w->width > left &&
    01293         bottom > w->top &&
    01294         w->top + w->height > top) {
    01295       return false;
    01296     }
    01297   }
    01298
    01299   pos.x = left;
    01300   pos.y = top;
    01301   return true;
    01302 }
    01303
    01315 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
    01316 {
    01317   /* Left part of the rectangle may be at most 1/4 off-screen,
    01318    * right part of the rectangle may be at most 1/2 off-screen
    01319    */
    01320   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
    01321   /* Bottom part of the rectangle may be at most 1/4 off-screen */
    01322   if (top < 22 || top > _screen.height - (height >> 2)) return false;
    01323
    01324   /* Make sure it is not obscured by any window. */
    01325   const Window *w;
    01326   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01327     if (w->window_class == WC_MAIN_WINDOW) continue;
    01328
    01329     if (left + width > w->left &&
    01330         w->left + w->width > left &&
    01331         top + height > w->top &&
    01332         w->top + w->height > top) {
    01333       return false;
    01334     }
    01335   }
    01336
    01337   pos.x = left;
    01338   pos.y = top;
    01339   return true;
    01340 }
    01341
    01348 static Point GetAutoPlacePosition(int width, int height)
    01349 {
    01350   Point pt;
    01351
    01352   /* First attempt, try top-left of the screen */
    01353   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
    01354   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
    01355
    01356   /* Second attempt, try around all existing windows with a distance of 2 pixels.
    01357    * The new window must be entirely on-screen, and not overlap with an existing window.
    01358    * Eight starting points are tried, two at each corner.
    01359    */
    01360   const Window *w;
    01361   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01362     if (w->window_class == WC_MAIN_WINDOW) continue;
    01363
    01364     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01365     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01366     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01367     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01368     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
    01369     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
    01370     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
    01371     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
    01372   }
    01373
    01374   /* Third attempt, try around all existing windows with a distance of 2 pixels.
    01375    * The new window may be partly off-screen, and must not overlap with an existing window.
    01376    * Only four starting points are tried.
    01377    */
    01378   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01379     if (w->window_class == WC_MAIN_WINDOW) continue;
    01380
    01381     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01382     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01383     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01384     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01385   }
    01386
    01387   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
    01388    * of (+5, +5)
    01389    */
    01390   int left = 0, top = 24;
    01391
    01392 restart:
    01393   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01394     if (w->left == left && w->top == top) {
    01395       left += 5;
    01396       top += 5;
    01397       goto restart;
    01398     }
    01399   }
    01400
    01401   pt.x = left;
    01402   pt.y = top;
    01403   return pt;
    01404 }
    01405
    01412 Point GetToolbarAlignedWindowPosition(int window_width)
    01413 {
    01414   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01415   assert(w != NULL);
    01416   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
    01417   return pt;
    01418 }
    01419
    01437 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01438 {
    01439   Point pt;
    01440   const Window *w;
    01441
    01442   int16 default_width  = max(desc->default_width,  sm_width);
    01443   int16 default_height = max(desc->default_height, sm_height);
    01444
    01445   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
    01446       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
    01447       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
    01448
    01449     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
    01450     if (pt.x > _screen.width + 10 - default_width) {
    01451       pt.x = (_screen.width + 10 - default_width) - 20;
    01452     }
    01453     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
    01454     return pt;
    01455   }
    01456
    01457   switch (desc->default_pos) {
    01458     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
    01459       return GetToolbarAlignedWindowPosition(default_width);
    01460
    01461     case WDP_AUTO: // Find a good automatic position for the window
    01462       return GetAutoPlacePosition(default_width, default_height);
    01463
    01464     case WDP_CENTER: // Centre the window horizontally
    01465       pt.x = (_screen.width - default_width) / 2;
    01466       pt.y = (_screen.height - default_height) / 2;
    01467       break;
    01468
    01469     case WDP_MANUAL:
    01470       pt.x = 0;
    01471       pt.y = 0;
    01472       break;
    01473
    01474     default:
    01475       NOT_REACHED();
    01476   }
    01477
    01478   return pt;
    01479 }
    01480
    01481 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01482 {
    01483   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
    01484 }
    01485
    01494 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
    01495 {
    01496   int biggest_index = -1;
    01497   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
    01498   this->nested_array_size = (uint)(biggest_index + 1);
    01499
    01500   if (fill_nested) {
    01501     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01502     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
    01503   }
    01504 }
    01505
    01511 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
    01512 {
    01513   this->InitializeData(desc, window_number);
    01514   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
    01515   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
    01516   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
    01517 }
    01518
    01524 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
    01525 {
    01526   this->CreateNestedTree(desc, false);
    01527   this->FinishInitNested(desc, window_number);
    01528 }
    01529
    01531 Window::Window() : scrolling_scrollbar(-1)
    01532 {
    01533 }
    01534
    01542 Window *FindWindowFromPt(int x, int y)
    01543 {
    01544   Window *w;
    01545   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01546     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
    01547       return w;
    01548     }
    01549   }
    01550
    01551   return NULL;
    01552 }
    01553
    01557 void InitWindowSystem()
    01558 {
    01559   IConsoleClose();
    01560
    01561   _z_back_window = NULL;
    01562   _z_front_window = NULL;
    01563   _focused_window = NULL;
    01564   _mouseover_last_w = NULL;
    01565   _last_scroll_window = NULL;
    01566   _scrolling_viewport = false;
    01567   _mouse_hovering = false;
    01568
    01569   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    01570   NWidgetScrollbar::InvalidateDimensionCache();
    01571
    01572   ShowFirstError();
    01573 }
    01574
    01578 void UnInitWindowSystem()
    01579 {
    01580   UnshowCriticalError();
    01581
    01582   Window *w;
    01583   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
    01584
    01585   for (w = _z_front_window; w != NULL; /* nothing */) {
    01586     Window *to_del = w;
    01587     w = w->z_back;
    01588     free(to_del);
    01589   }
    01590
    01591   _z_front_window = NULL;
    01592   _z_back_window = NULL;
    01593 }
    01594
    01598 void ResetWindowSystem()
    01599 {
    01600   UnInitWindowSystem();
    01601   InitWindowSystem();
    01602   _thd.Reset();
    01603 }
    01604
    01605 static void DecreaseWindowCounters()
    01606 {
    01607   Window *w;
    01608   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01609     if (_scroller_click_timeout == 0) {
    01610       /* Unclick scrollbar buttons if they are pressed. */
    01611       for (uint i = 0; i < w->nested_array_size; i++) {
    01612         NWidgetBase *nwid = w->nested_array[i];
    01613         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
    01614           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
    01615           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
    01616             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
    01617             w->scrolling_scrollbar = -1;
    01618             sb->SetDirty(w);
    01619           }
    01620         }
    01621       }
    01622     }
    01623     w->OnMouseLoop();
    01624   }
    01625
    01626   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01627     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
    01628       CLRBITS(w->flags, WF_TIMEOUT);
    01629
    01630       w->OnTimeout();
    01631       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
    01632     }
    01633   }
    01634 }
    01635
    01636 static void HandlePlacePresize()
    01637 {
    01638   if (_special_mouse_mode != WSM_PRESIZE) return;
    01639
    01640   Window *w = _thd.GetCallbackWnd();
    01641   if (w == NULL) return;
    01642
    01643   Point pt = GetTileBelowCursor();
    01644   if (pt.x == -1) {
    01645     _thd.selend.x = -1;
    01646     return;
    01647   }
    01648
    01649   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
    01650 }
    01651
    01656 static EventState HandleMouseDragDrop()
    01657 {
    01658   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
    01659
    01660   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
    01661
    01662   Window *w = _thd.GetCallbackWnd();
    01663   if (w != NULL) {
    01664     /* Send an event in client coordinates. */
    01665     Point pt;
    01666     pt.x = _cursor.pos.x - w->left;
    01667     pt.y = _cursor.pos.y - w->top;
    01668     if (_left_button_down) {
    01669       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01670     } else {
    01671       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01672     }
    01673   }
    01674
    01675   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
    01676   return ES_HANDLED;
    01677 }
    01678
    01680 static void HandleMouseOver()
    01681 {
    01682   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    01683
    01684   /* We changed window, put a MOUSEOVER event to the last window */
    01685   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
    01686     /* Reset mouse-over coordinates of previous window */
    01687     Point pt = { -1, -1 };
    01688     _mouseover_last_w->OnMouseOver(pt, 0);
    01689   }
    01690
    01691   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
    01692   _mouseover_last_w = w;
    01693
    01694   if (w != NULL) {
    01695     /* send an event in client coordinates. */
    01696     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
    01697     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
    01698     if (widget != NULL) w->OnMouseOver(pt, widget->index);
    01699   }
    01700 }
    01701
    01703 static const int MIN_VISIBLE_TITLE_BAR = 13;
    01704
    01706 enum PreventHideDirection {
    01707   PHD_UP,
    01708   PHD_DOWN,
    01709 };
    01710
    01721 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
    01722 {
    01723   if (v == NULL) return;
    01724
    01725   int v_bottom = v->top + v->height;
    01726   int v_right = v->left + v->width;
    01727   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.
    01728
    01729   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
    01730   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
    01731
    01732   /* Vertically, the rectangle is hidden behind v. */
    01733   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
    01734     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
    01735     return;
    01736   }
    01737   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
    01738     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
    01739     return;
    01740   }
    01741
    01742   /* Horizontally also hidden, force movement to a safe area. */
    01743   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
    01744     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
    01745   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
    01746     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
    01747   } else {
    01748     *ny = safe_y;
    01749   }
    01750 }
    01751
    01759 static void EnsureVisibleCaption(Window *w, int nx, int ny)
    01760 {
    01761   /* Search for the title bar rectangle. */
    01762   Rect caption_rect;
    01763   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
    01764   if (caption != NULL) {
    01765     caption_rect.left   = caption->pos_x;
    01766     caption_rect.right  = caption->pos_x + caption->current_x;
    01767     caption_rect.top    = caption->pos_y;
    01768     caption_rect.bottom = caption->pos_y + caption->current_y;
    01769
    01770     /* Make sure the window doesn't leave the screen */
    01771     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
    01772     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
    01773
    01774     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
    01775     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
    01776     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
    01777   }
    01778
    01779   if (w->viewport != NULL) {
    01780     w->viewport->left += nx - w->left;
    01781     w->viewport->top  += ny - w->top;
    01782   }
    01783
    01784   w->left = nx;
    01785   w->top  = ny;
    01786 }
    01787
    01798 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
    01799 {
    01800   if (delta_x != 0 || delta_y != 0) {
    01801     if (clamp_to_screen) {
    01802       /* Determine the new right/bottom position. If that is outside of the bounds of
    01803        * the resolution clamp it in such a manner that it stays within the bounds. */
    01804       int new_right  = w->left + w->width  + delta_x;
    01805       int new_bottom = w->top  + w->height + delta_y;
    01806       if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
    01807       if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
    01808     }
    01809
    01810     w->SetDirty();
    01811
    01812     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);
    01813     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);
    01814     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
    01815     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
    01816
    01817     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);
    01818     w->width  = w->nested_root->current_x;
    01819     w->height = w->nested_root->current_y;
    01820   }
    01821
    01822   EnsureVisibleCaption(w, w->left, w->top);
    01823
    01824   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
    01825   w->OnResize();
    01826   w->SetDirty();
    01827 }
    01828
    01834 int GetMainViewTop()
    01835 {
    01836   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01837   return (w == NULL) ? 0 : w->top + w->height;
    01838 }
    01839
    01845 int GetMainViewBottom()
    01846 {
    01847   Window *w = FindWindowById(WC_STATUS_BAR, 0);
    01848   return (w == NULL) ? _screen.height : w->top;
    01849 }
    01850
    01851 static bool _dragging_window;
    01852
    01857 static EventState HandleWindowDragging()
    01858 {
    01859   /* Get out immediately if no window is being dragged at all. */
    01860   if (!_dragging_window) return ES_NOT_HANDLED;
    01861
    01862   /* If button still down, but cursor hasn't moved, there is nothing to do. */
    01863   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
    01864
    01865   /* Otherwise find the window... */
    01866   Window *w;
    01867   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01868     if (w->flags & WF_DRAGGING) {
    01869       /* Stop the dragging if the left mouse button was released */
    01870       if (!_left_button_down) {
    01871         w->flags &= ~WF_DRAGGING;
    01872         break;
    01873       }
    01874
    01875       w->SetDirty();
    01876
    01877       int x = _cursor.pos.x + _drag_delta.x;
    01878       int y = _cursor.pos.y + _drag_delta.y;
    01879       int nx = x;
    01880       int ny = y;
    01881
    01882       if (_settings_client.gui.window_snap_radius != 0) {
    01883         const Window *v;
    01884
    01885         int hsnap = _settings_client.gui.window_snap_radius;
    01886         int vsnap = _settings_client.gui.window_snap_radius;
    01887         int delta;
    01888
    01889         FOR_ALL_WINDOWS_FROM_BACK(v) {
    01890           if (v == w) continue; // Don't snap at yourself
    01891
    01892           if (y + w->height > v->top && y < v->top + v->height) {
    01893             /* Your left border <-> other right border */
    01894             delta = abs(v->left + v->width - x);
    01895             if (delta <= hsnap) {
    01896               nx = v->left + v->width;
    01897               hsnap = delta;
    01898             }
    01899
    01900             /* Your right border <-> other left border */
    01901             delta = abs(v->left - x - w->width);
    01902             if (delta <= hsnap) {
    01903               nx = v->left - w->width;
    01904               hsnap = delta;
    01905             }
    01906           }
    01907
    01908           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
    01909             /* Your left border <-> other left border */
    01910             delta = abs(v->left - x);
    01911             if (delta <= hsnap) {
    01912               nx = v->left;
    01913               hsnap = delta;
    01914             }
    01915
    01916             /* Your right border <-> other right border */
    01917             delta = abs(v->left + v->width - x - w->width);
    01918             if (delta <= hsnap) {
    01919               nx = v->left + v->width - w->width;
    01920               hsnap = delta;
    01921             }
    01922           }
    01923
    01924           if (x + w->width > v->left && x < v->left + v->width) {
    01925             /* Your top border <-> other bottom border */
    01926             delta = abs(v->top + v->height - y);
    01927             if (delta <= vsnap) {
    01928               ny = v->top + v->height;
    01929               vsnap = delta;
    01930             }
    01931
    01932             /* Your bottom border <-> other top border */
    01933             delta = abs(v->top - y - w->height);
    01934             if (delta <= vsnap) {
    01935               ny = v->top - w->height;
    01936               vsnap = delta;
    01937             }
    01938           }
    01939
    01940           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
    01941             /* Your top border <-> other top border */
    01942             delta = abs(v->top - y);
    01943             if (delta <= vsnap) {
    01944               ny = v->top;
    01945               vsnap = delta;
    01946             }
    01947
    01948             /* Your bottom border <-> other bottom border */
    01949             delta = abs(v->top + v->height - y - w->height);
    01950             if (delta <= vsnap) {
    01951               ny = v->top + v->height - w->height;
    01952               vsnap = delta;
    01953             }
    01954           }
    01955         }
    01956       }
    01957
    01958       EnsureVisibleCaption(w, nx, ny);
    01959
    01960       w->SetDirty();
    01961       return ES_HANDLED;
    01962     } else if (w->flags & WF_SIZING) {
    01963       /* Stop the sizing if the left mouse button was released */
    01964       if (!_left_button_down) {
    01965         w->flags &= ~WF_SIZING;
    01966         w->SetDirty();
    01967         break;
    01968       }
    01969
    01970       /* Compute difference in pixels between cursor position and reference point in the window.
    01971        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
    01972        */
    01973       int x, y = _cursor.pos.y - _drag_delta.y;
    01974       if (w->flags & WF_SIZING_LEFT) {
    01975         x = _drag_delta.x - _cursor.pos.x;
    01976       } else {
    01977         x = _cursor.pos.x - _drag_delta.x;
    01978       }
    01979
    01980       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
    01981       if (w->resize.step_width  == 0) x = 0;
    01982       if (w->resize.step_height == 0) y = 0;
    01983
    01984       /* Check the resize button won't go past the bottom of the screen */
    01985       if (w->top + w->height + y > _screen.height) {
    01986         y = _screen.height - w->height - w->top;
    01987       }
    01988
    01989       /* X and Y has to go by step.. calculate it.
    01990        * The cast to int is necessary else x/y are implicitly casted to
    01991        * unsigned int, which won't work. */
    01992       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
    01993       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
    01994
    01995       /* Check that we don't go below the minimum set size */
    01996       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
    01997         x = w->nested_root->smallest_x - w->width;
    01998       }
    01999       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
    02000         y = w->nested_root->smallest_y - w->height;
    02001       }
    02002
    02003       /* Window already on size */
    02004       if (x == 0 && y == 0) return ES_HANDLED;
    02005
    02006       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
    02007       _drag_delta.y += y;
    02008       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
    02009         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
    02010         w->SetDirty();
    02011         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
    02012         /* ResizeWindow() below ensures marking new position as dirty. */
    02013       } else {
    02014         _drag_delta.x += x;
    02015       }
    02016
    02017       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
    02018       ResizeWindow(w, x, y);
    02019       return ES_HANDLED;
    02020     }
    02021   }
    02022
    02023   _dragging_window = false;
    02024   return ES_HANDLED;
    02025 }
    02026
    02031 static void StartWindowDrag(Window *w)
    02032 {
    02033   w->flags |= WF_DRAGGING;
    02034   w->flags &= ~WF_CENTERED;
    02035   _dragging_window = true;
    02036
    02037   _drag_delta.x = w->left - _cursor.pos.x;
    02038   _drag_delta.y = w->top  - _cursor.pos.y;
    02039
    02040   BringWindowToFront(w);
    02041   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    02042 }
    02043
    02049 static void StartWindowSizing(Window *w, bool to_left)
    02050 {
    02051   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
    02052   w->flags &= ~WF_CENTERED;
    02053   _dragging_window = true;
    02054
    02055   _drag_delta.x = _cursor.pos.x;
    02056   _drag_delta.y = _cursor.pos.y;
    02057
    02058   BringWindowToFront(w);
    02059   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    02060 }
    02061
    02066 static EventState HandleScrollbarScrolling()
    02067 {
    02068   Window *w;
    02069   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02070     if (w->scrolling_scrollbar >= 0) {
    02071       /* Abort if no button is clicked any more. */
    02072       if (!_left_button_down) {
    02073         w->scrolling_scrollbar = -1;
    02074         w->SetDirty();
    02075         return ES_HANDLED;
    02076       }
    02077
    02078       int i;
    02079       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
    02080       bool rtl = false;
    02081
    02082       if (sb->type == NWID_HSCROLLBAR) {
    02083         i = _cursor.pos.x - _cursorpos_drag_start.x;
    02084         rtl = _current_text_dir == TD_RTL;
    02085       } else {
    02086         i = _cursor.pos.y - _cursorpos_drag_start.y;
    02087       }
    02088
    02089       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
    02090         if (_scroller_click_timeout == 1) {
    02091           _scroller_click_timeout = 3;
    02092           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
    02093           w->SetDirty();
    02094         }
    02095         return ES_HANDLED;
    02096       }
    02097
    02098       /* Find the item we want to move to and make sure it's inside bounds. */
    02099       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
    02100       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
    02101       if (pos != sb->GetPosition()) {
    02102         sb->SetPosition(pos);
    02103         w->SetDirty();
    02104       }
    02105       return ES_HANDLED;
    02106     }
    02107   }
    02108
    02109   return ES_NOT_HANDLED;
    02110 }
    02111
    02116 static EventState HandleViewportScroll()
    02117 {
    02118   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02119
    02120   if (!_scrolling_viewport) return ES_NOT_HANDLED;
    02121
    02122   /* When we don't have a last scroll window we are starting to scroll.
    02123    * When the last scroll window and this are not the same we went
    02124    * outside of the window and should not left-mouse scroll anymore. */
    02125   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    02126
    02127   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
    02128     _cursor.fix_at = false;
    02129     _scrolling_viewport = false;
    02130     _last_scroll_window = NULL;
    02131     return ES_NOT_HANDLED;
    02132   }
    02133
    02134   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
    02135     /* If the main window is following a vehicle, then first let go of it! */
    02136     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
    02137     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
    02138     return ES_NOT_HANDLED;
    02139   }
    02140
    02141   Point delta;
    02142   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
    02143     delta.x = -_cursor.delta.x;
    02144     delta.y = -_cursor.delta.y;
    02145   } else {
    02146     delta.x = _cursor.delta.x;
    02147     delta.y = _cursor.delta.y;
    02148   }
    02149
    02150   if (scrollwheel_scrolling) {
    02151     /* We are using scrollwheels for scrolling */
    02152     delta.x = _cursor.h_wheel;
    02153     delta.y = _cursor.v_wheel;
    02154     _cursor.v_wheel = 0;
    02155     _cursor.h_wheel = 0;
    02156   }
    02157
    02158   /* Create a scroll-event and send it to the window */
    02159   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
    02160
    02161   _cursor.delta.x = 0;
    02162   _cursor.delta.y = 0;
    02163   return ES_HANDLED;
    02164 }
    02165
    02176 static bool MaybeBringWindowToFront(Window *w)
    02177 {
    02178   bool bring_to_front = false;
    02179
    02180   if (w->window_class == WC_MAIN_WINDOW ||
    02181       IsVitalWindow(w) ||
    02182       w->window_class == WC_TOOLTIPS ||
    02183       w->window_class == WC_DROPDOWN_MENU) {
    02184     return true;
    02185   }
    02186
    02187   /* Use unshaded window size rather than current size for shaded windows. */
    02188   int w_width  = w->width;
    02189   int w_height = w->height;
    02190   if (w->IsShaded()) {
    02191     w_width  = w->unshaded_size.width;
    02192     w_height = w->unshaded_size.height;
    02193   }
    02194
    02195   Window *u;
    02196   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
    02197     /* A modal child will prevent the activation of the parent window */
    02198     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
    02199       u->SetWhiteBorder();
    02200       u->SetDirty();
    02201       return false;
    02202     }
    02203
    02204     if (u->window_class == WC_MAIN_WINDOW ||
    02205         IsVitalWindow(u) ||
    02206         u->window_class == WC_TOOLTIPS ||
    02207         u->window_class == WC_DROPDOWN_MENU) {
    02208       continue;
    02209     }
    02210
    02211     /* Window sizes don't interfere, leave z-order alone */
    02212     if (w->left + w_width <= u->left ||
    02213         u->left + u->width <= w->left ||
    02214         w->top  + w_height <= u->top ||
    02215         u->top + u->height <= w->top) {
    02216       continue;
    02217     }
    02218
    02219     bring_to_front = true;
    02220   }
    02221
    02222   if (bring_to_front) BringWindowToFront(w);
    02223   return true;
    02224 }
    02225
    02230 void HandleKeypress(uint32 raw_key)
    02231 {
    02232   /* World generation is multithreaded and messes with companies.
    02233    * But there is no company related window open anyway, so _current_company is not used. */
    02234   assert(HasModalProgress() || IsLocalCompany());
    02235
    02236   /* Setup event */
    02237   uint16 key     = GB(raw_key,  0, 16);
    02238   uint16 keycode = GB(raw_key, 16, 16);
    02239
    02240   /*
    02241    * The Unicode standard defines an area called the private use area. Code points in this
    02242    * area are reserved for private use and thus not portable between systems. For instance,
    02243    * Apple defines code points for the arrow keys in this area, but these are only printable
    02244    * on a system running OS X. We don't want these keys to show up in text fields and such,
    02245    * and thus we have to clear the unicode character when we encounter such a key.
    02246    */
    02247   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
    02248
    02249   /*
    02250    * If both key and keycode is zero, we don't bother to process the event.
    02251    */
    02252   if (key == 0 && keycode == 0) return;
    02253
    02254   /* Check if the focused window has a focused editbox */
    02255   if (EditBoxInGlobalFocus()) {
    02256     /* All input will in this case go to the focused window */
    02257     if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02258   }
    02259
    02260   /* Call the event, start with the uppermost window, but ignore the toolbar. */
    02261   Window *w;
    02262   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02263     if (w->window_class == WC_MAIN_TOOLBAR) continue;
    02264     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02265   }
    02266
    02267   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    02268   /* When there is no toolbar w is null, check for that */
    02269   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02270
    02271   HandleGlobalHotkeys(key, keycode);
    02272 }
    02273
    02277 void HandleCtrlChanged()
    02278 {
    02279   /* Call the event, start with the uppermost window. */
    02280   Window *w;
    02281   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02282     if (w->OnCTRLStateChange() == ES_HANDLED) return;
    02283   }
    02284 }
    02285
    02292 static int _input_events_this_tick = 0;
    02293
    02298 static void HandleAutoscroll()
    02299 {
    02300   if (_game_mode == GM_MENU || HasModalProgress()) return;
    02301   if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
    02302   if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
    02303
    02304   int x = _cursor.pos.x;
    02305   int y = _cursor.pos.y;
    02306   Window *w = FindWindowFromPt(x, y);
    02307   if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
    02308   if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
    02309
    02310   ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02311   if (vp == NULL) return;
    02312
    02313   x -= vp->left;
    02314   y -= vp->top;
    02315
    02316   /* here allows scrolling in both x and y axis */
    02317 #define scrollspeed 3
    02318   if (x - 15 < 0) {
    02319     w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
    02320   } else if (15 - (vp->width - x) > 0) {
    02321     w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
    02322   }
    02323   if (y - 15 < 0) {
    02324     w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
    02325   } else if (15 - (vp->height - y) > 0) {
    02326     w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
    02327   }
    02328 #undef scrollspeed
    02329 }
    02330
    02331 enum MouseClick {
    02332   MC_NONE = 0,
    02333   MC_LEFT,
    02334   MC_RIGHT,
    02335   MC_DOUBLE_LEFT,
    02336   MC_HOVER,
    02337
    02338   MAX_OFFSET_DOUBLE_CLICK = 5,
    02339   TIME_BETWEEN_DOUBLE_CLICK = 500,
    02340   MAX_OFFSET_HOVER = 5,
    02341 };
    02342 extern EventState VpHandlePlaceSizingDrag();
    02343
    02344 static void ScrollMainViewport(int x, int y)
    02345 {
    02346   if (_game_mode != GM_MENU) {
    02347     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
    02348     assert(w);
    02349
    02350     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
    02351     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
    02352   }
    02353 }
    02354
    02364 static const int8 scrollamt[16][2] = {
    02365   { 0,  0},
    02366   {-2,  0},
    02367   { 0, -2},
    02368   {-2, -1},
    02369   { 2,  0},
    02370   { 0,  0},
    02371   { 2, -1},
    02372   { 0, -2},
    02373   { 0,  2},
    02374   {-2,  1},
    02375   { 0,  0},
    02376   {-2,  0},
    02377   { 2,  1},
    02378   { 0,  2},
    02379   { 2,  0},
    02380   { 0,  0},
    02381 };
    02382
    02383 static void HandleKeyScrolling()
    02384 {
    02385   /*
    02386    * Check that any of the dirkeys is pressed and that the focused window
    02387    * dont has an edit-box as focused widget.
    02388    */
    02389   if (_dirkeys && !EditBoxInGlobalFocus()) {
    02390     int factor = _shift_pressed ? 50 : 10;
    02391     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
    02392   }
    02393 }
    02394
    02395 static void MouseLoop(MouseClick click, int mousewheel)
    02396 {
    02397   /* World generation is multithreaded and messes with companies.
    02398    * But there is no company related window open anyway, so _current_company is not used. */
    02399   assert(HasModalProgress() || IsLocalCompany());
    02400
    02401   HandlePlacePresize();
    02402   UpdateTileSelection();
    02403
    02404   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
    02405   if (HandleMouseDragDrop()      == ES_HANDLED) return;
    02406   if (HandleWindowDragging()     == ES_HANDLED) return;
    02407   if (HandleScrollbarScrolling() == ES_HANDLED) return;
    02408   if (HandleViewportScroll()     == ES_HANDLED) return;
    02409
    02410   HandleMouseOver();
    02411
    02412   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02413   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
    02414
    02415   int x = _cursor.pos.x;
    02416   int y = _cursor.pos.y;
    02417   Window *w = FindWindowFromPt(x, y);
    02418   if (w == NULL) return;
    02419
    02420   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
    02421   ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02422
    02423   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
    02424   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
    02425
    02426   if (mousewheel != 0) {
    02427     /* Send mousewheel event to window */
    02428     w->OnMouseWheel(mousewheel);
    02429
    02430     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
    02431     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
    02432   }
    02433
    02434   if (vp != NULL) {
    02435     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
    02436     switch (click) {
    02437       case MC_DOUBLE_LEFT:
    02438       case MC_LEFT:
    02439         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
    02440         if (!HandleViewportClicked(vp, x, y) &&
    02441             !(w->flags & WF_DISABLE_VP_SCROLL) &&
    02442             _settings_client.gui.left_mouse_btn_scrolling) {
    02443           _scrolling_viewport = true;
    02444           _cursor.fix_at = false;
    02445         }
    02446         break;
    02447
    02448       case MC_RIGHT:
    02449         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
    02450           _scrolling_viewport = true;
    02451           _cursor.fix_at = true;
    02452
    02453           /* clear 2D scrolling caches before we start a 2D scroll */
    02454           _cursor.h_wheel = 0;
    02455           _cursor.v_wheel = 0;
    02456         }
    02457         break;
    02458
    02459       default:
    02460         break;
    02461     }
    02462   } else {
    02463     switch (click) {
    02464       case MC_LEFT:
    02465       case MC_DOUBLE_LEFT:
    02466         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
    02467         break;
    02468
    02469       default:
    02470         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
    02471         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
    02472          * Simulate a right button click so we can get started. */
    02473         /* FALL THROUGH */
    02474
    02475       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
    02476
    02477       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
    02478     }
    02479   }
    02480 }
    02481
    02485 void HandleMouseEvents()
    02486 {
    02487   /* World generation is multithreaded and messes with companies.
    02488    * But there is no company related window open anyway, so _current_company is not used. */
    02489   assert(HasModalProgress() || IsLocalCompany());
    02490
    02491   static int double_click_time = 0;
    02492   static Point double_click_pos = {0, 0};
    02493
    02494   /* Mouse event? */
    02495   MouseClick click = MC_NONE;
    02496   if (_left_button_down && !_left_button_clicked) {
    02497     click = MC_LEFT;
    02498     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
    02499         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
    02500         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
    02501       click = MC_DOUBLE_LEFT;
    02502     }
    02503     double_click_time = _realtime_tick;
    02504     double_click_pos = _cursor.pos;
    02505     _left_button_clicked = true;
    02506     _input_events_this_tick++;
    02507   } else if (_right_button_clicked) {
    02508     _right_button_clicked = false;
    02509     click = MC_RIGHT;
    02510     _input_events_this_tick++;
    02511   }
    02512
    02513   int mousewheel = 0;
    02514   if (_cursor.wheel) {
    02515     mousewheel = _cursor.wheel;
    02516     _cursor.wheel = 0;
    02517     _input_events_this_tick++;
    02518   }
    02519
    02520   static uint32 hover_time = 0;
    02521   static Point hover_pos = {0, 0};
    02522
    02523   if (_settings_client.gui.hover_delay > 0) {
    02524     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
    02525         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
    02526         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
    02527       hover_pos = _cursor.pos;
    02528       hover_time = _realtime_tick;
    02529       _mouse_hovering = false;
    02530     } else {
    02531       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
    02532         click = MC_HOVER;
    02533         _input_events_this_tick++;
    02534         _mouse_hovering = true;
    02535       }
    02536     }
    02537   }
    02538
    02539   /* Handle sprite picker before any GUI interaction */
    02540   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
    02541     /* Next realtime tick? Then redraw has finished */
    02542     _newgrf_debug_sprite_picker.mode = SPM_NONE;
    02543     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
    02544   }
    02545
    02546   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
    02547     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
    02548     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    02549     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
    02550     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
    02551     _newgrf_debug_sprite_picker.sprites.Clear();
    02552     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
    02553     MarkWholeScreenDirty();
    02554   } else {
    02555     MouseLoop(click, mousewheel);
    02556   }
    02557
    02558   /* We have moved the mouse the required distance,
    02559    * no need to move it at any later time. */
    02560   _cursor.delta.x = 0;
    02561   _cursor.delta.y = 0;
    02562 }
    02563
    02567 static void CheckSoftLimit()
    02568 {
    02569   if (_settings_client.gui.window_soft_limit == 0) return;
    02570
    02571   for (;;) {
    02572     uint deletable_count = 0;
    02573     Window *w, *last_deletable = NULL;
    02574     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02575       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
    02576
    02577       last_deletable = w;
    02578       deletable_count++;
    02579     }
    02580
    02581     /* We've not reached the soft limit yet. */
    02582     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
    02583
    02584     assert(last_deletable != NULL);
    02585     delete last_deletable;
    02586   }
    02587 }
    02588
    02592 void InputLoop()
    02593 {
    02594   /* World generation is multithreaded and messes with companies.
    02595    * But there is no company related window open anyway, so _current_company is not used. */
    02596   assert(HasModalProgress() || IsLocalCompany());
    02597
    02598   CheckSoftLimit();
    02599   HandleKeyScrolling();
    02600
    02601   /* Do the actual free of the deleted windows. */
    02602   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
    02603     Window *w = v;
    02604     v = v->z_back;
    02605
    02606     if (w->window_class != WC_INVALID) continue;
    02607
    02608     RemoveWindowFromZOrdering(w);
    02609     free(w);
    02610   }
    02611
    02612   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
    02613   DecreaseWindowCounters();
    02614
    02615   if (_input_events_this_tick != 0) {
    02616     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
    02617     _input_events_this_tick = 0;
    02618     /* there were some inputs this tick, don't scroll ??? */
    02619     return;
    02620   }
    02621
    02622   /* HandleMouseEvents was already called for this tick */
    02623   HandleMouseEvents();
    02624   HandleAutoscroll();
    02625 }
    02626
    02630 void UpdateWindows()
    02631 {
    02632   Window *w;
    02633
    02634   static int highlight_timer = 1;
    02635   if (--highlight_timer == 0) {
    02636     highlight_timer = 15;
    02637     _window_highlight_colour = !_window_highlight_colour;
    02638   }
    02639
    02640   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02641     w->ProcessScheduledInvalidations();
    02642     w->ProcessHighlightedInvalidations();
    02643   }
    02644
    02645   static int we4_timer = 0;
    02646   int t = we4_timer + 1;
    02647
    02648   if (t >= 100) {
    02649     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02650       w->OnHundredthTick();
    02651     }
    02652     t = 0;
    02653   }
    02654   we4_timer = t;
    02655
    02656   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02657     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
    02658       CLRBITS(w->flags, WF_WHITE_BORDER);
    02659       w->SetDirty();
    02660     }
    02661   }
    02662
    02663   DrawDirtyBlocks();
    02664
    02665   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02666     /* Update viewport only if window is not shaded. */
    02667     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
    02668   }
    02669   NetworkDrawChatMessage();
    02670   /* Redraw mouse cursor in case it was hidden */
    02671   DrawMouseCursor();
    02672 }
    02673
    02679 void SetWindowDirty(WindowClass cls, WindowNumber number)
    02680 {
    02681   const Window *w;
    02682   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02683     if (w->window_class == cls && w->window_number == number) w->SetDirty();
    02684   }
    02685 }
    02686
    02693 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
    02694 {
    02695   const Window *w;
    02696   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02697     if (w->window_class == cls && w->window_number == number) {
    02698       w->SetWidgetDirty(widget_index);
    02699     }
    02700   }
    02701 }
    02702
    02707 void SetWindowClassesDirty(WindowClass cls)
    02708 {
    02709   Window *w;
    02710   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02711     if (w->window_class == cls) w->SetDirty();
    02712   }
    02713 }
    02714
    02720 void Window::InvalidateData(int data, bool gui_scope)
    02721 {
    02722   this->SetDirty();
    02723   if (!gui_scope) {
    02724     /* Schedule GUI-scope invalidation for next redraw. */
    02725     *this->scheduled_invalidation_data.Append() = data;
    02726   }
    02727   this->OnInvalidateData(data, gui_scope);
    02728 }
    02729
    02733 void Window::ProcessScheduledInvalidations()
    02734 {
    02735   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
    02736     this->OnInvalidateData(*data, true);
    02737   }
    02738   this->scheduled_invalidation_data.Clear();
    02739 }
    02740
    02744 void Window::ProcessHighlightedInvalidations()
    02745 {
    02746   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
    02747
    02748   for (uint i = 0; i < this->nested_array_size; i++) {
    02749     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
    02750   }
    02751 }
    02752
    02779 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
    02780 {
    02781   Window *w;
    02782   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02783     if (w->window_class == cls && w->window_number == number) {
    02784       w->InvalidateData(data, gui_scope);
    02785     }
    02786   }
    02787 }
    02788
    02797 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
    02798 {
    02799   Window *w;
    02800
    02801   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02802     if (w->window_class == cls) {
    02803       w->InvalidateData(data, gui_scope);
    02804     }
    02805   }
    02806 }
    02807
    02811 void CallWindowTickEvent()
    02812 {
    02813   Window *w;
    02814   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02815     w->OnTick();
    02816   }
    02817 }
    02818
    02825 void DeleteNonVitalWindows()
    02826 {
    02827   Window *w;
    02828
    02829 restart_search:
    02830   /* When we find the window to delete, we need to restart the search
    02831    * as deleting this window could cascade in deleting (many) others
    02832    * anywhere in the z-array */
    02833   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02834     if (w->window_class != WC_MAIN_WINDOW &&
    02835         w->window_class != WC_SELECT_GAME &&
    02836         w->window_class != WC_MAIN_TOOLBAR &&
    02837         w->window_class != WC_STATUS_BAR &&
    02838         w->window_class != WC_TOOLTIPS &&
    02839         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
    02840
    02841       delete w;
    02842       goto restart_search;
    02843     }
    02844   }
    02845 }
    02846
    02854 void DeleteAllNonVitalWindows()
    02855 {
    02856   Window *w;
    02857
    02858   /* Delete every window except for stickied ones, then sticky ones as well */
    02859   DeleteNonVitalWindows();
    02860
    02861 restart_search:
    02862   /* When we find the window to delete, we need to restart the search
    02863    * as deleting this window could cascade in deleting (many) others
    02864    * anywhere in the z-array */
    02865   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02866     if (w->flags & WF_STICKY) {
    02867       delete w;
    02868       goto restart_search;
    02869     }
    02870   }
    02871 }
    02872
    02877 void DeleteConstructionWindows()
    02878 {
    02879   Window *w;
    02880
    02881 restart_search:
    02882   /* When we find the window to delete, we need to restart the search
    02883    * as deleting this window could cascade in deleting (many) others
    02884    * anywhere in the z-array */
    02885   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02886     if (w->desc_flags & WDF_CONSTRUCTION) {
    02887       delete w;
    02888       goto restart_search;
    02889     }
    02890   }
    02891
    02892   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
    02893 }
    02894
    02896 void HideVitalWindows()
    02897 {
    02898   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
    02899   DeleteWindowById(WC_STATUS_BAR, 0);
    02900 }
    02901
    02903 void ReInitAllWindows()
    02904 {
    02905   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    02906   NWidgetScrollbar::InvalidateDimensionCache();
    02907
    02908   Window *w;
    02909   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02910     w->ReInit();
    02911   }
    02912 #ifdef ENABLE_NETWORK
    02913   void NetworkReInitChatBoxSize();
    02914   NetworkReInitChatBoxSize();
    02915 #endif
    02916
    02917   /* Make sure essential parts of all windows are visible */
    02918   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
    02919   MarkWholeScreenDirty();
    02920 }
    02921
    02929 static int PositionWindow(Window *w, WindowClass clss, int setting)
    02930 {
    02931   if (w == NULL || w->window_class != clss) {
    02932     w = FindWindowById(clss, 0);
    02933   }
    02934   if (w == NULL) return 0;
    02935
    02936   int old_left = w->left;
    02937   switch (setting) {
    02938     case 1:  w->left = (_screen.width - w->width) / 2; break;
    02939     case 2:  w->left = _screen.width - w->width; break;
    02940     default: w->left = 0; break;
    02941   }
    02942   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
    02943   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
    02944   return w->left;
    02945 }
    02946
    02952 int PositionMainToolbar(Window *w)
    02953 {
    02954   DEBUG(misc, 5, "Repositioning Main Toolbar...");
    02955   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
    02956 }
    02957
    02963 int PositionStatusbar(Window *w)
    02964 {
    02965   DEBUG(misc, 5, "Repositioning statusbar...");
    02966   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
    02967 }
    02968
    02974 int PositionNewsMessage(Window *w)
    02975 {
    02976   DEBUG(misc, 5, "Repositioning news message...");
    02977   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
    02978 }
    02979
    02985 int PositionNetworkChatWindow(Window *w)
    02986 {
    02987   DEBUG(misc, 5, "Repositioning network chat window...");
    02988   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
    02989 }
    02990
    02991
    02997 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
    02998 {
    02999   Window *w;
    03000   FOR_ALL_WINDOWS_FROM_BACK(w) {
    03001     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
    03002       w->viewport->follow_vehicle = to_index;
    03003       w->SetDirty();
    03004     }
    03005   }
    03006 }
    03007
    03008
    03014 void RelocateAllWindows(int neww, int newh)
    03015 {
    03016   Window *w;
    03017
    03018   FOR_ALL_WINDOWS_FROM_BACK(w) {
    03019     int left, top;
    03020
    03021     if (w->window_class == WC_MAIN_WINDOW) {
    03022       ViewPort *vp = w->viewport;
    03023       vp->width = w->width = neww;
    03024       vp->height = w->height = newh;
    03025       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
    03026       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
    03027       continue; // don't modify top,left
    03028     }
    03029
    03030     /* XXX - this probably needs something more sane. For example specifying
    03031      * in a 'backup'-desc that the window should always be centered. */
    03032     switch (w->window_class) {
    03033       case WC_BOOTSTRAP:
    03034         ResizeWindow(w, neww, newh);
    03035         continue;
    03036
    03037       case WC_MAIN_TOOLBAR:
    03038         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0, false);
    03039
    03040         top = w->top;
    03041         left = PositionMainToolbar(w); // changes toolbar orientation
    03042         break;
    03043
    03044       case WC_NEWS_WINDOW:
    03045         top = newh - w->height;
    03046         left = PositionNewsMessage(w);
    03047         break;
    03048
    03049       case WC_STATUS_BAR:
    03050         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0, false);
    03051
    03052         top = newh - w->height;
    03053         left = PositionStatusbar(w);
    03054         break;
    03055
    03056       case WC_SEND_NETWORK_MSG:
    03057         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
    03058         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
    03059         left = PositionNetworkChatWindow(w);
    03060         break;
    03061
    03062       case WC_CONSOLE:
    03063         IConsoleResize(w);
    03064         continue;
    03065
    03066       default: {
    03067         if (w->flags & WF_CENTERED) {
    03068           top = (newh - w->height) >> 1;
    03069           left = (neww - w->width) >> 1;
    03070           break;
    03071         }
    03072
    03073         left = w->left;
    03074         if (left + (w->width >> 1) >= neww) left = neww - w->width;
    03075         if (left < 0) left = 0;
    03076
    03077         top = w->top;
    03078         if (top + (w->height >> 1) >= newh) top = newh - w->height;
    03079         break;
    03080       }
    03081     }
    03082
    03083     EnsureVisibleCaption(w, left, top);
    03084   }
    03085 }
    03086
    03092 PickerWindowBase::~PickerWindowBase()
    03093 {
    03094   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
    03095   ResetObjectToPlace();
    03096 }