# window.cpp

Go to the documentation of this file.

    00001 /* $Id: window.cpp 24900 2013-01-08 22:46:42Z planetmaker $ */
    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
    00235 const QueryString *Window::GetQueryString(uint widnum) const
    00236 {
    00237   const SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
    00238   return query != this->querystrings.End() ? query->second : NULL;
    00239 }
    00240
    00246 QueryString *Window::GetQueryString(uint widnum)
    00247 {
    00248   SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
    00249   return query != this->querystrings.End() ? query->second : NULL;
    00250 }
    00251
    00252
    00257 void SetFocusedWindow(Window *w)
    00258 {
    00259   if (_focused_window == w) return;
    00260
    00261   /* Invalidate focused widget */
    00262   if (_focused_window != NULL) {
    00263     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
    00264   }
    00265
    00266   /* Remember which window was previously focused */
    00267   Window *old_focused = _focused_window;
    00268   _focused_window = w;
    00269
    00270   /* So we can inform it that it lost focus */
    00271   if (old_focused != NULL) old_focused->OnFocusLost();
    00272   if (_focused_window != NULL) _focused_window->OnFocus();
    00273 }
    00274
    00280 static bool EditBoxInGlobalFocus()
    00281 {
    00282   if (_focused_window == NULL) return false;
    00283
    00284   /* The console does not have an edit box so a special case is needed. */
    00285   if (_focused_window->window_class == WC_CONSOLE) return true;
    00286
    00287   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
    00288 }
    00289
    00293 void Window::UnfocusFocusedWidget()
    00294 {
    00295   if (this->nested_focus != NULL) {
    00296     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00297     this->nested_focus->SetDirty(this);
    00298     this->nested_focus = NULL;
    00299   }
    00300 }
    00301
    00307 bool Window::SetFocusedWidget(int widget_index)
    00308 {
    00309   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
    00310   if ((uint)widget_index >= this->nested_array_size) return false;
    00311
    00312   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
    00313   if (this->nested_focus != NULL) {
    00314     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
    00315
    00316     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00317     this->nested_focus->SetDirty(this);
    00318   }
    00319   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
    00320   return true;
    00321 }
    00322
    00330 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
    00331 {
    00332   va_list wdg_list;
    00333
    00334   va_start(wdg_list, widgets);
    00335
    00336   while (widgets != WIDGET_LIST_END) {
    00337     SetWidgetDisabledState(widgets, disab_stat);
    00338     widgets = va_arg(wdg_list, int);
    00339   }
    00340
    00341   va_end(wdg_list);
    00342 }
    00343
    00349 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
    00350 {
    00351   va_list wdg_list;
    00352
    00353   va_start(wdg_list, widgets);
    00354
    00355   while (widgets != WIDGET_LIST_END) {
    00356     SetWidgetLoweredState(widgets, lowered_stat);
    00357     widgets = va_arg(wdg_list, int);
    00358   }
    00359
    00360   va_end(wdg_list);
    00361 }
    00362
    00367 void Window::RaiseButtons(bool autoraise)
    00368 {
    00369   for (uint i = 0; i < this->nested_array_size; i++) {
    00370     if (this->nested_array[i] == NULL) continue;
    00371     WidgetType type = this->nested_array[i]->type;
    00372     if (((type & ~WWB_PUSHBUTTON) < WWT_LAST || type == NWID_PUSHBUTTON_DROPDOWN) &&
    00373         (!autoraise || (type & WWB_PUSHBUTTON) || type == WWT_EDITBOX) && this->IsWidgetLowered(i)) {
    00374       this->RaiseWidget(i);
    00375       this->SetWidgetDirty(i);
    00376     }
    00377   }
    00378 }
    00379
    00384 void Window::SetWidgetDirty(byte widget_index) const
    00385 {
    00386   /* Sometimes this function is called before the window is even fully initialized */
    00387   if (this->nested_array == NULL) return;
    00388
    00389   this->nested_array[widget_index]->SetDirty(this);
    00390 }
    00391
    00397 void Window::HandleButtonClick(byte widget)
    00398 {
    00399   this->LowerWidget(widget);
    00400   this->SetTimeout();
    00401   this->SetWidgetDirty(widget);
    00402 }
    00403
    00404 static void StartWindowDrag(Window *w);
    00405 static void StartWindowSizing(Window *w, bool to_left);
    00406
    00414 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
    00415 {
    00416   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
    00417   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
    00418
    00419   bool focused_widget_changed = false;
    00420   /* If clicked on a window that previously did dot have focus */
    00421   if (_focused_window != w &&                 // We already have focus, right?
    00422       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
    00423       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
    00424     focused_widget_changed = true;
    00425     SetFocusedWindow(w);
    00426     w->OnFocus();
    00427   }
    00428
    00429   if (nw == NULL) return; // exit if clicked outside of widgets
    00430
    00431   /* don't allow any interaction if the button has been disabled */
    00432   if (nw->IsDisabled()) return;
    00433
    00434   int widget_index = nw->index;
    00435
    00436   /* Clicked on a widget that is not disabled.
    00437    * So unless the clicked widget is the caption bar, change focus to this widget.
    00438    * Exception: In the OSK we always want the editbox to stay focussed. */
    00439   if (widget_type != WWT_CAPTION && w->window_class != WC_OSK) {
    00440     /* focused_widget_changed is 'now' only true if the window this widget
    00441      * is in gained focus. In that case it must remain true, also if the
    00442      * local widget focus did not change. As such it's the logical-or of
    00443      * both changed states.
    00444      *
    00445      * If this is not preserved, then the OSK window would be opened when
    00446      * a user has the edit box focused and then click on another window and
    00447      * then back again on the edit box (to type some text).
    00448      */
    00449     focused_widget_changed |= w->SetFocusedWidget(widget_index);
    00450   }
    00451
    00452   /* Close any child drop down menus. If the button pressed was the drop down
    00453    * list's own button, then we should not process the click any further. */
    00454   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
    00455
    00456   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
    00457
    00458   Point pt = { x, y };
    00459
    00460   switch (widget_type) {
    00461     case NWID_VSCROLLBAR:
    00462     case NWID_HSCROLLBAR:
    00463       ScrollbarClickHandler(w, nw, x, y);
    00464       break;
    00465
    00466     case WWT_EDITBOX: {
    00467       QueryString *query = w->GetQueryString(widget_index);
    00468       if (query != NULL) query->ClickEditBox(w, pt, widget_index, click_count, focused_widget_changed);
    00469       break;
    00470     }
    00471
    00472     case WWT_CLOSEBOX: // 'X'
    00473       delete w;
    00474       return;
    00475
    00476     case WWT_CAPTION: // 'Title bar'
    00477       StartWindowDrag(w);
    00478       return;
    00479
    00480     case WWT_RESIZEBOX:
    00481       /* When the resize widget is on the left size of the window
    00482        * we assume that that button is used to resize to the left. */
    00483       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
    00484       nw->SetDirty(w);
    00485       return;
    00486
    00487     case WWT_DEBUGBOX:
    00488       w->ShowNewGRFInspectWindow();
    00489       break;
    00490
    00491     case WWT_SHADEBOX:
    00492       nw->SetDirty(w);
    00493       w->SetShaded(!w->IsShaded());
    00494       return;
    00495
    00496     case WWT_STICKYBOX:
    00497       w->flags ^= WF_STICKY;
    00498       nw->SetDirty(w);
    00499       return;
    00500
    00501     default:
    00502       break;
    00503   }
    00504
    00505   /* Widget has no index, so the window is not interested in it. */
    00506   if (widget_index < 0) return;
    00507
    00508   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
    00509   if (w->IsWidgetHighlighted(widget_index)) {
    00510     w->SetWidgetHighlight(widget_index, TC_INVALID);
    00511     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
    00512   }
    00513
    00514   w->OnClick(pt, widget_index, click_count);
    00515 }
    00516
    00523 static void DispatchRightClickEvent(Window *w, int x, int y)
    00524 {
    00525   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00526   if (wid == NULL) return;
    00527
    00528   /* No widget to handle, or the window is not interested in it. */
    00529   if (wid->index >= 0) {
    00530     Point pt = { x, y };
    00531     if (w->OnRightClick(pt, wid->index)) return;
    00532   }
    00533
    00534   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
    00535 }
    00536
    00543 static void DispatchHoverEvent(Window *w, int x, int y)
    00544 {
    00545   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00546
    00547   /* No widget to handle */
    00548   if (wid == NULL) return;
    00549
    00550   /* Show the tooltip if there is any */
    00551   if (wid->tool_tip != 0) {
    00552     GuiShowTooltips(w, wid->tool_tip);
    00553     return;
    00554   }
    00555
    00556   /* Widget has no index, so the window is not interested in it. */
    00557   if (wid->index < 0) return;
    00558
    00559   Point pt = { x, y };
    00560   w->OnHover(pt, wid->index);
    00561 }
    00562
    00570 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
    00571 {
    00572   if (nwid == NULL) return;
    00573
    00574   /* Using wheel on caption/shade-box shades or unshades the window. */
    00575   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
    00576     w->SetShaded(wheel < 0);
    00577     return;
    00578   }
    00579
    00580   /* Wheeling a vertical scrollbar. */
    00581   if (nwid->type == NWID_VSCROLLBAR) {
    00582     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
    00583     if (sb->GetCount() > sb->GetCapacity()) {
    00584       sb->UpdatePosition(wheel);
    00585       w->SetDirty();
    00586     }
    00587     return;
    00588   }
    00589
    00590   /* Scroll the widget attached to the scrollbar. */
    00591   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
    00592   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
    00593     sb->UpdatePosition(wheel);
    00594     w->SetDirty();
    00595   }
    00596 }
    00597
    00603 static bool MayBeShown(const Window *w)
    00604 {
    00605   /* If we're not modal, everything is okay. */
    00606   if (!HasModalProgress()) return true;
    00607
    00608   switch (w->window_class) {
    00609     case WC_MAIN_WINDOW:
    00610     case WC_MODAL_PROGRESS:
    00611     case WC_CONFIRM_POPUP_QUERY:
    00612       return true;
    00613
    00614     default:
    00615       return false;
    00616   }
    00617 }
    00618
    00631 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
    00632 {
    00633   const Window *v;
    00634   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
    00635     if (MayBeShown(v) &&
    00636         right > v->left &&
    00637         bottom > v->top &&
    00638         left < v->left + v->width &&
    00639         top < v->top + v->height) {
    00640       /* v and rectangle intersect with each other */
    00641       int x;
    00642
    00643       if (left < (x = v->left)) {
    00644         DrawOverlappedWindow(w, left, top, x, bottom);
    00645         DrawOverlappedWindow(w, x, top, right, bottom);
    00646         return;
    00647       }
    00648
    00649       if (right > (x = v->left + v->width)) {
    00650         DrawOverlappedWindow(w, left, top, x, bottom);
    00651         DrawOverlappedWindow(w, x, top, right, bottom);
    00652         return;
    00653       }
    00654
    00655       if (top < (x = v->top)) {
    00656         DrawOverlappedWindow(w, left, top, right, x);
    00657         DrawOverlappedWindow(w, left, x, right, bottom);
    00658         return;
    00659       }
    00660
    00661       if (bottom > (x = v->top + v->height)) {
    00662         DrawOverlappedWindow(w, left, top, right, x);
    00663         DrawOverlappedWindow(w, left, x, right, bottom);
    00664         return;
    00665       }
    00666
    00667       return;
    00668     }
    00669   }
    00670
    00671   /* Setup blitter, and dispatch a repaint event to window *wz */
    00672   DrawPixelInfo *dp = _cur_dpi;
    00673   dp->width = right - left;
    00674   dp->height = bottom - top;
    00675   dp->left = left - w->left;
    00676   dp->top = top - w->top;
    00677   dp->pitch = _screen.pitch;
    00678   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
    00679   dp->zoom = ZOOM_LVL_NORMAL;
    00680   w->OnPaint();
    00681 }
    00682
    00691 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
    00692 {
    00693   Window *w;
    00694   DrawPixelInfo bk;
    00695   _cur_dpi = &bk;
    00696
    00697   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00698     if (MayBeShown(w) &&
    00699         right > w->left &&
    00700         bottom > w->top &&
    00701         left < w->left + w->width &&
    00702         top < w->top + w->height) {
    00703       /* Window w intersects with the rectangle => needs repaint */
    00704       DrawOverlappedWindow(w, left, top, right, bottom);
    00705     }
    00706   }
    00707 }
    00708
    00713 void Window::SetDirty() const
    00714 {
    00715   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
    00716 }
    00717
    00724 void Window::ReInit(int rx, int ry)
    00725 {
    00726   this->SetDirty(); // Mark whole current window as dirty.
    00727
    00728   /* Save current size. */
    00729   int window_width  = this->width;
    00730   int window_height = this->height;
    00731
    00732   this->OnInit();
    00733   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
    00734   this->nested_root->SetupSmallestSize(this, false);
    00735   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
    00736   this->width  = this->nested_root->smallest_x;
    00737   this->height = this->nested_root->smallest_y;
    00738   this->resize.step_width  = this->nested_root->resize_x;
    00739   this->resize.step_height = this->nested_root->resize_y;
    00740
    00741   /* Resize as close to the original size + requested resize as possible. */
    00742   window_width  = max(window_width  + rx, this->width);
    00743   window_height = max(window_height + ry, this->height);
    00744   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
    00745   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
    00746   /* dx and dy has to go by step.. calculate it.
    00747    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
    00748   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
    00749   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
    00750
    00751   ResizeWindow(this, dx, dy);
    00752   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
    00753 }
    00754
    00760 void Window::SetShaded(bool make_shaded)
    00761 {
    00762   if (this->shade_select == NULL) return;
    00763
    00764   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
    00765   if (this->shade_select->shown_plane != desired) {
    00766     if (make_shaded) {
    00767       this->unshaded_size.width  = this->width;
    00768       this->unshaded_size.height = this->height;
    00769       this->shade_select->SetDisplayedPlane(desired);
    00770       this->ReInit(0, -this->height);
    00771     } else {
    00772       this->shade_select->SetDisplayedPlane(desired);
    00773       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
    00774       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
    00775       this->ReInit(dx, dy);
    00776     }
    00777   }
    00778 }
    00779
    00786 static Window *FindChildWindow(const Window *w, WindowClass wc)
    00787 {
    00788   Window *v;
    00789   FOR_ALL_WINDOWS_FROM_BACK(v) {
    00790     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
    00791   }
    00792
    00793   return NULL;
    00794 }
    00795
    00800 void Window::DeleteChildWindows(WindowClass wc) const
    00801 {
    00802   Window *child = FindChildWindow(this, wc);
    00803   while (child != NULL) {
    00804     delete child;
    00805     child = FindChildWindow(this, wc);
    00806   }
    00807 }
    00808
    00812 Window::~Window()
    00813 {
    00814   if (_thd.window_class == this->window_class &&
    00815       _thd.window_number == this->window_number) {
    00816     ResetObjectToPlace();
    00817   }
    00818
    00819   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
    00820   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
    00821
    00822   /* We can't scroll the window when it's closed. */
    00823   if (_last_scroll_window == this) _last_scroll_window = NULL;
    00824
    00825   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
    00826   if (_focused_window == this) _focused_window = NULL;
    00827
    00828   this->DeleteChildWindows();
    00829
    00830   if (this->viewport != NULL) DeleteWindowViewport(this);
    00831
    00832   this->SetDirty();
    00833
    00834   free(this->nested_array); // Contents is released through deletion of #nested_root.
    00835   delete this->nested_root;
    00836
    00837   this->window_class = WC_INVALID;
    00838 }
    00839
    00846 Window *FindWindowById(WindowClass cls, WindowNumber number)
    00847 {
    00848   Window *w;
    00849   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00850     if (w->window_class == cls && w->window_number == number) return w;
    00851   }
    00852
    00853   return NULL;
    00854 }
    00855
    00862 Window *FindWindowByClass(WindowClass cls)
    00863 {
    00864   Window *w;
    00865   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00866     if (w->window_class == cls) return w;
    00867   }
    00868
    00869   return NULL;
    00870 }
    00871
    00878 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
    00879 {
    00880   Window *w = FindWindowById(cls, number);
    00881   if (force || w == NULL ||
    00882       (w->flags & WF_STICKY) == 0) {
    00883     delete w;
    00884   }
    00885 }
    00886
    00891 void DeleteWindowByClass(WindowClass cls)
    00892 {
    00893   Window *w;
    00894
    00895 restart_search:
    00896   /* When we find the window to delete, we need to restart the search
    00897    * as deleting this window could cascade in deleting (many) others
    00898    * anywhere in the z-array */
    00899   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00900     if (w->window_class == cls) {
    00901       delete w;
    00902       goto restart_search;
    00903     }
    00904   }
    00905 }
    00906
    00913 void DeleteCompanyWindows(CompanyID id)
    00914 {
    00915   Window *w;
    00916
    00917 restart_search:
    00918   /* When we find the window to delete, we need to restart the search
    00919    * as deleting this window could cascade in deleting (many) others
    00920    * anywhere in the z-array */
    00921   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00922     if (w->owner == id) {
    00923       delete w;
    00924       goto restart_search;
    00925     }
    00926   }
    00927
    00928   /* Also delete the company specific windows that don't have a company-colour. */
    00929   DeleteWindowById(WC_BUY_COMPANY, id);
    00930 }
    00931
    00939 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
    00940 {
    00941   Window *w;
    00942   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00943     if (w->owner != old_owner) continue;
    00944
    00945     switch (w->window_class) {
    00946       case WC_COMPANY_COLOUR:
    00947       case WC_FINANCES:
    00948       case WC_STATION_LIST:
    00949       case WC_TRAINS_LIST:
    00950       case WC_ROADVEH_LIST:
    00951       case WC_SHIPS_LIST:
    00952       case WC_AIRCRAFT_LIST:
    00953       case WC_BUY_COMPANY:
    00954       case WC_COMPANY:
    00955       case WC_COMPANY_INFRASTRUCTURE:
    00956         continue;
    00957
    00958       default:
    00959         w->owner = new_owner;
    00960         break;
    00961     }
    00962   }
    00963 }
    00964
    00965 static void BringWindowToFront(Window *w);
    00966
    00974 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
    00975 {
    00976   Window *w = FindWindowById(cls, number);
    00977
    00978   if (w != NULL) {
    00979     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
    00980
    00981     w->SetWhiteBorder();
    00982     BringWindowToFront(w);
    00983     w->SetDirty();
    00984   }
    00985
    00986   return w;
    00987 }
    00988
    00989 static inline bool IsVitalWindow(const Window *w)
    00990 {
    00991   switch (w->window_class) {
    00992     case WC_MAIN_TOOLBAR:
    00993     case WC_STATUS_BAR:
    00994     case WC_NEWS_WINDOW:
    00995     case WC_SEND_NETWORK_MSG:
    00996       return true;
    00997
    00998     default:
    00999       return false;
    01000   }
    01001 }
    01002
    01011 static uint GetWindowZPriority(const Window *w)
    01012 {
    01013   assert(w->window_class != WC_INVALID);
    01014
    01015   uint z_priority = 0;
    01016
    01017   switch (w->window_class) {
    01018     case WC_ENDSCREEN:
    01019       ++z_priority;
    01020
    01021     case WC_HIGHSCORE:
    01022       ++z_priority;
    01023
    01024     case WC_TOOLTIPS:
    01025       ++z_priority;
    01026
    01027     case WC_DROPDOWN_MENU:
    01028       ++z_priority;
    01029
    01030     case WC_MAIN_TOOLBAR:
    01031     case WC_STATUS_BAR:
    01032       ++z_priority;
    01033
    01034     case WC_OSK:
    01035       ++z_priority;
    01036
    01037     case WC_QUERY_STRING:
    01038     case WC_SEND_NETWORK_MSG:
    01039       ++z_priority;
    01040
    01041     case WC_ERRMSG:
    01042     case WC_CONFIRM_POPUP_QUERY:
    01043     case WC_MODAL_PROGRESS:
    01044     case WC_NETWORK_STATUS_WINDOW:
    01045       ++z_priority;
    01046
    01047     case WC_GENERATE_LANDSCAPE:
    01048     case WC_SAVELOAD:
    01049     case WC_GAME_OPTIONS:
    01050     case WC_CUSTOM_CURRENCY:
    01051     case WC_NETWORK_WINDOW:
    01052     case WC_GRF_PARAMETERS:
    01053     case WC_AI_LIST:
    01054     case WC_AI_SETTINGS:
    01055     case WC_TEXTFILE:
    01056       ++z_priority;
    01057
    01058     case WC_CONSOLE:
    01059       ++z_priority;
    01060
    01061     case WC_NEWS_WINDOW:
    01062       ++z_priority;
    01063
    01064     default:
    01065       ++z_priority;
    01066
    01067     case WC_MAIN_WINDOW:
    01068       return z_priority;
    01069   }
    01070 }
    01071
    01076 static void AddWindowToZOrdering(Window *w)
    01077 {
    01078   assert(w->z_front == NULL && w->z_back == NULL);
    01079
    01080   if (_z_front_window == NULL) {
    01081     /* It's the only window. */
    01082     _z_front_window = _z_back_window = w;
    01083     w->z_front = w->z_back = NULL;
    01084   } else {
    01085     /* Search down the z-ordering for its location. */
    01086     Window *v = _z_front_window;
    01087     uint last_z_priority = UINT_MAX;
    01088     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
    01089       if (v->window_class != WC_INVALID) {
    01090         /* Sanity check z-ordering, while we're at it. */
    01091         assert(last_z_priority >= GetWindowZPriority(v));
    01092         last_z_priority = GetWindowZPriority(v);
    01093       }
    01094
    01095       v = v->z_back;
    01096     }
    01097
    01098     if (v == NULL) {
    01099       /* It's the new back window. */
    01100       w->z_front = _z_back_window;
    01101       w->z_back = NULL;
    01102       _z_back_window->z_back = w;
    01103       _z_back_window = w;
    01104     } else if (v == _z_front_window) {
    01105       /* It's the new front window. */
    01106       w->z_front = NULL;
    01107       w->z_back = _z_front_window;
    01108       _z_front_window->z_front = w;
    01109       _z_front_window = w;
    01110     } else {
    01111       /* It's somewhere else in the z-ordering. */
    01112       w->z_front = v->z_front;
    01113       w->z_back = v;
    01114       v->z_front->z_back = w;
    01115       v->z_front = w;
    01116     }
    01117   }
    01118 }
    01119
    01120
    01125 static void RemoveWindowFromZOrdering(Window *w)
    01126 {
    01127   if (w->z_front == NULL) {
    01128     assert(_z_front_window == w);
    01129     _z_front_window = w->z_back;
    01130   } else {
    01131     w->z_front->z_back = w->z_back;
    01132   }
    01133
    01134   if (w->z_back == NULL) {
    01135     assert(_z_back_window == w);
    01136     _z_back_window = w->z_front;
    01137   } else {
    01138     w->z_back->z_front = w->z_front;
    01139   }
    01140
    01141   w->z_front = w->z_back = NULL;
    01142 }
    01143
    01149 static void BringWindowToFront(Window *w)
    01150 {
    01151   RemoveWindowFromZOrdering(w);
    01152   AddWindowToZOrdering(w);
    01153
    01154   w->SetDirty();
    01155 }
    01156
    01165 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
    01166 {
    01167   /* Set up window properties; some of them are needed to set up smallest size below */
    01168   this->window_class = desc->cls;
    01169   this->SetWhiteBorder();
    01170   if (desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
    01171   this->owner = INVALID_OWNER;
    01172   this->nested_focus = NULL;
    01173   this->window_number = window_number;
    01174   this->desc_flags = desc->flags;
    01175
    01176   this->OnInit();
    01177   /* Initialize nested widget tree. */
    01178   if (this->nested_array == NULL) {
    01179     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01180     this->nested_root->SetupSmallestSize(this, true);
    01181   } else {
    01182     this->nested_root->SetupSmallestSize(this, false);
    01183   }
    01184   /* Initialize to smallest size. */
    01185   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
    01186
    01187   /* Further set up window properties,
    01188    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
    01189   this->resize.step_width  = this->nested_root->resize_x;
    01190   this->resize.step_height = this->nested_root->resize_y;
    01191
    01192   /* Give focus to the opened window unless a text box
    01193    * of focused window has focus (so we don't interrupt typing). But if the new
    01194    * window has a text box, then take focus anyway. */
    01195   if (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL) SetFocusedWindow(this);
    01196
    01197   /* Insert the window into the correct location in the z-ordering. */
    01198   AddWindowToZOrdering(this);
    01199 }
    01200
    01208 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
    01209 {
    01210   this->left = x;
    01211   this->top = y;
    01212   this->width = sm_width;
    01213   this->height = sm_height;
    01214 }
    01215
    01226 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
    01227 {
    01228   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
    01229   def_height = max(def_height, this->height);
    01230   /* Try to make windows smaller when our window is too small.
    01231    * w->(width|height) is normally the same as min_(width|height),
    01232    * but this way the GUIs can be made a little more dynamic;
    01233    * one can use the same spec for multiple windows and those
    01234    * can then determine the real minimum size of the window. */
    01235   if (this->width != def_width || this->height != def_height) {
    01236     /* Think about the overlapping toolbars when determining the minimum window size */
    01237     int free_height = _screen.height;
    01238     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
    01239     if (wt != NULL) free_height -= wt->height;
    01240     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01241     if (wt != NULL) free_height -= wt->height;
    01242
    01243     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
    01244     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
    01245
    01246     /* X and Y has to go by step.. calculate it.
    01247      * The cast to int is necessary else x/y are implicitly casted to
    01248      * unsigned int, which won't work. */
    01249     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
    01250     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
    01251
    01252     ResizeWindow(this, enlarge_x, enlarge_y);
    01253     /* ResizeWindow() calls this->OnResize(). */
    01254   } else {
    01255     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
    01256     this->OnResize();
    01257   }
    01258
    01259   int nx = this->left;
    01260   int ny = this->top;
    01261
    01262   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
    01263
    01264   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01265   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
    01266   nx = max(nx, 0);
    01267
    01268   if (this->viewport != NULL) {
    01269     this->viewport->left += nx - this->left;
    01270     this->viewport->top  += ny - this->top;
    01271   }
    01272   this->left = nx;
    01273   this->top = ny;
    01274
    01275   this->SetDirty();
    01276 }
    01277
    01289 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
    01290 {
    01291   int right  = width + left;
    01292   int bottom = height + top;
    01293
    01294   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
    01295   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
    01296
    01297   /* Make sure it is not obscured by any window. */
    01298   const Window *w;
    01299   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01300     if (w->window_class == WC_MAIN_WINDOW) continue;
    01301
    01302     if (right > w->left &&
    01303         w->left + w->width > left &&
    01304         bottom > w->top &&
    01305         w->top + w->height > top) {
    01306       return false;
    01307     }
    01308   }
    01309
    01310   pos.x = left;
    01311   pos.y = top;
    01312   return true;
    01313 }
    01314
    01326 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
    01327 {
    01328   /* Left part of the rectangle may be at most 1/4 off-screen,
    01329    * right part of the rectangle may be at most 1/2 off-screen
    01330    */
    01331   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
    01332   /* Bottom part of the rectangle may be at most 1/4 off-screen */
    01333   if (top < 22 || top > _screen.height - (height >> 2)) return false;
    01334
    01335   /* Make sure it is not obscured by any window. */
    01336   const Window *w;
    01337   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01338     if (w->window_class == WC_MAIN_WINDOW) continue;
    01339
    01340     if (left + width > w->left &&
    01341         w->left + w->width > left &&
    01342         top + height > w->top &&
    01343         w->top + w->height > top) {
    01344       return false;
    01345     }
    01346   }
    01347
    01348   pos.x = left;
    01349   pos.y = top;
    01350   return true;
    01351 }
    01352
    01359 static Point GetAutoPlacePosition(int width, int height)
    01360 {
    01361   Point pt;
    01362
    01363   /* First attempt, try top-left of the screen */
    01364   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
    01365   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
    01366
    01367   /* Second attempt, try around all existing windows with a distance of 2 pixels.
    01368    * The new window must be entirely on-screen, and not overlap with an existing window.
    01369    * Eight starting points are tried, two at each corner.
    01370    */
    01371   const Window *w;
    01372   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01373     if (w->window_class == WC_MAIN_WINDOW) continue;
    01374
    01375     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01376     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01377     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01378     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01379     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
    01380     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
    01381     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
    01382     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
    01383   }
    01384
    01385   /* Third attempt, try around all existing windows with a distance of 2 pixels.
    01386    * The new window may be partly off-screen, and must not overlap with an existing window.
    01387    * Only four starting points are tried.
    01388    */
    01389   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01390     if (w->window_class == WC_MAIN_WINDOW) continue;
    01391
    01392     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01393     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01394     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01395     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01396   }
    01397
    01398   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
    01399    * of (+5, +5)
    01400    */
    01401   int left = 0, top = 24;
    01402
    01403 restart:
    01404   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01405     if (w->left == left && w->top == top) {
    01406       left += 5;
    01407       top += 5;
    01408       goto restart;
    01409     }
    01410   }
    01411
    01412   pt.x = left;
    01413   pt.y = top;
    01414   return pt;
    01415 }
    01416
    01423 Point GetToolbarAlignedWindowPosition(int window_width)
    01424 {
    01425   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01426   assert(w != NULL);
    01427   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
    01428   return pt;
    01429 }
    01430
    01448 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01449 {
    01450   Point pt;
    01451   const Window *w;
    01452
    01453   int16 default_width  = max(desc->default_width,  sm_width);
    01454   int16 default_height = max(desc->default_height, sm_height);
    01455
    01456   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
    01457       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
    01458       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
    01459
    01460     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
    01461     if (pt.x > _screen.width + 10 - default_width) {
    01462       pt.x = (_screen.width + 10 - default_width) - 20;
    01463     }
    01464     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
    01465     return pt;
    01466   }
    01467
    01468   switch (desc->default_pos) {
    01469     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
    01470       return GetToolbarAlignedWindowPosition(default_width);
    01471
    01472     case WDP_AUTO: // Find a good automatic position for the window
    01473       return GetAutoPlacePosition(default_width, default_height);
    01474
    01475     case WDP_CENTER: // Centre the window horizontally
    01476       pt.x = (_screen.width - default_width) / 2;
    01477       pt.y = (_screen.height - default_height) / 2;
    01478       break;
    01479
    01480     case WDP_MANUAL:
    01481       pt.x = 0;
    01482       pt.y = 0;
    01483       break;
    01484
    01485     default:
    01486       NOT_REACHED();
    01487   }
    01488
    01489   return pt;
    01490 }
    01491
    01492 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01493 {
    01494   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
    01495 }
    01496
    01505 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
    01506 {
    01507   int biggest_index = -1;
    01508   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
    01509   this->nested_array_size = (uint)(biggest_index + 1);
    01510
    01511   if (fill_nested) {
    01512     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01513     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
    01514   }
    01515 }
    01516
    01522 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
    01523 {
    01524   this->InitializeData(desc, window_number);
    01525   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
    01526   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
    01527   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
    01528 }
    01529
    01535 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
    01536 {
    01537   this->CreateNestedTree(desc, false);
    01538   this->FinishInitNested(desc, window_number);
    01539 }
    01540
    01542 Window::Window() : scrolling_scrollbar(-1)
    01543 {
    01544 }
    01545
    01553 Window *FindWindowFromPt(int x, int y)
    01554 {
    01555   Window *w;
    01556   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01557     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
    01558       return w;
    01559     }
    01560   }
    01561
    01562   return NULL;
    01563 }
    01564
    01568 void InitWindowSystem()
    01569 {
    01570   IConsoleClose();
    01571
    01572   _z_back_window = NULL;
    01573   _z_front_window = NULL;
    01574   _focused_window = NULL;
    01575   _mouseover_last_w = NULL;
    01576   _last_scroll_window = NULL;
    01577   _scrolling_viewport = false;
    01578   _mouse_hovering = false;
    01579
    01580   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    01581   NWidgetScrollbar::InvalidateDimensionCache();
    01582
    01583   ShowFirstError();
    01584 }
    01585
    01589 void UnInitWindowSystem()
    01590 {
    01591   UnshowCriticalError();
    01592
    01593   Window *w;
    01594   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
    01595
    01596   for (w = _z_front_window; w != NULL; /* nothing */) {
    01597     Window *to_del = w;
    01598     w = w->z_back;
    01599     free(to_del);
    01600   }
    01601
    01602   _z_front_window = NULL;
    01603   _z_back_window = NULL;
    01604 }
    01605
    01609 void ResetWindowSystem()
    01610 {
    01611   UnInitWindowSystem();
    01612   InitWindowSystem();
    01613   _thd.Reset();
    01614 }
    01615
    01616 static void DecreaseWindowCounters()
    01617 {
    01618   Window *w;
    01619   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01620     if (_scroller_click_timeout == 0) {
    01621       /* Unclick scrollbar buttons if they are pressed. */
    01622       for (uint i = 0; i < w->nested_array_size; i++) {
    01623         NWidgetBase *nwid = w->nested_array[i];
    01624         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
    01625           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
    01626           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
    01627             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
    01628             w->scrolling_scrollbar = -1;
    01629             sb->SetDirty(w);
    01630           }
    01631         }
    01632       }
    01633     }
    01634
    01635     /* Handle editboxes */
    01636     for (SmallMap<int, QueryString*>::Pair *it = w->querystrings.Begin(); it != w->querystrings.End(); ++it) {
    01637       it->second->HandleEditBox(w, it->first);
    01638     }
    01639
    01640     w->OnMouseLoop();
    01641   }
    01642
    01643   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01644     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
    01645       CLRBITS(w->flags, WF_TIMEOUT);
    01646
    01647       w->OnTimeout();
    01648       w->RaiseButtons(true);
    01649     }
    01650   }
    01651 }
    01652
    01653 static void HandlePlacePresize()
    01654 {
    01655   if (_special_mouse_mode != WSM_PRESIZE) return;
    01656
    01657   Window *w = _thd.GetCallbackWnd();
    01658   if (w == NULL) return;
    01659
    01660   Point pt = GetTileBelowCursor();
    01661   if (pt.x == -1) {
    01662     _thd.selend.x = -1;
    01663     return;
    01664   }
    01665
    01666   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
    01667 }
    01668
    01673 static EventState HandleMouseDragDrop()
    01674 {
    01675   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
    01676
    01677   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
    01678
    01679   Window *w = _thd.GetCallbackWnd();
    01680   if (w != NULL) {
    01681     /* Send an event in client coordinates. */
    01682     Point pt;
    01683     pt.x = _cursor.pos.x - w->left;
    01684     pt.y = _cursor.pos.y - w->top;
    01685     if (_left_button_down) {
    01686       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01687     } else {
    01688       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01689     }
    01690   }
    01691
    01692   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
    01693   return ES_HANDLED;
    01694 }
    01695
    01697 static void HandleMouseOver()
    01698 {
    01699   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    01700
    01701   /* We changed window, put a MOUSEOVER event to the last window */
    01702   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
    01703     /* Reset mouse-over coordinates of previous window */
    01704     Point pt = { -1, -1 };
    01705     _mouseover_last_w->OnMouseOver(pt, 0);
    01706   }
    01707
    01708   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
    01709   _mouseover_last_w = w;
    01710
    01711   if (w != NULL) {
    01712     /* send an event in client coordinates. */
    01713     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
    01714     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
    01715     if (widget != NULL) w->OnMouseOver(pt, widget->index);
    01716   }
    01717 }
    01718
    01720 static const int MIN_VISIBLE_TITLE_BAR = 13;
    01721
    01723 enum PreventHideDirection {
    01724   PHD_UP,
    01725   PHD_DOWN,
    01726 };
    01727
    01738 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
    01739 {
    01740   if (v == NULL) return;
    01741
    01742   int v_bottom = v->top + v->height;
    01743   int v_right = v->left + v->width;
    01744   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.
    01745
    01746   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
    01747   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
    01748
    01749   /* Vertically, the rectangle is hidden behind v. */
    01750   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
    01751     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
    01752     return;
    01753   }
    01754   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
    01755     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
    01756     return;
    01757   }
    01758
    01759   /* Horizontally also hidden, force movement to a safe area. */
    01760   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
    01761     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
    01762   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
    01763     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
    01764   } else {
    01765     *ny = safe_y;
    01766   }
    01767 }
    01768
    01776 static void EnsureVisibleCaption(Window *w, int nx, int ny)
    01777 {
    01778   /* Search for the title bar rectangle. */
    01779   Rect caption_rect;
    01780   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
    01781   if (caption != NULL) {
    01782     caption_rect.left   = caption->pos_x;
    01783     caption_rect.right  = caption->pos_x + caption->current_x;
    01784     caption_rect.top    = caption->pos_y;
    01785     caption_rect.bottom = caption->pos_y + caption->current_y;
    01786
    01787     /* Make sure the window doesn't leave the screen */
    01788     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
    01789     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
    01790
    01791     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
    01792     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
    01793     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
    01794   }
    01795
    01796   if (w->viewport != NULL) {
    01797     w->viewport->left += nx - w->left;
    01798     w->viewport->top  += ny - w->top;
    01799   }
    01800
    01801   w->left = nx;
    01802   w->top  = ny;
    01803 }
    01804
    01815 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
    01816 {
    01817   if (delta_x != 0 || delta_y != 0) {
    01818     if (clamp_to_screen) {
    01819       /* Determine the new right/bottom position. If that is outside of the bounds of
    01820        * the resolution clamp it in such a manner that it stays within the bounds. */
    01821       int new_right  = w->left + w->width  + delta_x;
    01822       int new_bottom = w->top  + w->height + delta_y;
    01823       if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
    01824       if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
    01825     }
    01826
    01827     w->SetDirty();
    01828
    01829     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);
    01830     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);
    01831     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
    01832     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
    01833
    01834     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);
    01835     w->width  = w->nested_root->current_x;
    01836     w->height = w->nested_root->current_y;
    01837   }
    01838
    01839   EnsureVisibleCaption(w, w->left, w->top);
    01840
    01841   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
    01842   w->OnResize();
    01843   w->SetDirty();
    01844 }
    01845
    01851 int GetMainViewTop()
    01852 {
    01853   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01854   return (w == NULL) ? 0 : w->top + w->height;
    01855 }
    01856
    01862 int GetMainViewBottom()
    01863 {
    01864   Window *w = FindWindowById(WC_STATUS_BAR, 0);
    01865   return (w == NULL) ? _screen.height : w->top;
    01866 }
    01867
    01868 static bool _dragging_window;
    01869
    01874 static EventState HandleWindowDragging()
    01875 {
    01876   /* Get out immediately if no window is being dragged at all. */
    01877   if (!_dragging_window) return ES_NOT_HANDLED;
    01878
    01879   /* If button still down, but cursor hasn't moved, there is nothing to do. */
    01880   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
    01881
    01882   /* Otherwise find the window... */
    01883   Window *w;
    01884   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01885     if (w->flags & WF_DRAGGING) {
    01886       /* Stop the dragging if the left mouse button was released */
    01887       if (!_left_button_down) {
    01888         w->flags &= ~WF_DRAGGING;
    01889         break;
    01890       }
    01891
    01892       w->SetDirty();
    01893
    01894       int x = _cursor.pos.x + _drag_delta.x;
    01895       int y = _cursor.pos.y + _drag_delta.y;
    01896       int nx = x;
    01897       int ny = y;
    01898
    01899       if (_settings_client.gui.window_snap_radius != 0) {
    01900         const Window *v;
    01901
    01902         int hsnap = _settings_client.gui.window_snap_radius;
    01903         int vsnap = _settings_client.gui.window_snap_radius;
    01904         int delta;
    01905
    01906         FOR_ALL_WINDOWS_FROM_BACK(v) {
    01907           if (v == w) continue; // Don't snap at yourself
    01908
    01909           if (y + w->height > v->top && y < v->top + v->height) {
    01910             /* Your left border <-> other right border */
    01911             delta = abs(v->left + v->width - x);
    01912             if (delta <= hsnap) {
    01913               nx = v->left + v->width;
    01914               hsnap = delta;
    01915             }
    01916
    01917             /* Your right border <-> other left border */
    01918             delta = abs(v->left - x - w->width);
    01919             if (delta <= hsnap) {
    01920               nx = v->left - w->width;
    01921               hsnap = delta;
    01922             }
    01923           }
    01924
    01925           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
    01926             /* Your left border <-> other left border */
    01927             delta = abs(v->left - x);
    01928             if (delta <= hsnap) {
    01929               nx = v->left;
    01930               hsnap = delta;
    01931             }
    01932
    01933             /* Your right border <-> other right border */
    01934             delta = abs(v->left + v->width - x - w->width);
    01935             if (delta <= hsnap) {
    01936               nx = v->left + v->width - w->width;
    01937               hsnap = delta;
    01938             }
    01939           }
    01940
    01941           if (x + w->width > v->left && x < v->left + v->width) {
    01942             /* Your top border <-> other bottom border */
    01943             delta = abs(v->top + v->height - y);
    01944             if (delta <= vsnap) {
    01945               ny = v->top + v->height;
    01946               vsnap = delta;
    01947             }
    01948
    01949             /* Your bottom border <-> other top border */
    01950             delta = abs(v->top - y - w->height);
    01951             if (delta <= vsnap) {
    01952               ny = v->top - w->height;
    01953               vsnap = delta;
    01954             }
    01955           }
    01956
    01957           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
    01958             /* Your top border <-> other top border */
    01959             delta = abs(v->top - y);
    01960             if (delta <= vsnap) {
    01961               ny = v->top;
    01962               vsnap = delta;
    01963             }
    01964
    01965             /* Your bottom border <-> other bottom border */
    01966             delta = abs(v->top + v->height - y - w->height);
    01967             if (delta <= vsnap) {
    01968               ny = v->top + v->height - w->height;
    01969               vsnap = delta;
    01970             }
    01971           }
    01972         }
    01973       }
    01974
    01975       EnsureVisibleCaption(w, nx, ny);
    01976
    01977       w->SetDirty();
    01978       return ES_HANDLED;
    01979     } else if (w->flags & WF_SIZING) {
    01980       /* Stop the sizing if the left mouse button was released */
    01981       if (!_left_button_down) {
    01982         w->flags &= ~WF_SIZING;
    01983         w->SetDirty();
    01984         break;
    01985       }
    01986
    01987       /* Compute difference in pixels between cursor position and reference point in the window.
    01988        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
    01989        */
    01990       int x, y = _cursor.pos.y - _drag_delta.y;
    01991       if (w->flags & WF_SIZING_LEFT) {
    01992         x = _drag_delta.x - _cursor.pos.x;
    01993       } else {
    01994         x = _cursor.pos.x - _drag_delta.x;
    01995       }
    01996
    01997       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
    01998       if (w->resize.step_width  == 0) x = 0;
    01999       if (w->resize.step_height == 0) y = 0;
    02000
    02001       /* Check the resize button won't go past the bottom of the screen */
    02002       if (w->top + w->height + y > _screen.height) {
    02003         y = _screen.height - w->height - w->top;
    02004       }
    02005
    02006       /* X and Y has to go by step.. calculate it.
    02007        * The cast to int is necessary else x/y are implicitly casted to
    02008        * unsigned int, which won't work. */
    02009       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
    02010       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
    02011
    02012       /* Check that we don't go below the minimum set size */
    02013       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
    02014         x = w->nested_root->smallest_x - w->width;
    02015       }
    02016       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
    02017         y = w->nested_root->smallest_y - w->height;
    02018       }
    02019
    02020       /* Window already on size */
    02021       if (x == 0 && y == 0) return ES_HANDLED;
    02022
    02023       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
    02024       _drag_delta.y += y;
    02025       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
    02026         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
    02027         w->SetDirty();
    02028         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
    02029         /* ResizeWindow() below ensures marking new position as dirty. */
    02030       } else {
    02031         _drag_delta.x += x;
    02032       }
    02033
    02034       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
    02035       ResizeWindow(w, x, y);
    02036       return ES_HANDLED;
    02037     }
    02038   }
    02039
    02040   _dragging_window = false;
    02041   return ES_HANDLED;
    02042 }
    02043
    02048 static void StartWindowDrag(Window *w)
    02049 {
    02050   w->flags |= WF_DRAGGING;
    02051   w->flags &= ~WF_CENTERED;
    02052   _dragging_window = true;
    02053
    02054   _drag_delta.x = w->left - _cursor.pos.x;
    02055   _drag_delta.y = w->top  - _cursor.pos.y;
    02056
    02057   BringWindowToFront(w);
    02058   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    02059 }
    02060
    02066 static void StartWindowSizing(Window *w, bool to_left)
    02067 {
    02068   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
    02069   w->flags &= ~WF_CENTERED;
    02070   _dragging_window = true;
    02071
    02072   _drag_delta.x = _cursor.pos.x;
    02073   _drag_delta.y = _cursor.pos.y;
    02074
    02075   BringWindowToFront(w);
    02076   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    02077 }
    02078
    02083 static EventState HandleScrollbarScrolling()
    02084 {
    02085   Window *w;
    02086   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02087     if (w->scrolling_scrollbar >= 0) {
    02088       /* Abort if no button is clicked any more. */
    02089       if (!_left_button_down) {
    02090         w->scrolling_scrollbar = -1;
    02091         w->SetDirty();
    02092         return ES_HANDLED;
    02093       }
    02094
    02095       int i;
    02096       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
    02097       bool rtl = false;
    02098
    02099       if (sb->type == NWID_HSCROLLBAR) {
    02100         i = _cursor.pos.x - _cursorpos_drag_start.x;
    02101         rtl = _current_text_dir == TD_RTL;
    02102       } else {
    02103         i = _cursor.pos.y - _cursorpos_drag_start.y;
    02104       }
    02105
    02106       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
    02107         if (_scroller_click_timeout == 1) {
    02108           _scroller_click_timeout = 3;
    02109           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
    02110           w->SetDirty();
    02111         }
    02112         return ES_HANDLED;
    02113       }
    02114
    02115       /* Find the item we want to move to and make sure it's inside bounds. */
    02116       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
    02117       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
    02118       if (pos != sb->GetPosition()) {
    02119         sb->SetPosition(pos);
    02120         w->SetDirty();
    02121       }
    02122       return ES_HANDLED;
    02123     }
    02124   }
    02125
    02126   return ES_NOT_HANDLED;
    02127 }
    02128
    02133 static EventState HandleViewportScroll()
    02134 {
    02135   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02136
    02137   if (!_scrolling_viewport) return ES_NOT_HANDLED;
    02138
    02139   /* When we don't have a last scroll window we are starting to scroll.
    02140    * When the last scroll window and this are not the same we went
    02141    * outside of the window and should not left-mouse scroll anymore. */
    02142   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    02143
    02144   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
    02145     _cursor.fix_at = false;
    02146     _scrolling_viewport = false;
    02147     _last_scroll_window = NULL;
    02148     return ES_NOT_HANDLED;
    02149   }
    02150
    02151   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
    02152     /* If the main window is following a vehicle, then first let go of it! */
    02153     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
    02154     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
    02155     return ES_NOT_HANDLED;
    02156   }
    02157
    02158   Point delta;
    02159   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
    02160     delta.x = -_cursor.delta.x;
    02161     delta.y = -_cursor.delta.y;
    02162   } else {
    02163     delta.x = _cursor.delta.x;
    02164     delta.y = _cursor.delta.y;
    02165   }
    02166
    02167   if (scrollwheel_scrolling) {
    02168     /* We are using scrollwheels for scrolling */
    02169     delta.x = _cursor.h_wheel;
    02170     delta.y = _cursor.v_wheel;
    02171     _cursor.v_wheel = 0;
    02172     _cursor.h_wheel = 0;
    02173   }
    02174
    02175   /* Create a scroll-event and send it to the window */
    02176   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
    02177
    02178   _cursor.delta.x = 0;
    02179   _cursor.delta.y = 0;
    02180   return ES_HANDLED;
    02181 }
    02182
    02193 static bool MaybeBringWindowToFront(Window *w)
    02194 {
    02195   bool bring_to_front = false;
    02196
    02197   if (w->window_class == WC_MAIN_WINDOW ||
    02198       IsVitalWindow(w) ||
    02199       w->window_class == WC_TOOLTIPS ||
    02200       w->window_class == WC_DROPDOWN_MENU) {
    02201     return true;
    02202   }
    02203
    02204   /* Use unshaded window size rather than current size for shaded windows. */
    02205   int w_width  = w->width;
    02206   int w_height = w->height;
    02207   if (w->IsShaded()) {
    02208     w_width  = w->unshaded_size.width;
    02209     w_height = w->unshaded_size.height;
    02210   }
    02211
    02212   Window *u;
    02213   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
    02214     /* A modal child will prevent the activation of the parent window */
    02215     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
    02216       u->SetWhiteBorder();
    02217       u->SetDirty();
    02218       return false;
    02219     }
    02220
    02221     if (u->window_class == WC_MAIN_WINDOW ||
    02222         IsVitalWindow(u) ||
    02223         u->window_class == WC_TOOLTIPS ||
    02224         u->window_class == WC_DROPDOWN_MENU) {
    02225       continue;
    02226     }
    02227
    02228     /* Window sizes don't interfere, leave z-order alone */
    02229     if (w->left + w_width <= u->left ||
    02230         u->left + u->width <= w->left ||
    02231         w->top  + w_height <= u->top ||
    02232         u->top + u->height <= w->top) {
    02233       continue;
    02234     }
    02235
    02236     bring_to_front = true;
    02237   }
    02238
    02239   if (bring_to_front) BringWindowToFront(w);
    02240   return true;
    02241 }
    02242
    02251 EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
    02252 {
    02253   EventState state = ES_NOT_HANDLED;
    02254
    02255   QueryString *query = this->GetQueryString(wid);
    02256   if (query == NULL) return state;
    02257
    02258   int action = QueryString::ACTION_NOTHING;
    02259
    02260   switch (query->HandleEditBoxKey(this, wid, key, keycode, state)) {
    02261     case HEBR_EDITING:
    02262       this->SetWidgetDirty(wid);
    02263       this->OnEditboxChanged(wid);
    02264       break;
    02265
    02266     case HEBR_CURSOR:
    02267       this->SetWidgetDirty(wid);
    02268       /* For the OSK also invalidate the parent window */
    02269       if (this->window_class == WC_OSK) this->InvalidateData();
    02270       break;
    02271
    02272     case HEBR_CONFIRM:
    02273       if (this->window_class == WC_OSK) {
    02274         this->OnClick(Point(), WID_OSK_OK, 1);
    02275       } else if (query->ok_button >= 0) {
    02276         this->OnClick(Point(), query->ok_button, 1);
    02277       } else {
    02278         action = query->ok_button;
    02279       }
    02280       break;
    02281
    02282     case HEBR_CANCEL:
    02283       if (this->window_class == WC_OSK) {
    02284         this->OnClick(Point(), WID_OSK_CANCEL, 1);
    02285       } else if (query->cancel_button >= 0) {
    02286         this->OnClick(Point(), query->cancel_button, 1);
    02287       } else {
    02288         action = query->cancel_button;
    02289       }
    02290       break;
    02291
    02292     default: break;
    02293   }
    02294
    02295   switch (action) {
    02296     case QueryString::ACTION_DESELECT:
    02297       this->UnfocusFocusedWidget();
    02298       break;
    02299
    02300     case QueryString::ACTION_CLEAR:
    02301       query->text.DeleteAll();
    02302       this->SetWidgetDirty(wid);
    02303       this->OnEditboxChanged(wid);
    02304       break;
    02305
    02306     default:
    02307       break;
    02308   }
    02309
    02310   return state;
    02311 }
    02312
    02317 void HandleKeypress(uint32 raw_key)
    02318 {
    02319   /* World generation is multithreaded and messes with companies.
    02320    * But there is no company related window open anyway, so _current_company is not used. */
    02321   assert(HasModalProgress() || IsLocalCompany());
    02322
    02323   /* Setup event */
    02324   uint16 key     = GB(raw_key,  0, 16);
    02325   uint16 keycode = GB(raw_key, 16, 16);
    02326
    02327   /*
    02328    * The Unicode standard defines an area called the private use area. Code points in this
    02329    * area are reserved for private use and thus not portable between systems. For instance,
    02330    * Apple defines code points for the arrow keys in this area, but these are only printable
    02331    * on a system running OS X. We don't want these keys to show up in text fields and such,
    02332    * and thus we have to clear the unicode character when we encounter such a key.
    02333    */
    02334   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
    02335
    02336   /*
    02337    * If both key and keycode is zero, we don't bother to process the event.
    02338    */
    02339   if (key == 0 && keycode == 0) return;
    02340
    02341   /* Check if the focused window has a focused editbox */
    02342   if (EditBoxInGlobalFocus()) {
    02343     /* All input will in this case go to the focused editbox */
    02344     if (_focused_window->window_class == WC_CONSOLE) {
    02345       if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02346     } else {
    02347       if (_focused_window->HandleEditBoxKey(_focused_window->nested_focus->index, key, keycode) == ES_HANDLED) return;
    02348     }
    02349   }
    02350
    02351   /* Call the event, start with the uppermost window, but ignore the toolbar. */
    02352   Window *w;
    02353   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02354     if (w->window_class == WC_MAIN_TOOLBAR) continue;
    02355     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02356   }
    02357
    02358   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    02359   /* When there is no toolbar w is null, check for that */
    02360   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02361
    02362   HandleGlobalHotkeys(key, keycode);
    02363 }
    02364
    02368 void HandleCtrlChanged()
    02369 {
    02370   /* Call the event, start with the uppermost window. */
    02371   Window *w;
    02372   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02373     if (w->OnCTRLStateChange() == ES_HANDLED) return;
    02374   }
    02375 }
    02376
    02383 static int _input_events_this_tick = 0;
    02384
    02389 static void HandleAutoscroll()
    02390 {
    02391   if (_game_mode == GM_MENU || HasModalProgress()) return;
    02392   if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
    02393   if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
    02394
    02395   int x = _cursor.pos.x;
    02396   int y = _cursor.pos.y;
    02397   Window *w = FindWindowFromPt(x, y);
    02398   if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
    02399   if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
    02400
    02401   ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02402   if (vp == NULL) return;
    02403
    02404   x -= vp->left;
    02405   y -= vp->top;
    02406
    02407   /* here allows scrolling in both x and y axis */
    02408 #define scrollspeed 3
    02409   if (x - 15 < 0) {
    02410     w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
    02411   } else if (15 - (vp->width - x) > 0) {
    02412     w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
    02413   }
    02414   if (y - 15 < 0) {
    02415     w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
    02416   } else if (15 - (vp->height - y) > 0) {
    02417     w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
    02418   }
    02419 #undef scrollspeed
    02420 }
    02421
    02422 enum MouseClick {
    02423   MC_NONE = 0,
    02424   MC_LEFT,
    02425   MC_RIGHT,
    02426   MC_DOUBLE_LEFT,
    02427   MC_HOVER,
    02428
    02429   MAX_OFFSET_DOUBLE_CLICK = 5,
    02430   TIME_BETWEEN_DOUBLE_CLICK = 500,
    02431   MAX_OFFSET_HOVER = 5,
    02432 };
    02433 extern EventState VpHandlePlaceSizingDrag();
    02434
    02435 static void ScrollMainViewport(int x, int y)
    02436 {
    02437   if (_game_mode != GM_MENU) {
    02438     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
    02439     assert(w);
    02440
    02441     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
    02442     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
    02443   }
    02444 }
    02445
    02455 static const int8 scrollamt[16][2] = {
    02456   { 0,  0},
    02457   {-2,  0},
    02458   { 0, -2},
    02459   {-2, -1},
    02460   { 2,  0},
    02461   { 0,  0},
    02462   { 2, -1},
    02463   { 0, -2},
    02464   { 0,  2},
    02465   {-2,  1},
    02466   { 0,  0},
    02467   {-2,  0},
    02468   { 2,  1},
    02469   { 0,  2},
    02470   { 2,  0},
    02471   { 0,  0},
    02472 };
    02473
    02474 static void HandleKeyScrolling()
    02475 {
    02476   /*
    02477    * Check that any of the dirkeys is pressed and that the focused window
    02478    * doesn't have an edit-box as focused widget.
    02479    */
    02480   if (_dirkeys && !EditBoxInGlobalFocus()) {
    02481     int factor = _shift_pressed ? 50 : 10;
    02482     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
    02483   }
    02484 }
    02485
    02486 static void MouseLoop(MouseClick click, int mousewheel)
    02487 {
    02488   /* World generation is multithreaded and messes with companies.
    02489    * But there is no company related window open anyway, so _current_company is not used. */
    02490   assert(HasModalProgress() || IsLocalCompany());
    02491
    02492   HandlePlacePresize();
    02493   UpdateTileSelection();
    02494
    02495   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
    02496   if (HandleMouseDragDrop()      == ES_HANDLED) return;
    02497   if (HandleWindowDragging()     == ES_HANDLED) return;
    02498   if (HandleScrollbarScrolling() == ES_HANDLED) return;
    02499   if (HandleViewportScroll()     == ES_HANDLED) return;
    02500
    02501   HandleMouseOver();
    02502
    02503   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02504   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
    02505
    02506   int x = _cursor.pos.x;
    02507   int y = _cursor.pos.y;
    02508   Window *w = FindWindowFromPt(x, y);
    02509   if (w == NULL) return;
    02510
    02511   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
    02512   ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02513
    02514   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
    02515   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
    02516
    02517   if (mousewheel != 0) {
    02518     /* Send mousewheel event to window */
    02519     w->OnMouseWheel(mousewheel);
    02520
    02521     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
    02522     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
    02523   }
    02524
    02525   if (vp != NULL) {
    02526     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
    02527     switch (click) {
    02528       case MC_DOUBLE_LEFT:
    02529       case MC_LEFT:
    02530         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
    02531         if (!HandleViewportClicked(vp, x, y) &&
    02532             !(w->flags & WF_DISABLE_VP_SCROLL) &&
    02533             _settings_client.gui.left_mouse_btn_scrolling) {
    02534           _scrolling_viewport = true;
    02535           _cursor.fix_at = false;
    02536         }
    02537         break;
    02538
    02539       case MC_RIGHT:
    02540         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
    02541           _scrolling_viewport = true;
    02542           _cursor.fix_at = true;
    02543
    02544           /* clear 2D scrolling caches before we start a 2D scroll */
    02545           _cursor.h_wheel = 0;
    02546           _cursor.v_wheel = 0;
    02547         }
    02548         break;
    02549
    02550       default:
    02551         break;
    02552     }
    02553   } else {
    02554     switch (click) {
    02555       case MC_LEFT:
    02556       case MC_DOUBLE_LEFT:
    02557         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
    02558         break;
    02559
    02560       default:
    02561         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
    02562         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
    02563          * Simulate a right button click so we can get started. */
    02564         /* FALL THROUGH */
    02565
    02566       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
    02567
    02568       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
    02569     }
    02570   }
    02571 }
    02572
    02576 void HandleMouseEvents()
    02577 {
    02578   /* World generation is multithreaded and messes with companies.
    02579    * But there is no company related window open anyway, so _current_company is not used. */
    02580   assert(HasModalProgress() || IsLocalCompany());
    02581
    02582   static int double_click_time = 0;
    02583   static Point double_click_pos = {0, 0};
    02584
    02585   /* Mouse event? */
    02586   MouseClick click = MC_NONE;
    02587   if (_left_button_down && !_left_button_clicked) {
    02588     click = MC_LEFT;
    02589     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
    02590         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
    02591         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
    02592       click = MC_DOUBLE_LEFT;
    02593     }
    02594     double_click_time = _realtime_tick;
    02595     double_click_pos = _cursor.pos;
    02596     _left_button_clicked = true;
    02597     _input_events_this_tick++;
    02598   } else if (_right_button_clicked) {
    02599     _right_button_clicked = false;
    02600     click = MC_RIGHT;
    02601     _input_events_this_tick++;
    02602   }
    02603
    02604   int mousewheel = 0;
    02605   if (_cursor.wheel) {
    02606     mousewheel = _cursor.wheel;
    02607     _cursor.wheel = 0;
    02608     _input_events_this_tick++;
    02609   }
    02610
    02611   static uint32 hover_time = 0;
    02612   static Point hover_pos = {0, 0};
    02613
    02614   if (_settings_client.gui.hover_delay > 0) {
    02615     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
    02616         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
    02617         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
    02618       hover_pos = _cursor.pos;
    02619       hover_time = _realtime_tick;
    02620       _mouse_hovering = false;
    02621     } else {
    02622       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
    02623         click = MC_HOVER;
    02624         _input_events_this_tick++;
    02625         _mouse_hovering = true;
    02626       }
    02627     }
    02628   }
    02629
    02630   /* Handle sprite picker before any GUI interaction */
    02631   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
    02632     /* Next realtime tick? Then redraw has finished */
    02633     _newgrf_debug_sprite_picker.mode = SPM_NONE;
    02634     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
    02635   }
    02636
    02637   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
    02638     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
    02639     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    02640     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
    02641     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
    02642     _newgrf_debug_sprite_picker.sprites.Clear();
    02643     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
    02644     MarkWholeScreenDirty();
    02645   } else {
    02646     MouseLoop(click, mousewheel);
    02647   }
    02648
    02649   /* We have moved the mouse the required distance,
    02650    * no need to move it at any later time. */
    02651   _cursor.delta.x = 0;
    02652   _cursor.delta.y = 0;
    02653 }
    02654
    02658 static void CheckSoftLimit()
    02659 {
    02660   if (_settings_client.gui.window_soft_limit == 0) return;
    02661
    02662   for (;;) {
    02663     uint deletable_count = 0;
    02664     Window *w, *last_deletable = NULL;
    02665     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02666       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
    02667
    02668       last_deletable = w;
    02669       deletable_count++;
    02670     }
    02671
    02672     /* We've not reached the soft limit yet. */
    02673     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
    02674
    02675     assert(last_deletable != NULL);
    02676     delete last_deletable;
    02677   }
    02678 }
    02679
    02683 void InputLoop()
    02684 {
    02685   /* World generation is multithreaded and messes with companies.
    02686    * But there is no company related window open anyway, so _current_company is not used. */
    02687   assert(HasModalProgress() || IsLocalCompany());
    02688
    02689   CheckSoftLimit();
    02690   HandleKeyScrolling();
    02691
    02692   /* Do the actual free of the deleted windows. */
    02693   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
    02694     Window *w = v;
    02695     v = v->z_back;
    02696
    02697     if (w->window_class != WC_INVALID) continue;
    02698
    02699     RemoveWindowFromZOrdering(w);
    02700     free(w);
    02701   }
    02702
    02703   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
    02704   DecreaseWindowCounters();
    02705
    02706   if (_input_events_this_tick != 0) {
    02707     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
    02708     _input_events_this_tick = 0;
    02709     /* there were some inputs this tick, don't scroll ??? */
    02710     return;
    02711   }
    02712
    02713   /* HandleMouseEvents was already called for this tick */
    02714   HandleMouseEvents();
    02715   HandleAutoscroll();
    02716 }
    02717
    02721 void UpdateWindows()
    02722 {
    02723   Window *w;
    02724
    02725   static int highlight_timer = 1;
    02726   if (--highlight_timer == 0) {
    02727     highlight_timer = 15;
    02728     _window_highlight_colour = !_window_highlight_colour;
    02729   }
    02730
    02731   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02732     w->ProcessScheduledInvalidations();
    02733     w->ProcessHighlightedInvalidations();
    02734   }
    02735
    02736   static int we4_timer = 0;
    02737   int t = we4_timer + 1;
    02738
    02739   if (t >= 100) {
    02740     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02741       w->OnHundredthTick();
    02742     }
    02743     t = 0;
    02744   }
    02745   we4_timer = t;
    02746
    02747   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02748     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
    02749       CLRBITS(w->flags, WF_WHITE_BORDER);
    02750       w->SetDirty();
    02751     }
    02752   }
    02753
    02754   DrawDirtyBlocks();
    02755
    02756   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02757     /* Update viewport only if window is not shaded. */
    02758     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
    02759   }
    02760   NetworkDrawChatMessage();
    02761   /* Redraw mouse cursor in case it was hidden */
    02762   DrawMouseCursor();
    02763 }
    02764
    02770 void SetWindowDirty(WindowClass cls, WindowNumber number)
    02771 {
    02772   const Window *w;
    02773   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02774     if (w->window_class == cls && w->window_number == number) w->SetDirty();
    02775   }
    02776 }
    02777
    02784 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
    02785 {
    02786   const Window *w;
    02787   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02788     if (w->window_class == cls && w->window_number == number) {
    02789       w->SetWidgetDirty(widget_index);
    02790     }
    02791   }
    02792 }
    02793
    02798 void SetWindowClassesDirty(WindowClass cls)
    02799 {
    02800   Window *w;
    02801   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02802     if (w->window_class == cls) w->SetDirty();
    02803   }
    02804 }
    02805
    02811 void Window::InvalidateData(int data, bool gui_scope)
    02812 {
    02813   this->SetDirty();
    02814   if (!gui_scope) {
    02815     /* Schedule GUI-scope invalidation for next redraw. */
    02816     *this->scheduled_invalidation_data.Append() = data;
    02817   }
    02818   this->OnInvalidateData(data, gui_scope);
    02819 }
    02820
    02824 void Window::ProcessScheduledInvalidations()
    02825 {
    02826   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
    02827     this->OnInvalidateData(*data, true);
    02828   }
    02829   this->scheduled_invalidation_data.Clear();
    02830 }
    02831
    02835 void Window::ProcessHighlightedInvalidations()
    02836 {
    02837   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
    02838
    02839   for (uint i = 0; i < this->nested_array_size; i++) {
    02840     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
    02841   }
    02842 }
    02843
    02870 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
    02871 {
    02872   Window *w;
    02873   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02874     if (w->window_class == cls && w->window_number == number) {
    02875       w->InvalidateData(data, gui_scope);
    02876     }
    02877   }
    02878 }
    02879
    02888 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
    02889 {
    02890   Window *w;
    02891
    02892   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02893     if (w->window_class == cls) {
    02894       w->InvalidateData(data, gui_scope);
    02895     }
    02896   }
    02897 }
    02898
    02902 void CallWindowTickEvent()
    02903 {
    02904   Window *w;
    02905   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02906     w->OnTick();
    02907   }
    02908 }
    02909
    02916 void DeleteNonVitalWindows()
    02917 {
    02918   Window *w;
    02919
    02920 restart_search:
    02921   /* When we find the window to delete, we need to restart the search
    02922    * as deleting this window could cascade in deleting (many) others
    02923    * anywhere in the z-array */
    02924   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02925     if (w->window_class != WC_MAIN_WINDOW &&
    02926         w->window_class != WC_SELECT_GAME &&
    02927         w->window_class != WC_MAIN_TOOLBAR &&
    02928         w->window_class != WC_STATUS_BAR &&
    02929         w->window_class != WC_TOOLTIPS &&
    02930         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
    02931
    02932       delete w;
    02933       goto restart_search;
    02934     }
    02935   }
    02936 }
    02937
    02945 void DeleteAllNonVitalWindows()
    02946 {
    02947   Window *w;
    02948
    02949   /* Delete every window except for stickied ones, then sticky ones as well */
    02950   DeleteNonVitalWindows();
    02951
    02952 restart_search:
    02953   /* When we find the window to delete, we need to restart the search
    02954    * as deleting this window could cascade in deleting (many) others
    02955    * anywhere in the z-array */
    02956   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02957     if (w->flags & WF_STICKY) {
    02958       delete w;
    02959       goto restart_search;
    02960     }
    02961   }
    02962 }
    02963
    02968 void DeleteConstructionWindows()
    02969 {
    02970   Window *w;
    02971
    02972 restart_search:
    02973   /* When we find the window to delete, we need to restart the search
    02974    * as deleting this window could cascade in deleting (many) others
    02975    * anywhere in the z-array */
    02976   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02977     if (w->desc_flags & WDF_CONSTRUCTION) {
    02978       delete w;
    02979       goto restart_search;
    02980     }
    02981   }
    02982
    02983   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
    02984 }
    02985
    02987 void HideVitalWindows()
    02988 {
    02989   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
    02990   DeleteWindowById(WC_STATUS_BAR, 0);
    02991 }
    02992
    02994 void ReInitAllWindows()
    02995 {
    02996   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    02997   NWidgetScrollbar::InvalidateDimensionCache();
    02998
    02999   Window *w;
    03000   FOR_ALL_WINDOWS_FROM_BACK(w) {
    03001     w->ReInit();
    03002   }
    03003 #ifdef ENABLE_NETWORK
    03004   void NetworkReInitChatBoxSize();
    03005   NetworkReInitChatBoxSize();
    03006 #endif
    03007
    03008   /* Make sure essential parts of all windows are visible */
    03009   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
    03010   MarkWholeScreenDirty();
    03011 }
    03012
    03020 static int PositionWindow(Window *w, WindowClass clss, int setting)
    03021 {
    03022   if (w == NULL || w->window_class != clss) {
    03023     w = FindWindowById(clss, 0);
    03024   }
    03025   if (w == NULL) return 0;
    03026
    03027   int old_left = w->left;
    03028   switch (setting) {
    03029     case 1:  w->left = (_screen.width - w->width) / 2; break;
    03030     case 2:  w->left = _screen.width - w->width; break;
    03031     default: w->left = 0; break;
    03032   }
    03033   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
    03034   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
    03035   return w->left;
    03036 }
    03037
    03043 int PositionMainToolbar(Window *w)
    03044 {
    03045   DEBUG(misc, 5, "Repositioning Main Toolbar...");
    03046   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
    03047 }
    03048
    03054 int PositionStatusbar(Window *w)
    03055 {
    03056   DEBUG(misc, 5, "Repositioning statusbar...");
    03057   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
    03058 }
    03059
    03065 int PositionNewsMessage(Window *w)
    03066 {
    03067   DEBUG(misc, 5, "Repositioning news message...");
    03068   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
    03069 }
    03070
    03076 int PositionNetworkChatWindow(Window *w)
    03077 {
    03078   DEBUG(misc, 5, "Repositioning network chat window...");
    03079   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
    03080 }
    03081
    03082
    03088 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
    03089 {
    03090   Window *w;
    03091   FOR_ALL_WINDOWS_FROM_BACK(w) {
    03092     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
    03093       w->viewport->follow_vehicle = to_index;
    03094       w->SetDirty();
    03095     }
    03096   }
    03097 }
    03098
    03099
    03105 void RelocateAllWindows(int neww, int newh)
    03106 {
    03107   Window *w;
    03108
    03109   FOR_ALL_WINDOWS_FROM_BACK(w) {
    03110     int left, top;
    03111
    03112     if (w->window_class == WC_MAIN_WINDOW) {
    03113       ViewPort *vp = w->viewport;
    03114       vp->width = w->width = neww;
    03115       vp->height = w->height = newh;
    03116       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
    03117       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
    03118       continue; // don't modify top,left
    03119     }
    03120
    03121     /* XXX - this probably needs something more sane. For example specifying
    03122      * in a 'backup'-desc that the window should always be centered. */
    03123     switch (w->window_class) {
    03124       case WC_BOOTSTRAP:
    03125         ResizeWindow(w, neww, newh);
    03126         continue;
    03127
    03128       case WC_MAIN_TOOLBAR:
    03129         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0, false);
    03130
    03131         top = w->top;
    03132         left = PositionMainToolbar(w); // changes toolbar orientation
    03133         break;
    03134
    03135       case WC_NEWS_WINDOW:
    03136         top = newh - w->height;
    03137         left = PositionNewsMessage(w);
    03138         break;
    03139
    03140       case WC_STATUS_BAR:
    03141         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0, false);
    03142
    03143         top = newh - w->height;
    03144         left = PositionStatusbar(w);
    03145         break;
    03146
    03147       case WC_SEND_NETWORK_MSG:
    03148         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
    03149         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
    03150         left = PositionNetworkChatWindow(w);
    03151         break;
    03152
    03153       case WC_CONSOLE:
    03154         IConsoleResize(w);
    03155         continue;
    03156
    03157       default: {
    03158         if (w->flags & WF_CENTERED) {
    03159           top = (newh - w->height) >> 1;
    03160           left = (neww - w->width) >> 1;
    03161           break;
    03162         }
    03163
    03164         left = w->left;
    03165         if (left + (w->width >> 1) >= neww) left = neww - w->width;
    03166         if (left < 0) left = 0;
    03167
    03168         top = w->top;
    03169         if (top + (w->height >> 1) >= newh) top = newh - w->height;
    03170         break;
    03171       }
    03172     }
    03173
    03174     EnsureVisibleCaption(w, left, top);
    03175   }
    03176 }
    03177
    03183 PickerWindowBase::~PickerWindowBase()
    03184 {
    03185   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
    03186   ResetObjectToPlace();
    03187 }