# window.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include <stdarg.h>
    00014 #include "company_func.h"
    00015 #include "gfx_func.h"
    00016 #include "console_func.h"
    00017 #include "console_gui.h"
    00018 #include "viewport_func.h"
    00019 #include "progress.h"
    00020 #include "blitter/factory.hpp"
    00021 #include "zoom_func.h"
    00022 #include "vehicle_base.h"
    00023 #include "window_func.h"
    00024 #include "tilehighlight_func.h"
    00025 #include "network/network.h"
    00026 #include "querystring_gui.h"
    00027 #include "widgets/dropdown_func.h"
    00028 #include "strings_func.h"
    00029 #include "settings_type.h"
    00030 #include "newgrf_debug.h"
    00031 #include "hotkeys.h"
    00032 #include "toolbar_gui.h"
    00033 #include "statusbar_gui.h"
    00034 #include "error.h"
    00035 #include "game/game.hpp"
    00036
    00037
    00038 static Point _drag_delta;
    00039 static Window *_mouseover_last_w = NULL;
    00040 static Window *_last_scroll_window = NULL;
    00041
    00043 Window *_z_front_window = NULL;
    00045 Window *_z_back_window  = NULL;
    00046
    00048 bool _window_highlight_colour = false;
    00049
    00050 /*
    00051  * Window that currently has focus. - The main purpose is to generate
    00052  * #FocusLost events, not to give next window in z-order focus when a
    00053  * window is closed.
    00054  */
    00055 Window *_focused_window;
    00056
    00057 Point _cursorpos_drag_start;
    00058
    00059 int _scrollbar_start_pos;
    00060 int _scrollbar_size;
    00061 byte _scroller_click_timeout = 0;
    00062
    00063 bool _scrolling_viewport;
    00064 bool _mouse_hovering;
    00065
    00066 SpecialMouseMode _special_mouse_mode;
    00067
    00069 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
    00070       WindowClass window_class, WindowClass parent_class, uint32 flags,
    00071       const NWidgetPart *nwid_parts, int16 nwid_length) :
    00072   default_pos(def_pos),
    00073   default_width(def_width),
    00074   default_height(def_height),
    00075   cls(window_class),
    00076   parent_cls(parent_class),
    00077   flags(flags),
    00078   nwid_parts(nwid_parts),
    00079   nwid_length(nwid_length)
    00080 {
    00081 }
    00082
    00083 WindowDesc::~WindowDesc()
    00084 {
    00085 }
    00086
    00096 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
    00097 {
    00098   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
    00099   if (line_height < 0) line_height = wid->resize_y;
    00100   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
    00101   return (clickpos - (int)wid->pos_y - padding) / line_height;
    00102 }
    00103
    00107 void Window::DisableAllWidgetHighlight()
    00108 {
    00109   for (uint i = 0; i < this->nested_array_size; i++) {
    00110     NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
    00111     if (nwid == NULL) continue;
    00112
    00113     if (nwid->IsHighlighted()) {
    00114       nwid->SetHighlighted(TC_INVALID);
    00115       this->SetWidgetDirty(i);
    00116     }
    00117   }
    00118
    00119   CLRBITS(this->flags, WF_HIGHLIGHTED);
    00120 }
    00121
    00127 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
    00128 {
    00129   assert(widget_index < this->nested_array_size);
    00130
    00131   NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
    00132   if (nwid == NULL) return;
    00133
    00134   nwid->SetHighlighted(highlighted_colour);
    00135   this->SetWidgetDirty(widget_index);
    00136
    00137   if (highlighted_colour != TC_INVALID) {
    00138     /* If we set a highlight, the window has a highlight */
    00139     this->flags |= WF_HIGHLIGHTED;
    00140   } else {
    00141     /* If we disable a highlight, check all widgets if anyone still has a highlight */
    00142     bool valid = false;
    00143     for (uint i = 0; i < this->nested_array_size; i++) {
    00144       NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
    00145       if (nwid == NULL) continue;
    00146       if (!nwid->IsHighlighted()) continue;
    00147
    00148       valid = true;
    00149     }
    00150     /* If nobody has a highlight, disable the flag on the window */
    00151     if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
    00152   }
    00153 }
    00154
    00160 bool Window::IsWidgetHighlighted(byte widget_index) const
    00161 {
    00162   assert(widget_index < this->nested_array_size);
    00163
    00164   const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
    00165   if (nwid == NULL) return false;
    00166
    00167   return nwid->IsHighlighted();
    00168 }
    00169
    00175 const Scrollbar *Window::GetScrollbar(uint widnum) const
    00176 {
    00177   return this->GetWidget<NWidgetScrollbar>(widnum);
    00178 }
    00179
    00185 Scrollbar *Window::GetScrollbar(uint widnum)
    00186 {
    00187   return this->GetWidget<NWidgetScrollbar>(widnum);
    00188 }
    00189
    00190
    00195 void SetFocusedWindow(Window *w)
    00196 {
    00197   if (_focused_window == w) return;
    00198
    00199   /* Invalidate focused widget */
    00200   if (_focused_window != NULL) {
    00201     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
    00202   }
    00203
    00204   /* Remember which window was previously focused */
    00205   Window *old_focused = _focused_window;
    00206   _focused_window = w;
    00207
    00208   /* So we can inform it that it lost focus */
    00209   if (old_focused != NULL) old_focused->OnFocusLost();
    00210   if (_focused_window != NULL) _focused_window->OnFocus();
    00211 }
    00212
    00218 static bool EditBoxInGlobalFocus()
    00219 {
    00220   if (_focused_window == NULL) return false;
    00221
    00222   /* The console does not have an edit box so a special case is needed. */
    00223   if (_focused_window->window_class == WC_CONSOLE) return true;
    00224
    00225   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
    00226 }
    00227
    00231 void Window::UnfocusFocusedWidget()
    00232 {
    00233   if (this->nested_focus != NULL) {
    00234     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00235     this->nested_focus->SetDirty(this);
    00236     this->nested_focus = NULL;
    00237   }
    00238 }
    00239
    00245 bool Window::SetFocusedWidget(byte widget_index)
    00246 {
    00247   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
    00248   if (widget_index >= this->nested_array_size) return false;
    00249
    00250   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
    00251   if (this->nested_focus != NULL) {
    00252     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
    00253
    00254     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00255     this->nested_focus->SetDirty(this);
    00256   }
    00257   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
    00258   return true;
    00259 }
    00260
    00268 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
    00269 {
    00270   va_list wdg_list;
    00271
    00272   va_start(wdg_list, widgets);
    00273
    00274   while (widgets != WIDGET_LIST_END) {
    00275     SetWidgetDisabledState(widgets, disab_stat);
    00276     widgets = va_arg(wdg_list, int);
    00277   }
    00278
    00279   va_end(wdg_list);
    00280 }
    00281
    00287 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
    00288 {
    00289   va_list wdg_list;
    00290
    00291   va_start(wdg_list, widgets);
    00292
    00293   while (widgets != WIDGET_LIST_END) {
    00294     SetWidgetLoweredState(widgets, lowered_stat);
    00295     widgets = va_arg(wdg_list, int);
    00296   }
    00297
    00298   va_end(wdg_list);
    00299 }
    00300
    00305 void Window::RaiseButtons(bool autoraise)
    00306 {
    00307   for (uint i = 0; i < this->nested_array_size; i++) {
    00308     if (this->nested_array[i] != NULL && (this->nested_array[i]->type & ~WWB_PUSHBUTTON) < WWT_LAST &&
    00309         (!autoraise || (this->nested_array[i]->type & WWB_PUSHBUTTON)) && this->IsWidgetLowered(i)) {
    00310       this->RaiseWidget(i);
    00311       this->SetWidgetDirty(i);
    00312     }
    00313   }
    00314 }
    00315
    00320 void Window::SetWidgetDirty(byte widget_index) const
    00321 {
    00322   /* Sometimes this function is called before the window is even fully initialized */
    00323   if (this->nested_array == NULL) return;
    00324
    00325   this->nested_array[widget_index]->SetDirty(this);
    00326 }
    00327
    00333 void Window::HandleButtonClick(byte widget)
    00334 {
    00335   this->LowerWidget(widget);
    00336   this->SetTimeout();
    00337   this->SetWidgetDirty(widget);
    00338 }
    00339
    00340 static void StartWindowDrag(Window *w);
    00341 static void StartWindowSizing(Window *w, bool to_left);
    00342
    00350 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
    00351 {
    00352   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
    00353   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
    00354
    00355   bool focused_widget_changed = false;
    00356   /* If clicked on a window that previously did dot have focus */
    00357   if (_focused_window != w &&                 // We already have focus, right?
    00358       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
    00359       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
    00360     focused_widget_changed = true;
    00361     if (_focused_window != NULL) {
    00362       _focused_window->OnFocusLost();
    00363
    00364       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
    00365       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
    00366     }
    00367     SetFocusedWindow(w);
    00368     w->OnFocus();
    00369   }
    00370
    00371   if (nw == NULL) return; // exit if clicked outside of widgets
    00372
    00373   /* don't allow any interaction if the button has been disabled */
    00374   if (nw->IsDisabled()) return;
    00375
    00376   int widget_index = nw->index;
    00377
    00378   /* Clicked on a widget that is not disabled.
    00379    * So unless the clicked widget is the caption bar, change focus to this widget */
    00380   if (widget_type != WWT_CAPTION) {
    00381     /* Close the OSK window if a edit box loses focus */
    00382     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
    00383       DeleteWindowById(WC_OSK, 0);
    00384     }
    00385
    00386     /* focused_widget_changed is 'now' only true if the window this widget
    00387      * is in gained focus. In that case it must remain true, also if the
    00388      * local widget focus did not change. As such it's the logical-or of
    00389      * both changed states.
    00390      *
    00391      * If this is not preserved, then the OSK window would be opened when
    00392      * a user has the edit box focused and then click on another window and
    00393      * then back again on the edit box (to type some text).
    00394      */
    00395     focused_widget_changed |= w->SetFocusedWidget(widget_index);
    00396   }
    00397
    00398   /* Close any child drop down menus. If the button pressed was the drop down
    00399    * list's own button, then we should not process the click any further. */
    00400   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
    00401
    00402   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
    00403
    00404   switch (widget_type) {
    00405     case NWID_VSCROLLBAR:
    00406     case NWID_HSCROLLBAR:
    00407       ScrollbarClickHandler(w, nw, x, y);
    00408       break;
    00409
    00410     case WWT_EDITBOX:
    00411       if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
    00412         /* Open the OSK window if clicked on an edit box */
    00413         QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
    00414         if (qs != NULL) {
    00415           qs->OnOpenOSKWindow(widget_index);
    00416         }
    00417       }
    00418       break;
    00419
    00420     case WWT_CLOSEBOX: // 'X'
    00421       delete w;
    00422       return;
    00423
    00424     case WWT_CAPTION: // 'Title bar'
    00425       StartWindowDrag(w);
    00426       return;
    00427
    00428     case WWT_RESIZEBOX:
    00429       /* When the resize widget is on the left size of the window
    00430        * we assume that that button is used to resize to the left. */
    00431       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
    00432       nw->SetDirty(w);
    00433       return;
    00434
    00435     case WWT_DEBUGBOX:
    00436       w->ShowNewGRFInspectWindow();
    00437       break;
    00438
    00439     case WWT_SHADEBOX:
    00440       nw->SetDirty(w);
    00441       w->SetShaded(!w->IsShaded());
    00442       return;
    00443
    00444     case WWT_STICKYBOX:
    00445       w->flags ^= WF_STICKY;
    00446       nw->SetDirty(w);
    00447       return;
    00448
    00449     default:
    00450       break;
    00451   }
    00452
    00453   /* Widget has no index, so the window is not interested in it. */
    00454   if (widget_index < 0) return;
    00455
    00456   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
    00457   if (w->IsWidgetHighlighted(widget_index)) {
    00458     w->SetWidgetHighlight(widget_index, TC_INVALID);
    00459     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
    00460   }
    00461
    00462   Point pt = { x, y };
    00463   w->OnClick(pt, widget_index, click_count);
    00464 }
    00465
    00472 static void DispatchRightClickEvent(Window *w, int x, int y)
    00473 {
    00474   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00475   if (wid == NULL) return;
    00476
    00477   /* No widget to handle, or the window is not interested in it. */
    00478   if (wid->index >= 0) {
    00479     Point pt = { x, y };
    00480     if (w->OnRightClick(pt, wid->index)) return;
    00481   }
    00482
    00483   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
    00484 }
    00485
    00492 static void DispatchHoverEvent(Window *w, int x, int y)
    00493 {
    00494   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00495
    00496   /* No widget to handle */
    00497   if (wid == NULL) return;
    00498
    00499   /* Show the tooltip if there is any */
    00500   if (wid->tool_tip != 0) {
    00501     GuiShowTooltips(w, wid->tool_tip);
    00502     return;
    00503   }
    00504
    00505   /* Widget has no index, so the window is not interested in it. */
    00506   if (wid->index < 0) return;
    00507
    00508   Point pt = { x, y };
    00509   w->OnHover(pt, wid->index);
    00510 }
    00511
    00519 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
    00520 {
    00521   if (nwid == NULL) return;
    00522
    00523   /* Using wheel on caption/shade-box shades or unshades the window. */
    00524   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
    00525     w->SetShaded(wheel < 0);
    00526     return;
    00527   }
    00528
    00529   /* Wheeling a vertical scrollbar. */
    00530   if (nwid->type == NWID_VSCROLLBAR) {
    00531     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
    00532     if (sb->GetCount() > sb->GetCapacity()) {
    00533       sb->UpdatePosition(wheel);
    00534       w->SetDirty();
    00535     }
    00536     return;
    00537   }
    00538
    00539   /* Scroll the widget attached to the scrollbar. */
    00540   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
    00541   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
    00542     sb->UpdatePosition(wheel);
    00543     w->SetDirty();
    00544   }
    00545 }
    00546
    00552 static bool MayBeShown(const Window *w)
    00553 {
    00554   /* If we're not modal, everything is okay. */
    00555   if (!HasModalProgress()) return true;
    00556
    00557   switch (w->window_class) {
    00558     case WC_MAIN_WINDOW:
    00559     case WC_MODAL_PROGRESS:
    00560     case WC_QUERY_STRING:
    00561       return true;
    00562
    00563     default:
    00564       return false;
    00565   }
    00566 }
    00567
    00580 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
    00581 {
    00582   const Window *v;
    00583   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
    00584     if (MayBeShown(v) &&
    00585         right > v->left &&
    00586         bottom > v->top &&
    00587         left < v->left + v->width &&
    00588         top < v->top + v->height) {
    00589       /* v and rectangle intersect with each other */
    00590       int x;
    00591
    00592       if (left < (x = v->left)) {
    00593         DrawOverlappedWindow(w, left, top, x, bottom);
    00594         DrawOverlappedWindow(w, x, top, right, bottom);
    00595         return;
    00596       }
    00597
    00598       if (right > (x = v->left + v->width)) {
    00599         DrawOverlappedWindow(w, left, top, x, bottom);
    00600         DrawOverlappedWindow(w, x, top, right, bottom);
    00601         return;
    00602       }
    00603
    00604       if (top < (x = v->top)) {
    00605         DrawOverlappedWindow(w, left, top, right, x);
    00606         DrawOverlappedWindow(w, left, x, right, bottom);
    00607         return;
    00608       }
    00609
    00610       if (bottom > (x = v->top + v->height)) {
    00611         DrawOverlappedWindow(w, left, top, right, x);
    00612         DrawOverlappedWindow(w, left, x, right, bottom);
    00613         return;
    00614       }
    00615
    00616       return;
    00617     }
    00618   }
    00619
    00620   /* Setup blitter, and dispatch a repaint event to window *wz */
    00621   DrawPixelInfo *dp = _cur_dpi;
    00622   dp->width = right - left;
    00623   dp->height = bottom - top;
    00624   dp->left = left - w->left;
    00625   dp->top = top - w->top;
    00626   dp->pitch = _screen.pitch;
    00627   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
    00628   dp->zoom = ZOOM_LVL_NORMAL;
    00629   w->OnPaint();
    00630 }
    00631
    00640 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
    00641 {
    00642   Window *w;
    00643   DrawPixelInfo bk;
    00644   _cur_dpi = &bk;
    00645
    00646   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00647     if (MayBeShown(w) &&
    00648         right > w->left &&
    00649         bottom > w->top &&
    00650         left < w->left + w->width &&
    00651         top < w->top + w->height) {
    00652       /* Window w intersects with the rectangle => needs repaint */
    00653       DrawOverlappedWindow(w, left, top, right, bottom);
    00654     }
    00655   }
    00656 }
    00657
    00662 void Window::SetDirty() const
    00663 {
    00664   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
    00665 }
    00666
    00673 void Window::ReInit(int rx, int ry)
    00674 {
    00675   this->SetDirty(); // Mark whole current window as dirty.
    00676
    00677   /* Save current size. */
    00678   int window_width  = this->width;
    00679   int window_height = this->height;
    00680
    00681   this->OnInit();
    00682   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
    00683   this->nested_root->SetupSmallestSize(this, false);
    00684   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
    00685   this->width  = this->nested_root->smallest_x;
    00686   this->height = this->nested_root->smallest_y;
    00687   this->resize.step_width  = this->nested_root->resize_x;
    00688   this->resize.step_height = this->nested_root->resize_y;
    00689
    00690   /* Resize as close to the original size + requested resize as possible. */
    00691   window_width  = max(window_width  + rx, this->width);
    00692   window_height = max(window_height + ry, this->height);
    00693   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
    00694   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
    00695   /* dx and dy has to go by step.. calculate it.
    00696    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
    00697   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
    00698   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
    00699
    00700   ResizeWindow(this, dx, dy);
    00701   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
    00702 }
    00703
    00709 void Window::SetShaded(bool make_shaded)
    00710 {
    00711   if (this->shade_select == NULL) return;
    00712
    00713   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
    00714   if (this->shade_select->shown_plane != desired) {
    00715     if (make_shaded) {
    00716       this->unshaded_size.width  = this->width;
    00717       this->unshaded_size.height = this->height;
    00718       this->shade_select->SetDisplayedPlane(desired);
    00719       this->ReInit(0, -this->height);
    00720     } else {
    00721       this->shade_select->SetDisplayedPlane(desired);
    00722       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
    00723       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
    00724       this->ReInit(dx, dy);
    00725     }
    00726   }
    00727 }
    00728
    00735 static Window *FindChildWindow(const Window *w, WindowClass wc)
    00736 {
    00737   Window *v;
    00738   FOR_ALL_WINDOWS_FROM_BACK(v) {
    00739     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
    00740   }
    00741
    00742   return NULL;
    00743 }
    00744
    00749 void Window::DeleteChildWindows(WindowClass wc) const
    00750 {
    00751   Window *child = FindChildWindow(this, wc);
    00752   while (child != NULL) {
    00753     delete child;
    00754     child = FindChildWindow(this, wc);
    00755   }
    00756 }
    00757
    00761 Window::~Window()
    00762 {
    00763   if (_thd.window_class == this->window_class &&
    00764       _thd.window_number == this->window_number) {
    00765     ResetObjectToPlace();
    00766   }
    00767
    00768   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
    00769   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
    00770
    00771   /* We can't scroll the window when it's closed. */
    00772   if (_last_scroll_window == this) _last_scroll_window = NULL;
    00773
    00774   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
    00775   if (_focused_window == this) _focused_window = NULL;
    00776
    00777   this->DeleteChildWindows();
    00778
    00779   if (this->viewport != NULL) DeleteWindowViewport(this);
    00780
    00781   this->SetDirty();
    00782
    00783   free(this->nested_array); // Contents is released through deletion of #nested_root.
    00784   delete this->nested_root;
    00785
    00786   this->window_class = WC_INVALID;
    00787 }
    00788
    00795 Window *FindWindowById(WindowClass cls, WindowNumber number)
    00796 {
    00797   Window *w;
    00798   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00799     if (w->window_class == cls && w->window_number == number) return w;
    00800   }
    00801
    00802   return NULL;
    00803 }
    00804
    00811 Window *FindWindowByClass(WindowClass cls)
    00812 {
    00813   Window *w;
    00814   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00815     if (w->window_class == cls) return w;
    00816   }
    00817
    00818   return NULL;
    00819 }
    00820
    00827 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
    00828 {
    00829   Window *w = FindWindowById(cls, number);
    00830   if (force || w == NULL ||
    00831       (w->flags & WF_STICKY) == 0) {
    00832     delete w;
    00833   }
    00834 }
    00835
    00840 void DeleteWindowByClass(WindowClass cls)
    00841 {
    00842   Window *w;
    00843
    00844 restart_search:
    00845   /* When we find the window to delete, we need to restart the search
    00846    * as deleting this window could cascade in deleting (many) others
    00847    * anywhere in the z-array */
    00848   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00849     if (w->window_class == cls) {
    00850       delete w;
    00851       goto restart_search;
    00852     }
    00853   }
    00854 }
    00855
    00862 void DeleteCompanyWindows(CompanyID id)
    00863 {
    00864   Window *w;
    00865
    00866 restart_search:
    00867   /* When we find the window to delete, we need to restart the search
    00868    * as deleting this window could cascade in deleting (many) others
    00869    * anywhere in the z-array */
    00870   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00871     if (w->owner == id) {
    00872       delete w;
    00873       goto restart_search;
    00874     }
    00875   }
    00876
    00877   /* Also delete the company specific windows that don't have a company-colour. */
    00878   DeleteWindowById(WC_BUY_COMPANY, id);
    00879 }
    00880
    00888 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
    00889 {
    00890   Window *w;
    00891   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00892     if (w->owner != old_owner) continue;
    00893
    00894     switch (w->window_class) {
    00895       case WC_COMPANY_COLOUR:
    00896       case WC_FINANCES:
    00897       case WC_STATION_LIST:
    00898       case WC_TRAINS_LIST:
    00899       case WC_ROADVEH_LIST:
    00900       case WC_SHIPS_LIST:
    00901       case WC_AIRCRAFT_LIST:
    00902       case WC_BUY_COMPANY:
    00903       case WC_COMPANY:
    00904       case WC_COMPANY_INFRASTRUCTURE:
    00905         continue;
    00906
    00907       default:
    00908         w->owner = new_owner;
    00909         break;
    00910     }
    00911   }
    00912 }
    00913
    00914 static void BringWindowToFront(Window *w);
    00915
    00923 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
    00924 {
    00925   Window *w = FindWindowById(cls, number);
    00926
    00927   if (w != NULL) {
    00928     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
    00929
    00930     w->SetWhiteBorder();
    00931     BringWindowToFront(w);
    00932     w->SetDirty();
    00933   }
    00934
    00935   return w;
    00936 }
    00937
    00938 static inline bool IsVitalWindow(const Window *w)
    00939 {
    00940   switch (w->window_class) {
    00941     case WC_MAIN_TOOLBAR:
    00942     case WC_STATUS_BAR:
    00943     case WC_NEWS_WINDOW:
    00944     case WC_SEND_NETWORK_MSG:
    00945       return true;
    00946
    00947     default:
    00948       return false;
    00949   }
    00950 }
    00951
    00960 static uint GetWindowZPriority(const Window *w)
    00961 {
    00962   assert(w->window_class != WC_INVALID);
    00963
    00964   uint z_priority = 0;
    00965
    00966   switch (w->window_class) {
    00967     case WC_ENDSCREEN:
    00968       ++z_priority;
    00969
    00970     case WC_HIGHSCORE:
    00971       ++z_priority;
    00972
    00973     case WC_TOOLTIPS:
    00974       ++z_priority;
    00975
    00976     case WC_DROPDOWN_MENU:
    00977       ++z_priority;
    00978
    00979     case WC_MAIN_TOOLBAR:
    00980     case WC_STATUS_BAR:
    00981       ++z_priority;
    00982
    00983     case WC_OSK:
    00984       ++z_priority;
    00985
    00986     case WC_QUERY_STRING:
    00987     case WC_SEND_NETWORK_MSG:
    00988       ++z_priority;
    00989
    00990     case WC_ERRMSG:
    00991     case WC_CONFIRM_POPUP_QUERY:
    00992     case WC_MODAL_PROGRESS:
    00993     case WC_NETWORK_STATUS_WINDOW:
    00994       ++z_priority;
    00995
    00996     case WC_GENERATE_LANDSCAPE:
    00997     case WC_SAVELOAD:
    00998     case WC_GAME_OPTIONS:
    00999     case WC_CUSTOM_CURRENCY:
    01000     case WC_NETWORK_WINDOW:
    01001     case WC_GRF_PARAMETERS:
    01002     case WC_AI_LIST:
    01003     case WC_AI_SETTINGS:
    01004     case WC_TEXTFILE:
    01005       ++z_priority;
    01006
    01007     case WC_CONSOLE:
    01008       ++z_priority;
    01009
    01010     case WC_NEWS_WINDOW:
    01011       ++z_priority;
    01012
    01013     default:
    01014       ++z_priority;
    01015
    01016     case WC_MAIN_WINDOW:
    01017       return z_priority;
    01018   }
    01019 }
    01020
    01025 static void AddWindowToZOrdering(Window *w)
    01026 {
    01027   assert(w->z_front == NULL && w->z_back == NULL);
    01028
    01029   if (_z_front_window == NULL) {
    01030     /* It's the only window. */
    01031     _z_front_window = _z_back_window = w;
    01032     w->z_front = w->z_back = NULL;
    01033   } else {
    01034     /* Search down the z-ordering for its location. */
    01035     Window *v = _z_front_window;
    01036     uint last_z_priority = UINT_MAX;
    01037     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
    01038       if (v->window_class != WC_INVALID) {
    01039         /* Sanity check z-ordering, while we're at it. */
    01040         assert(last_z_priority >= GetWindowZPriority(v));
    01041         last_z_priority = GetWindowZPriority(v);
    01042       }
    01043
    01044       v = v->z_back;
    01045     }
    01046
    01047     if (v == NULL) {
    01048       /* It's the new back window. */
    01049       w->z_front = _z_back_window;
    01050       w->z_back = NULL;
    01051       _z_back_window->z_back = w;
    01052       _z_back_window = w;
    01053     } else if (v == _z_front_window) {
    01054       /* It's the new front window. */
    01055       w->z_front = NULL;
    01056       w->z_back = _z_front_window;
    01057       _z_front_window->z_front = w;
    01058       _z_front_window = w;
    01059     } else {
    01060       /* It's somewhere else in the z-ordering. */
    01061       w->z_front = v->z_front;
    01062       w->z_back = v;
    01063       v->z_front->z_back = w;
    01064       v->z_front = w;
    01065     }
    01066   }
    01067 }
    01068
    01069
    01074 static void RemoveWindowFromZOrdering(Window *w)
    01075 {
    01076   if (w->z_front == NULL) {
    01077     assert(_z_front_window == w);
    01078     _z_front_window = w->z_back;
    01079   } else {
    01080     w->z_front->z_back = w->z_back;
    01081   }
    01082
    01083   if (w->z_back == NULL) {
    01084     assert(_z_back_window == w);
    01085     _z_back_window = w->z_front;
    01086   } else {
    01087     w->z_back->z_front = w->z_front;
    01088   }
    01089
    01090   w->z_front = w->z_back = NULL;
    01091 }
    01092
    01098 static void BringWindowToFront(Window *w)
    01099 {
    01100   RemoveWindowFromZOrdering(w);
    01101   AddWindowToZOrdering(w);
    01102
    01103   w->SetDirty();
    01104 }
    01105
    01114 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
    01115 {
    01116   /* Set up window properties; some of them are needed to set up smallest size below */
    01117   this->window_class = desc->cls;
    01118   this->SetWhiteBorder();
    01119   if (desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
    01120   this->owner = INVALID_OWNER;
    01121   this->nested_focus = NULL;
    01122   this->window_number = window_number;
    01123   this->desc_flags = desc->flags;
    01124
    01125   this->OnInit();
    01126   /* Initialize nested widget tree. */
    01127   if (this->nested_array == NULL) {
    01128     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01129     this->nested_root->SetupSmallestSize(this, true);
    01130   } else {
    01131     this->nested_root->SetupSmallestSize(this, false);
    01132   }
    01133   /* Initialize to smallest size. */
    01134   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
    01135
    01136   /* Further set up window properties,
    01137    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
    01138   this->resize.step_width  = this->nested_root->resize_x;
    01139   this->resize.step_height = this->nested_root->resize_y;
    01140
    01141   /* Give focus to the opened window unless it is the OSK window or a text box
    01142    * of focused window has focus (so we don't interrupt typing). But if the new
    01143    * window has a text box, then take focus anyway. */
    01144   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
    01145
    01146   /* Insert the window into the correct location in the z-ordering. */
    01147   AddWindowToZOrdering(this);
    01148 }
    01149
    01157 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
    01158 {
    01159   this->left = x;
    01160   this->top = y;
    01161   this->width = sm_width;
    01162   this->height = sm_height;
    01163 }
    01164
    01175 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
    01176 {
    01177   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
    01178   def_height = max(def_height, this->height);
    01179   /* Try to make windows smaller when our window is too small.
    01180    * w->(width|height) is normally the same as min_(width|height),
    01181    * but this way the GUIs can be made a little more dynamic;
    01182    * one can use the same spec for multiple windows and those
    01183    * can then determine the real minimum size of the window. */
    01184   if (this->width != def_width || this->height != def_height) {
    01185     /* Think about the overlapping toolbars when determining the minimum window size */
    01186     int free_height = _screen.height;
    01187     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
    01188     if (wt != NULL) free_height -= wt->height;
    01189     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01190     if (wt != NULL) free_height -= wt->height;
    01191
    01192     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
    01193     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
    01194
    01195     /* X and Y has to go by step.. calculate it.
    01196      * The cast to int is necessary else x/y are implicitly casted to
    01197      * unsigned int, which won't work. */
    01198     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
    01199     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
    01200
    01201     ResizeWindow(this, enlarge_x, enlarge_y);
    01202     /* ResizeWindow() calls this->OnResize(). */
    01203   } else {
    01204     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
    01205     this->OnResize();
    01206   }
    01207
    01208   int nx = this->left;
    01209   int ny = this->top;
    01210
    01211   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
    01212
    01213   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01214   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
    01215   nx = max(nx, 0);
    01216
    01217   if (this->viewport != NULL) {
    01218     this->viewport->left += nx - this->left;
    01219     this->viewport->top  += ny - this->top;
    01220   }
    01221   this->left = nx;
    01222   this->top = ny;
    01223
    01224   this->SetDirty();
    01225 }
    01226
    01238 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
    01239 {
    01240   int right  = width + left;
    01241   int bottom = height + top;
    01242
    01243   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
    01244   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
    01245
    01246   /* Make sure it is not obscured by any window. */
    01247   const Window *w;
    01248   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01249     if (w->window_class == WC_MAIN_WINDOW) continue;
    01250
    01251     if (right > w->left &&
    01252         w->left + w->width > left &&
    01253         bottom > w->top &&
    01254         w->top + w->height > top) {
    01255       return false;
    01256     }
    01257   }
    01258
    01259   pos.x = left;
    01260   pos.y = top;
    01261   return true;
    01262 }
    01263
    01275 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
    01276 {
    01277   /* Left part of the rectangle may be at most 1/4 off-screen,
    01278    * right part of the rectangle may be at most 1/2 off-screen
    01279    */
    01280   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
    01281   /* Bottom part of the rectangle may be at most 1/4 off-screen */
    01282   if (top < 22 || top > _screen.height - (height >> 2)) return false;
    01283
    01284   /* Make sure it is not obscured by any window. */
    01285   const Window *w;
    01286   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01287     if (w->window_class == WC_MAIN_WINDOW) continue;
    01288
    01289     if (left + width > w->left &&
    01290         w->left + w->width > left &&
    01291         top + height > w->top &&
    01292         w->top + w->height > top) {
    01293       return false;
    01294     }
    01295   }
    01296
    01297   pos.x = left;
    01298   pos.y = top;
    01299   return true;
    01300 }
    01301
    01308 static Point GetAutoPlacePosition(int width, int height)
    01309 {
    01310   Point pt;
    01311
    01312   /* First attempt, try top-left of the screen */
    01313   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
    01314   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
    01315
    01316   /* Second attempt, try around all existing windows with a distance of 2 pixels.
    01317    * The new window must be entirely on-screen, and not overlap with an existing window.
    01318    * Eight starting points are tried, two at each corner.
    01319    */
    01320   const Window *w;
    01321   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01322     if (w->window_class == WC_MAIN_WINDOW) continue;
    01323
    01324     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01325     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01326     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01327     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01328     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
    01329     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
    01330     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
    01331     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
    01332   }
    01333
    01334   /* Third attempt, try around all existing windows with a distance of 2 pixels.
    01335    * The new window may be partly off-screen, and must not overlap with an existing window.
    01336    * Only four starting points are tried.
    01337    */
    01338   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01339     if (w->window_class == WC_MAIN_WINDOW) continue;
    01340
    01341     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01342     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01343     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01344     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01345   }
    01346
    01347   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
    01348    * of (+5, +5)
    01349    */
    01350   int left = 0, top = 24;
    01351
    01352 restart:
    01353   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01354     if (w->left == left && w->top == top) {
    01355       left += 5;
    01356       top += 5;
    01357       goto restart;
    01358     }
    01359   }
    01360
    01361   pt.x = left;
    01362   pt.y = top;
    01363   return pt;
    01364 }
    01365
    01372 Point GetToolbarAlignedWindowPosition(int window_width)
    01373 {
    01374   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01375   assert(w != NULL);
    01376   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
    01377   return pt;
    01378 }
    01379
    01397 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01398 {
    01399   Point pt;
    01400   const Window *w;
    01401
    01402   int16 default_width  = max(desc->default_width,  sm_width);
    01403   int16 default_height = max(desc->default_height, sm_height);
    01404
    01405   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
    01406       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
    01407       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
    01408
    01409     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
    01410     if (pt.x > _screen.width + 10 - default_width) {
    01411       pt.x = (_screen.width + 10 - default_width) - 20;
    01412     }
    01413     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
    01414     return pt;
    01415   }
    01416
    01417   switch (desc->default_pos) {
    01418     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
    01419       return GetToolbarAlignedWindowPosition(default_width);
    01420
    01421     case WDP_AUTO: // Find a good automatic position for the window
    01422       return GetAutoPlacePosition(default_width, default_height);
    01423
    01424     case WDP_CENTER: // Centre the window horizontally
    01425       pt.x = (_screen.width - default_width) / 2;
    01426       pt.y = (_screen.height - default_height) / 2;
    01427       break;
    01428
    01429     case WDP_MANUAL:
    01430       pt.x = 0;
    01431       pt.y = 0;
    01432       break;
    01433
    01434     default:
    01435       NOT_REACHED();
    01436   }
    01437
    01438   return pt;
    01439 }
    01440
    01441 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01442 {
    01443   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
    01444 }
    01445
    01454 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
    01455 {
    01456   int biggest_index = -1;
    01457   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
    01458   this->nested_array_size = (uint)(biggest_index + 1);
    01459
    01460   if (fill_nested) {
    01461     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01462     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
    01463   }
    01464 }
    01465
    01471 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
    01472 {
    01473   this->InitializeData(desc, window_number);
    01474   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
    01475   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
    01476   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
    01477 }
    01478
    01484 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
    01485 {
    01486   this->CreateNestedTree(desc, false);
    01487   this->FinishInitNested(desc, window_number);
    01488 }
    01489
    01491 Window::Window() : scrolling_scrollbar(-1)
    01492 {
    01493 }
    01494
    01502 Window *FindWindowFromPt(int x, int y)
    01503 {
    01504   Window *w;
    01505   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01506     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
    01507       return w;
    01508     }
    01509   }
    01510
    01511   return NULL;
    01512 }
    01513
    01517 void InitWindowSystem()
    01518 {
    01519   IConsoleClose();
    01520
    01521   _z_back_window = NULL;
    01522   _z_front_window = NULL;
    01523   _focused_window = NULL;
    01524   _mouseover_last_w = NULL;
    01525   _last_scroll_window = NULL;
    01526   _scrolling_viewport = false;
    01527   _mouse_hovering = false;
    01528
    01529   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    01530   NWidgetScrollbar::InvalidateDimensionCache();
    01531
    01532   ShowFirstError();
    01533 }
    01534
    01538 void UnInitWindowSystem()
    01539 {
    01540   UnshowCriticalError();
    01541
    01542   Window *w;
    01543   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
    01544
    01545   for (w = _z_front_window; w != NULL; /* nothing */) {
    01546     Window *to_del = w;
    01547     w = w->z_back;
    01548     free(to_del);
    01549   }
    01550
    01551   _z_front_window = NULL;
    01552   _z_back_window = NULL;
    01553 }
    01554
    01558 void ResetWindowSystem()
    01559 {
    01560   UnInitWindowSystem();
    01561   InitWindowSystem();
    01562   _thd.Reset();
    01563 }
    01564
    01565 static void DecreaseWindowCounters()
    01566 {
    01567   Window *w;
    01568   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01569     if (_scroller_click_timeout == 0) {
    01570       /* Unclick scrollbar buttons if they are pressed. */
    01571       for (uint i = 0; i < w->nested_array_size; i++) {
    01572         NWidgetBase *nwid = w->nested_array[i];
    01573         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
    01574           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
    01575           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
    01576             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
    01577             w->scrolling_scrollbar = -1;
    01578             sb->SetDirty(w);
    01579           }
    01580         }
    01581       }
    01582     }
    01583     w->OnMouseLoop();
    01584   }
    01585
    01586   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01587     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
    01588       CLRBITS(w->flags, WF_TIMEOUT);
    01589
    01590       w->OnTimeout();
    01591       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
    01592     }
    01593   }
    01594 }
    01595
    01596 static void HandlePlacePresize()
    01597 {
    01598   if (_special_mouse_mode != WSM_PRESIZE) return;
    01599
    01600   Window *w = _thd.GetCallbackWnd();
    01601   if (w == NULL) return;
    01602
    01603   Point pt = GetTileBelowCursor();
    01604   if (pt.x == -1) {
    01605     _thd.selend.x = -1;
    01606     return;
    01607   }
    01608
    01609   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
    01610 }
    01611
    01616 static EventState HandleMouseDragDrop()
    01617 {
    01618   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
    01619
    01620   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
    01621
    01622   Window *w = _thd.GetCallbackWnd();
    01623   if (w != NULL) {
    01624     /* Send an event in client coordinates. */
    01625     Point pt;
    01626     pt.x = _cursor.pos.x - w->left;
    01627     pt.y = _cursor.pos.y - w->top;
    01628     if (_left_button_down) {
    01629       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01630     } else {
    01631       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01632     }
    01633   }
    01634
    01635   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
    01636   return ES_HANDLED;
    01637 }
    01638
    01640 static void HandleMouseOver()
    01641 {
    01642   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    01643
    01644   /* We changed window, put a MOUSEOVER event to the last window */
    01645   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
    01646     /* Reset mouse-over coordinates of previous window */
    01647     Point pt = { -1, -1 };
    01648     _mouseover_last_w->OnMouseOver(pt, 0);
    01649   }
    01650
    01651   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
    01652   _mouseover_last_w = w;
    01653
    01654   if (w != NULL) {
    01655     /* send an event in client coordinates. */
    01656     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
    01657     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
    01658     if (widget != NULL) w->OnMouseOver(pt, widget->index);
    01659   }
    01660 }
    01661
    01663 static const int MIN_VISIBLE_TITLE_BAR = 13;
    01664
    01666 enum PreventHideDirection {
    01667   PHD_UP,
    01668   PHD_DOWN,
    01669 };
    01670
    01681 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
    01682 {
    01683   if (v == NULL) return;
    01684
    01685   int v_bottom = v->top + v->height;
    01686   int v_right = v->left + v->width;
    01687   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.
    01688
    01689   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
    01690   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
    01691
    01692   /* Vertically, the rectangle is hidden behind v. */
    01693   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
    01694     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
    01695     return;
    01696   }
    01697   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
    01698     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
    01699     return;
    01700   }
    01701
    01702   /* Horizontally also hidden, force movement to a safe area. */
    01703   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
    01704     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
    01705   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
    01706     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
    01707   } else {
    01708     *ny = safe_y;
    01709   }
    01710 }
    01711
    01719 static void EnsureVisibleCaption(Window *w, int nx, int ny)
    01720 {
    01721   /* Search for the title bar rectangle. */
    01722   Rect caption_rect;
    01723   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
    01724   if (caption != NULL) {
    01725     caption_rect.left   = caption->pos_x;
    01726     caption_rect.right  = caption->pos_x + caption->current_x;
    01727     caption_rect.top    = caption->pos_y;
    01728     caption_rect.bottom = caption->pos_y + caption->current_y;
    01729
    01730     /* Make sure the window doesn't leave the screen */
    01731     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
    01732     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
    01733
    01734     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
    01735     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
    01736     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
    01737   }
    01738
    01739   if (w->viewport != NULL) {
    01740     w->viewport->left += nx - w->left;
    01741     w->viewport->top  += ny - w->top;
    01742   }
    01743
    01744   w->left = nx;
    01745   w->top  = ny;
    01746 }
    01747
    01757 void ResizeWindow(Window *w, int delta_x, int delta_y)
    01758 {
    01759   if (delta_x != 0 || delta_y != 0) {
    01760     /* Determine the new right/bottom position. If that is outside of the bounds of
    01761      * the resolution clamp it in such a manner that it stays within the bounds. */
    01762     int new_right  = w->left + w->width  + delta_x;
    01763     int new_bottom = w->top  + w->height + delta_y;
    01764     if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
    01765     if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
    01766
    01767     w->SetDirty();
    01768
    01769     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);
    01770     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);
    01771     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
    01772     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
    01773
    01774     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);
    01775     w->width  = w->nested_root->current_x;
    01776     w->height = w->nested_root->current_y;
    01777   }
    01778
    01779   EnsureVisibleCaption(w, w->left, w->top);
    01780
    01781   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
    01782   w->OnResize();
    01783   w->SetDirty();
    01784 }
    01785
    01791 int GetMainViewTop()
    01792 {
    01793   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01794   return (w == NULL) ? 0 : w->top + w->height;
    01795 }
    01796
    01802 int GetMainViewBottom()
    01803 {
    01804   Window *w = FindWindowById(WC_STATUS_BAR, 0);
    01805   return (w == NULL) ? _screen.height : w->top;
    01806 }
    01807
    01808 static bool _dragging_window;
    01809
    01814 static EventState HandleWindowDragging()
    01815 {
    01816   /* Get out immediately if no window is being dragged at all. */
    01817   if (!_dragging_window) return ES_NOT_HANDLED;
    01818
    01819   /* If button still down, but cursor hasn't moved, there is nothing to do. */
    01820   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
    01821
    01822   /* Otherwise find the window... */
    01823   Window *w;
    01824   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01825     if (w->flags & WF_DRAGGING) {
    01826       /* Stop the dragging if the left mouse button was released */
    01827       if (!_left_button_down) {
    01828         w->flags &= ~WF_DRAGGING;
    01829         break;
    01830       }
    01831
    01832       w->SetDirty();
    01833
    01834       int x = _cursor.pos.x + _drag_delta.x;
    01835       int y = _cursor.pos.y + _drag_delta.y;
    01836       int nx = x;
    01837       int ny = y;
    01838
    01839       if (_settings_client.gui.window_snap_radius != 0) {
    01840         const Window *v;
    01841
    01842         int hsnap = _settings_client.gui.window_snap_radius;
    01843         int vsnap = _settings_client.gui.window_snap_radius;
    01844         int delta;
    01845
    01846         FOR_ALL_WINDOWS_FROM_BACK(v) {
    01847           if (v == w) continue; // Don't snap at yourself
    01848
    01849           if (y + w->height > v->top && y < v->top + v->height) {
    01850             /* Your left border <-> other right border */
    01851             delta = abs(v->left + v->width - x);
    01852             if (delta <= hsnap) {
    01853               nx = v->left + v->width;
    01854               hsnap = delta;
    01855             }
    01856
    01857             /* Your right border <-> other left border */
    01858             delta = abs(v->left - x - w->width);
    01859             if (delta <= hsnap) {
    01860               nx = v->left - w->width;
    01861               hsnap = delta;
    01862             }
    01863           }
    01864
    01865           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
    01866             /* Your left border <-> other left border */
    01867             delta = abs(v->left - x);
    01868             if (delta <= hsnap) {
    01869               nx = v->left;
    01870               hsnap = delta;
    01871             }
    01872
    01873             /* Your right border <-> other right border */
    01874             delta = abs(v->left + v->width - x - w->width);
    01875             if (delta <= hsnap) {
    01876               nx = v->left + v->width - w->width;
    01877               hsnap = delta;
    01878             }
    01879           }
    01880
    01881           if (x + w->width > v->left && x < v->left + v->width) {
    01882             /* Your top border <-> other bottom border */
    01883             delta = abs(v->top + v->height - y);
    01884             if (delta <= vsnap) {
    01885               ny = v->top + v->height;
    01886               vsnap = delta;
    01887             }
    01888
    01889             /* Your bottom border <-> other top border */
    01890             delta = abs(v->top - y - w->height);
    01891             if (delta <= vsnap) {
    01892               ny = v->top - w->height;
    01893               vsnap = delta;
    01894             }
    01895           }
    01896
    01897           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
    01898             /* Your top border <-> other top border */
    01899             delta = abs(v->top - y);
    01900             if (delta <= vsnap) {
    01901               ny = v->top;
    01902               vsnap = delta;
    01903             }
    01904
    01905             /* Your bottom border <-> other bottom border */
    01906             delta = abs(v->top + v->height - y - w->height);
    01907             if (delta <= vsnap) {
    01908               ny = v->top + v->height - w->height;
    01909               vsnap = delta;
    01910             }
    01911           }
    01912         }
    01913       }
    01914
    01915       EnsureVisibleCaption(w, nx, ny);
    01916
    01917       w->SetDirty();
    01918       return ES_HANDLED;
    01919     } else if (w->flags & WF_SIZING) {
    01920       /* Stop the sizing if the left mouse button was released */
    01921       if (!_left_button_down) {
    01922         w->flags &= ~WF_SIZING;
    01923         w->SetDirty();
    01924         break;
    01925       }
    01926
    01927       /* Compute difference in pixels between cursor position and reference point in the window.
    01928        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
    01929        */
    01930       int x, y = _cursor.pos.y - _drag_delta.y;
    01931       if (w->flags & WF_SIZING_LEFT) {
    01932         x = _drag_delta.x - _cursor.pos.x;
    01933       } else {
    01934         x = _cursor.pos.x - _drag_delta.x;
    01935       }
    01936
    01937       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
    01938       if (w->resize.step_width  == 0) x = 0;
    01939       if (w->resize.step_height == 0) y = 0;
    01940
    01941       /* Check the resize button won't go past the bottom of the screen */
    01942       if (w->top + w->height + y > _screen.height) {
    01943         y = _screen.height - w->height - w->top;
    01944       }
    01945
    01946       /* X and Y has to go by step.. calculate it.
    01947        * The cast to int is necessary else x/y are implicitly casted to
    01948        * unsigned int, which won't work. */
    01949       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
    01950       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
    01951
    01952       /* Check that we don't go below the minimum set size */
    01953       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
    01954         x = w->nested_root->smallest_x - w->width;
    01955       }
    01956       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
    01957         y = w->nested_root->smallest_y - w->height;
    01958       }
    01959
    01960       /* Window already on size */
    01961       if (x == 0 && y == 0) return ES_HANDLED;
    01962
    01963       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
    01964       _drag_delta.y += y;
    01965       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
    01966         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
    01967         w->SetDirty();
    01968         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
    01969         /* ResizeWindow() below ensures marking new position as dirty. */
    01970       } else {
    01971         _drag_delta.x += x;
    01972       }
    01973
    01974       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
    01975       ResizeWindow(w, x, y);
    01976       return ES_HANDLED;
    01977     }
    01978   }
    01979
    01980   _dragging_window = false;
    01981   return ES_HANDLED;
    01982 }
    01983
    01988 static void StartWindowDrag(Window *w)
    01989 {
    01990   w->flags |= WF_DRAGGING;
    01991   w->flags &= ~WF_CENTERED;
    01992   _dragging_window = true;
    01993
    01994   _drag_delta.x = w->left - _cursor.pos.x;
    01995   _drag_delta.y = w->top  - _cursor.pos.y;
    01996
    01997   BringWindowToFront(w);
    01998   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    01999 }
    02000
    02006 static void StartWindowSizing(Window *w, bool to_left)
    02007 {
    02008   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
    02009   w->flags &= ~WF_CENTERED;
    02010   _dragging_window = true;
    02011
    02012   _drag_delta.x = _cursor.pos.x;
    02013   _drag_delta.y = _cursor.pos.y;
    02014
    02015   BringWindowToFront(w);
    02016   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    02017 }
    02018
    02023 static EventState HandleScrollbarScrolling()
    02024 {
    02025   Window *w;
    02026   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02027     if (w->scrolling_scrollbar >= 0) {
    02028       /* Abort if no button is clicked any more. */
    02029       if (!_left_button_down) {
    02030         w->scrolling_scrollbar = -1;
    02031         w->SetDirty();
    02032         return ES_HANDLED;
    02033       }
    02034
    02035       int i;
    02036       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
    02037       bool rtl = false;
    02038
    02039       if (sb->type == NWID_HSCROLLBAR) {
    02040         i = _cursor.pos.x - _cursorpos_drag_start.x;
    02041         rtl = _current_text_dir == TD_RTL;
    02042       } else {
    02043         i = _cursor.pos.y - _cursorpos_drag_start.y;
    02044       }
    02045
    02046       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
    02047         if (_scroller_click_timeout == 1) {
    02048           _scroller_click_timeout = 3;
    02049           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
    02050           w->SetDirty();
    02051         }
    02052         return ES_HANDLED;
    02053       }
    02054
    02055       /* Find the item we want to move to and make sure it's inside bounds. */
    02056       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
    02057       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
    02058       if (pos != sb->GetPosition()) {
    02059         sb->SetPosition(pos);
    02060         w->SetDirty();
    02061       }
    02062       return ES_HANDLED;
    02063     }
    02064   }
    02065
    02066   return ES_NOT_HANDLED;
    02067 }
    02068
    02073 static EventState HandleViewportScroll()
    02074 {
    02075   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02076
    02077   if (!_scrolling_viewport) return ES_NOT_HANDLED;
    02078
    02079   /* When we don't have a last scroll window we are starting to scroll.
    02080    * When the last scroll window and this are not the same we went
    02081    * outside of the window and should not left-mouse scroll anymore. */
    02082   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    02083
    02084   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
    02085     _cursor.fix_at = false;
    02086     _scrolling_viewport = false;
    02087     _last_scroll_window = NULL;
    02088     return ES_NOT_HANDLED;
    02089   }
    02090
    02091   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
    02092     /* If the main window is following a vehicle, then first let go of it! */
    02093     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
    02094     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
    02095     return ES_NOT_HANDLED;
    02096   }
    02097
    02098   Point delta;
    02099   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
    02100     delta.x = -_cursor.delta.x;
    02101     delta.y = -_cursor.delta.y;
    02102   } else {
    02103     delta.x = _cursor.delta.x;
    02104     delta.y = _cursor.delta.y;
    02105   }
    02106
    02107   if (scrollwheel_scrolling) {
    02108     /* We are using scrollwheels for scrolling */
    02109     delta.x = _cursor.h_wheel;
    02110     delta.y = _cursor.v_wheel;
    02111     _cursor.v_wheel = 0;
    02112     _cursor.h_wheel = 0;
    02113   }
    02114
    02115   /* Create a scroll-event and send it to the window */
    02116   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
    02117
    02118   _cursor.delta.x = 0;
    02119   _cursor.delta.y = 0;
    02120   return ES_HANDLED;
    02121 }
    02122
    02133 static bool MaybeBringWindowToFront(Window *w)
    02134 {
    02135   bool bring_to_front = false;
    02136
    02137   if (w->window_class == WC_MAIN_WINDOW ||
    02138       IsVitalWindow(w) ||
    02139       w->window_class == WC_TOOLTIPS ||
    02140       w->window_class == WC_DROPDOWN_MENU) {
    02141     return true;
    02142   }
    02143
    02144   /* Use unshaded window size rather than current size for shaded windows. */
    02145   int w_width  = w->width;
    02146   int w_height = w->height;
    02147   if (w->IsShaded()) {
    02148     w_width  = w->unshaded_size.width;
    02149     w_height = w->unshaded_size.height;
    02150   }
    02151
    02152   Window *u;
    02153   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
    02154     /* A modal child will prevent the activation of the parent window */
    02155     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
    02156       u->SetWhiteBorder();
    02157       u->SetDirty();
    02158       return false;
    02159     }
    02160
    02161     if (u->window_class == WC_MAIN_WINDOW ||
    02162         IsVitalWindow(u) ||
    02163         u->window_class == WC_TOOLTIPS ||
    02164         u->window_class == WC_DROPDOWN_MENU) {
    02165       continue;
    02166     }
    02167
    02168     /* Window sizes don't interfere, leave z-order alone */
    02169     if (w->left + w_width <= u->left ||
    02170         u->left + u->width <= w->left ||
    02171         w->top  + w_height <= u->top ||
    02172         u->top + u->height <= w->top) {
    02173       continue;
    02174     }
    02175
    02176     bring_to_front = true;
    02177   }
    02178
    02179   if (bring_to_front) BringWindowToFront(w);
    02180   return true;
    02181 }
    02182
    02187 void HandleKeypress(uint32 raw_key)
    02188 {
    02189   /* World generation is multithreaded and messes with companies.
    02190    * But there is no company related window open anyway, so _current_company is not used. */
    02191   assert(HasModalProgress() || IsLocalCompany());
    02192
    02193   /* Setup event */
    02194   uint16 key     = GB(raw_key,  0, 16);
    02195   uint16 keycode = GB(raw_key, 16, 16);
    02196
    02197   /*
    02198    * The Unicode standard defines an area called the private use area. Code points in this
    02199    * area are reserved for private use and thus not portable between systems. For instance,
    02200    * Apple defines code points for the arrow keys in this area, but these are only printable
    02201    * on a system running OS X. We don't want these keys to show up in text fields and such,
    02202    * and thus we have to clear the unicode character when we encounter such a key.
    02203    */
    02204   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
    02205
    02206   /*
    02207    * If both key and keycode is zero, we don't bother to process the event.
    02208    */
    02209   if (key == 0 && keycode == 0) return;
    02210
    02211   /* Check if the focused window has a focused editbox */
    02212   if (EditBoxInGlobalFocus()) {
    02213     /* All input will in this case go to the focused window */
    02214     if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02215   }
    02216
    02217   /* Call the event, start with the uppermost window, but ignore the toolbar. */
    02218   Window *w;
    02219   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02220     if (w->window_class == WC_MAIN_TOOLBAR) continue;
    02221     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02222   }
    02223
    02224   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    02225   /* When there is no toolbar w is null, check for that */
    02226   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
    02227
    02228   HandleGlobalHotkeys(key, keycode);
    02229 }
    02230
    02234 void HandleCtrlChanged()
    02235 {
    02236   /* Call the event, start with the uppermost window. */
    02237   Window *w;
    02238   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02239     if (w->OnCTRLStateChange() == ES_HANDLED) return;
    02240   }
    02241 }
    02242
    02249 static int _input_events_this_tick = 0;
    02250
    02255 static void HandleAutoscroll()
    02256 {
    02257   if (_settings_client.gui.autoscroll && _game_mode != GM_MENU && !HasModalProgress()) {
    02258     int x = _cursor.pos.x;
    02259     int y = _cursor.pos.y;
    02260     Window *w = FindWindowFromPt(x, y);
    02261     if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
    02262     ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02263     if (vp != NULL) {
    02264       x -= vp->left;
    02265       y -= vp->top;
    02266
    02267       /* here allows scrolling in both x and y axis */
    02268 #define scrollspeed 3
    02269       if (x - 15 < 0) {
    02270         w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
    02271       } else if (15 - (vp->width - x) > 0) {
    02272         w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
    02273       }
    02274       if (y - 15 < 0) {
    02275         w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
    02276       } else if (15 - (vp->height - y) > 0) {
    02277         w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
    02278       }
    02279 #undef scrollspeed
    02280     }
    02281   }
    02282 }
    02283
    02284 enum MouseClick {
    02285   MC_NONE = 0,
    02286   MC_LEFT,
    02287   MC_RIGHT,
    02288   MC_DOUBLE_LEFT,
    02289   MC_HOVER,
    02290
    02291   MAX_OFFSET_DOUBLE_CLICK = 5,
    02292   TIME_BETWEEN_DOUBLE_CLICK = 500,
    02293   MAX_OFFSET_HOVER = 5,
    02294 };
    02295 extern EventState VpHandlePlaceSizingDrag();
    02296
    02297 static void ScrollMainViewport(int x, int y)
    02298 {
    02299   if (_game_mode != GM_MENU) {
    02300     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
    02301     assert(w);
    02302
    02303     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
    02304     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
    02305   }
    02306 }
    02307
    02317 static const int8 scrollamt[16][2] = {
    02318   { 0,  0},
    02319   {-2,  0},
    02320   { 0, -2},
    02321   {-2, -1},
    02322   { 2,  0},
    02323   { 0,  0},
    02324   { 2, -1},
    02325   { 0, -2},
    02326   { 0,  2},
    02327   {-2,  1},
    02328   { 0,  0},
    02329   {-2,  0},
    02330   { 2,  1},
    02331   { 0,  2},
    02332   { 2,  0},
    02333   { 0,  0},
    02334 };
    02335
    02336 static void HandleKeyScrolling()
    02337 {
    02338   /*
    02339    * Check that any of the dirkeys is pressed and that the focused window
    02340    * dont has an edit-box as focused widget.
    02341    */
    02342   if (_dirkeys && !EditBoxInGlobalFocus()) {
    02343     int factor = _shift_pressed ? 50 : 10;
    02344     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
    02345   }
    02346 }
    02347
    02348 static void MouseLoop(MouseClick click, int mousewheel)
    02349 {
    02350   /* World generation is multithreaded and messes with companies.
    02351    * But there is no company related window open anyway, so _current_company is not used. */
    02352   assert(HasModalProgress() || IsLocalCompany());
    02353
    02354   HandlePlacePresize();
    02355   UpdateTileSelection();
    02356
    02357   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
    02358   if (HandleMouseDragDrop()      == ES_HANDLED) return;
    02359   if (HandleWindowDragging()     == ES_HANDLED) return;
    02360   if (HandleScrollbarScrolling() == ES_HANDLED) return;
    02361   if (HandleViewportScroll()     == ES_HANDLED) return;
    02362
    02363   HandleMouseOver();
    02364
    02365   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02366   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
    02367
    02368   int x = _cursor.pos.x;
    02369   int y = _cursor.pos.y;
    02370   Window *w = FindWindowFromPt(x, y);
    02371   if (w == NULL) return;
    02372
    02373   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
    02374   ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02375
    02376   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
    02377   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
    02378
    02379   if (mousewheel != 0) {
    02380     /* Send mousewheel event to window */
    02381     w->OnMouseWheel(mousewheel);
    02382
    02383     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
    02384     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
    02385   }
    02386
    02387   if (vp != NULL) {
    02388     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
    02389     switch (click) {
    02390       case MC_DOUBLE_LEFT:
    02391       case MC_LEFT:
    02392         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
    02393         if (!HandleViewportClicked(vp, x, y) &&
    02394             !(w->flags & WF_DISABLE_VP_SCROLL) &&
    02395             _settings_client.gui.left_mouse_btn_scrolling) {
    02396           _scrolling_viewport = true;
    02397           _cursor.fix_at = false;
    02398         }
    02399         break;
    02400
    02401       case MC_RIGHT:
    02402         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
    02403           _scrolling_viewport = true;
    02404           _cursor.fix_at = true;
    02405
    02406           /* clear 2D scrolling caches before we start a 2D scroll */
    02407           _cursor.h_wheel = 0;
    02408           _cursor.v_wheel = 0;
    02409         }
    02410         break;
    02411
    02412       default:
    02413         break;
    02414     }
    02415   } else {
    02416     switch (click) {
    02417       case MC_LEFT:
    02418       case MC_DOUBLE_LEFT:
    02419         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
    02420         break;
    02421
    02422       default:
    02423         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
    02424         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
    02425          * Simulate a right button click so we can get started. */
    02426         /* FALL THROUGH */
    02427
    02428       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
    02429
    02430       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
    02431     }
    02432   }
    02433 }
    02434
    02438 void HandleMouseEvents()
    02439 {
    02440   /* World generation is multithreaded and messes with companies.
    02441    * But there is no company related window open anyway, so _current_company is not used. */
    02442   assert(HasModalProgress() || IsLocalCompany());
    02443
    02444   static int double_click_time = 0;
    02445   static Point double_click_pos = {0, 0};
    02446
    02447   /* Mouse event? */
    02448   MouseClick click = MC_NONE;
    02449   if (_left_button_down && !_left_button_clicked) {
    02450     click = MC_LEFT;
    02451     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
    02452         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
    02453         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
    02454       click = MC_DOUBLE_LEFT;
    02455     }
    02456     double_click_time = _realtime_tick;
    02457     double_click_pos = _cursor.pos;
    02458     _left_button_clicked = true;
    02459     _input_events_this_tick++;
    02460   } else if (_right_button_clicked) {
    02461     _right_button_clicked = false;
    02462     click = MC_RIGHT;
    02463     _input_events_this_tick++;
    02464   }
    02465
    02466   int mousewheel = 0;
    02467   if (_cursor.wheel) {
    02468     mousewheel = _cursor.wheel;
    02469     _cursor.wheel = 0;
    02470     _input_events_this_tick++;
    02471   }
    02472
    02473   static uint32 hover_time = 0;
    02474   static Point hover_pos = {0, 0};
    02475
    02476   if (_settings_client.gui.hover_delay > 0) {
    02477     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
    02478         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
    02479         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
    02480       hover_pos = _cursor.pos;
    02481       hover_time = _realtime_tick;
    02482       _mouse_hovering = false;
    02483     } else {
    02484       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
    02485         click = MC_HOVER;
    02486         _input_events_this_tick++;
    02487         _mouse_hovering = true;
    02488       }
    02489     }
    02490   }
    02491
    02492   /* Handle sprite picker before any GUI interaction */
    02493   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
    02494     /* Next realtime tick? Then redraw has finished */
    02495     _newgrf_debug_sprite_picker.mode = SPM_NONE;
    02496     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
    02497   }
    02498
    02499   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
    02500     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
    02501     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    02502     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
    02503     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
    02504     _newgrf_debug_sprite_picker.sprites.Clear();
    02505     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
    02506     MarkWholeScreenDirty();
    02507   } else {
    02508     MouseLoop(click, mousewheel);
    02509   }
    02510
    02511   /* We have moved the mouse the required distance,
    02512    * no need to move it at any later time. */
    02513   _cursor.delta.x = 0;
    02514   _cursor.delta.y = 0;
    02515 }
    02516
    02520 static void CheckSoftLimit()
    02521 {
    02522   if (_settings_client.gui.window_soft_limit == 0) return;
    02523
    02524   for (;;) {
    02525     uint deletable_count = 0;
    02526     Window *w, *last_deletable = NULL;
    02527     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02528       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
    02529
    02530       last_deletable = w;
    02531       deletable_count++;
    02532     }
    02533
    02534     /* We've not reached the soft limit yet. */
    02535     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
    02536
    02537     assert(last_deletable != NULL);
    02538     delete last_deletable;
    02539   }
    02540 }
    02541
    02545 void InputLoop()
    02546 {
    02547   /* World generation is multithreaded and messes with companies.
    02548    * But there is no company related window open anyway, so _current_company is not used. */
    02549   assert(HasModalProgress() || IsLocalCompany());
    02550
    02551   CheckSoftLimit();
    02552   HandleKeyScrolling();
    02553
    02554   /* Do the actual free of the deleted windows. */
    02555   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
    02556     Window *w = v;
    02557     v = v->z_back;
    02558
    02559     if (w->window_class != WC_INVALID) continue;
    02560
    02561     RemoveWindowFromZOrdering(w);
    02562     free(w);
    02563   }
    02564
    02565   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
    02566   DecreaseWindowCounters();
    02567
    02568   if (_input_events_this_tick != 0) {
    02569     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
    02570     _input_events_this_tick = 0;
    02571     /* there were some inputs this tick, don't scroll ??? */
    02572     return;
    02573   }
    02574
    02575   /* HandleMouseEvents was already called for this tick */
    02576   HandleMouseEvents();
    02577   HandleAutoscroll();
    02578 }
    02579
    02583 void UpdateWindows()
    02584 {
    02585   Window *w;
    02586
    02587   static int highlight_timer = 1;
    02588   if (--highlight_timer == 0) {
    02589     highlight_timer = 15;
    02590     _window_highlight_colour = !_window_highlight_colour;
    02591   }
    02592
    02593   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02594     w->ProcessScheduledInvalidations();
    02595     w->ProcessHighlightedInvalidations();
    02596   }
    02597
    02598   static int we4_timer = 0;
    02599   int t = we4_timer + 1;
    02600
    02601   if (t >= 100) {
    02602     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02603       w->OnHundredthTick();
    02604     }
    02605     t = 0;
    02606   }
    02607   we4_timer = t;
    02608
    02609   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02610     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
    02611       CLRBITS(w->flags, WF_WHITE_BORDER);
    02612       w->SetDirty();
    02613     }
    02614   }
    02615
    02616   DrawDirtyBlocks();
    02617
    02618   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02619     /* Update viewport only if window is not shaded. */
    02620     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
    02621   }
    02622   NetworkDrawChatMessage();
    02623   /* Redraw mouse cursor in case it was hidden */
    02624   DrawMouseCursor();
    02625 }
    02626
    02632 void SetWindowDirty(WindowClass cls, WindowNumber number)
    02633 {
    02634   const Window *w;
    02635   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02636     if (w->window_class == cls && w->window_number == number) w->SetDirty();
    02637   }
    02638 }
    02639
    02646 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
    02647 {
    02648   const Window *w;
    02649   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02650     if (w->window_class == cls && w->window_number == number) {
    02651       w->SetWidgetDirty(widget_index);
    02652     }
    02653   }
    02654 }
    02655
    02660 void SetWindowClassesDirty(WindowClass cls)
    02661 {
    02662   Window *w;
    02663   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02664     if (w->window_class == cls) w->SetDirty();
    02665   }
    02666 }
    02667
    02673 void Window::InvalidateData(int data, bool gui_scope)
    02674 {
    02675   this->SetDirty();
    02676   if (!gui_scope) {
    02677     /* Schedule GUI-scope invalidation for next redraw. */
    02678     *this->scheduled_invalidation_data.Append() = data;
    02679   }
    02680   this->OnInvalidateData(data, gui_scope);
    02681 }
    02682
    02686 void Window::ProcessScheduledInvalidations()
    02687 {
    02688   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
    02689     this->OnInvalidateData(*data, true);
    02690   }
    02691   this->scheduled_invalidation_data.Clear();
    02692 }
    02693
    02697 void Window::ProcessHighlightedInvalidations()
    02698 {
    02699   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
    02700
    02701   for (uint i = 0; i < this->nested_array_size; i++) {
    02702     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
    02703   }
    02704 }
    02705
    02732 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
    02733 {
    02734   Window *w;
    02735   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02736     if (w->window_class == cls && w->window_number == number) {
    02737       w->InvalidateData(data, gui_scope);
    02738     }
    02739   }
    02740 }
    02741
    02750 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
    02751 {
    02752   Window *w;
    02753
    02754   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02755     if (w->window_class == cls) {
    02756       w->InvalidateData(data, gui_scope);
    02757     }
    02758   }
    02759 }
    02760
    02764 void CallWindowTickEvent()
    02765 {
    02766   Window *w;
    02767   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02768     w->OnTick();
    02769   }
    02770 }
    02771
    02778 void DeleteNonVitalWindows()
    02779 {
    02780   Window *w;
    02781
    02782 restart_search:
    02783   /* When we find the window to delete, we need to restart the search
    02784    * as deleting this window could cascade in deleting (many) others
    02785    * anywhere in the z-array */
    02786   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02787     if (w->window_class != WC_MAIN_WINDOW &&
    02788         w->window_class != WC_SELECT_GAME &&
    02789         w->window_class != WC_MAIN_TOOLBAR &&
    02790         w->window_class != WC_STATUS_BAR &&
    02791         w->window_class != WC_TOOLTIPS &&
    02792         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
    02793
    02794       delete w;
    02795       goto restart_search;
    02796     }
    02797   }
    02798 }
    02799
    02807 void DeleteAllNonVitalWindows()
    02808 {
    02809   Window *w;
    02810
    02811   /* Delete every window except for stickied ones, then sticky ones as well */
    02812   DeleteNonVitalWindows();
    02813
    02814 restart_search:
    02815   /* When we find the window to delete, we need to restart the search
    02816    * as deleting this window could cascade in deleting (many) others
    02817    * anywhere in the z-array */
    02818   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02819     if (w->flags & WF_STICKY) {
    02820       delete w;
    02821       goto restart_search;
    02822     }
    02823   }
    02824 }
    02825
    02830 void DeleteConstructionWindows()
    02831 {
    02832   Window *w;
    02833
    02834 restart_search:
    02835   /* When we find the window to delete, we need to restart the search
    02836    * as deleting this window could cascade in deleting (many) others
    02837    * anywhere in the z-array */
    02838   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02839     if (w->desc_flags & WDF_CONSTRUCTION) {
    02840       delete w;
    02841       goto restart_search;
    02842     }
    02843   }
    02844
    02845   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
    02846 }
    02847
    02849 void HideVitalWindows()
    02850 {
    02851   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
    02852   DeleteWindowById(WC_STATUS_BAR, 0);
    02853 }
    02854
    02856 void ReInitAllWindows()
    02857 {
    02858   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    02859   NWidgetScrollbar::InvalidateDimensionCache();
    02860
    02861   Window *w;
    02862   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02863     w->ReInit();
    02864   }
    02865 #ifdef ENABLE_NETWORK
    02866   void NetworkReInitChatBoxSize();
    02867   NetworkReInitChatBoxSize();
    02868 #endif
    02869
    02870   /* Make sure essential parts of all windows are visible */
    02871   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
    02872   MarkWholeScreenDirty();
    02873 }
    02874
    02882 static int PositionWindow(Window *w, WindowClass clss, int setting)
    02883 {
    02884   if (w == NULL || w->window_class != clss) {
    02885     w = FindWindowById(clss, 0);
    02886   }
    02887   if (w == NULL) return 0;
    02888
    02889   int old_left = w->left;
    02890   switch (setting) {
    02891     case 1:  w->left = (_screen.width - w->width) / 2; break;
    02892     case 2:  w->left = _screen.width - w->width; break;
    02893     default: w->left = 0; break;
    02894   }
    02895   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
    02896   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
    02897   return w->left;
    02898 }
    02899
    02905 int PositionMainToolbar(Window *w)
    02906 {
    02907   DEBUG(misc, 5, "Repositioning Main Toolbar...");
    02908   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
    02909 }
    02910
    02916 int PositionStatusbar(Window *w)
    02917 {
    02918   DEBUG(misc, 5, "Repositioning statusbar...");
    02919   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
    02920 }
    02921
    02927 int PositionNewsMessage(Window *w)
    02928 {
    02929   DEBUG(misc, 5, "Repositioning news message...");
    02930   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
    02931 }
    02932
    02938 int PositionNetworkChatWindow(Window *w)
    02939 {
    02940   DEBUG(misc, 5, "Repositioning network chat window...");
    02941   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
    02942 }
    02943
    02944
    02950 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
    02951 {
    02952   Window *w;
    02953   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02954     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
    02955       w->viewport->follow_vehicle = to_index;
    02956       w->SetDirty();
    02957     }
    02958   }
    02959 }
    02960
    02961
    02967 void RelocateAllWindows(int neww, int newh)
    02968 {
    02969   Window *w;
    02970
    02971   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02972     int left, top;
    02973
    02974     if (w->window_class == WC_MAIN_WINDOW) {
    02975       ViewPort *vp = w->viewport;
    02976       vp->width = w->width = neww;
    02977       vp->height = w->height = newh;
    02978       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
    02979       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
    02980       continue; // don't modify top,left
    02981     }
    02982
    02983     /* XXX - this probably needs something more sane. For example specifying
    02984      * in a 'backup'-desc that the window should always be centered. */
    02985     switch (w->window_class) {
    02986       case WC_BOOTSTRAP:
    02987         ResizeWindow(w, neww, newh);
    02988         continue;
    02989
    02990       case WC_MAIN_TOOLBAR:
    02991         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0);
    02992
    02993         top = w->top;
    02994         left = PositionMainToolbar(w); // changes toolbar orientation
    02995         break;
    02996
    02997       case WC_NEWS_WINDOW:
    02998         top = newh - w->height;
    02999         left = PositionNewsMessage(w);
    03000         break;
    03001
    03002       case WC_STATUS_BAR:
    03003         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0);
    03004
    03005         top = newh - w->height;
    03006         left = PositionStatusbar(w);
    03007         break;
    03008
    03009       case WC_SEND_NETWORK_MSG:
    03010         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
    03011         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
    03012         left = PositionNetworkChatWindow(w);
    03013         break;
    03014
    03015       case WC_CONSOLE:
    03016         IConsoleResize(w);
    03017         continue;
    03018
    03019       default: {
    03020         if (w->flags & WF_CENTERED) {
    03021           top = (newh - w->height) >> 1;
    03022           left = (neww - w->width) >> 1;
    03023           break;
    03024         }
    03025
    03026         left = w->left;
    03027         if (left + (w->width >> 1) >= neww) left = neww - w->width;
    03028         if (left < 0) left = 0;
    03029
    03030         top = w->top;
    03031         if (top + (w->height >> 1) >= newh) top = newh - w->height;
    03032         break;
    03033       }
    03034     }
    03035
    03036     EnsureVisibleCaption(w, left, top);
    03037   }
    03038 }
    03039
    03045 PickerWindowBase::~PickerWindowBase()
    03046 {
    03047   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
    03048   ResetObjectToPlace();
    03049 }