# window.cpp

Go to the documentation of this file.

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