# window.cpp

Go to the documentation of this file.

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